aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/access/detection.cpp2
-rw-r--r--engines/access/sound.cpp18
-rw-r--r--engines/access/sound.h4
-rw-r--r--engines/adl/POTFILES1
-rw-r--r--engines/adl/adl.cpp1068
-rw-r--r--engines/adl/adl.h324
-rw-r--r--engines/adl/adl_v2.cpp539
-rw-r--r--engines/adl/adl_v2.h95
-rw-r--r--engines/adl/adl_v3.cpp259
-rw-r--r--engines/adl/adl_v3.h76
-rw-r--r--engines/adl/console.cpp333
-rw-r--r--engines/adl/console.h65
-rw-r--r--engines/adl/detection.cpp99
-rw-r--r--engines/adl/detection.h5
-rw-r--r--engines/adl/disk.cpp460
-rw-r--r--engines/adl/disk.h188
-rw-r--r--engines/adl/display.cpp63
-rw-r--r--engines/adl/display.h7
-rw-r--r--engines/adl/graphics.cpp69
-rw-r--r--engines/adl/graphics.h76
-rw-r--r--engines/adl/graphics_v1.cpp120
-rw-r--r--engines/adl/graphics_v2.cpp304
-rw-r--r--engines/adl/hires1.cpp352
-rw-r--r--engines/adl/hires1.h47
-rw-r--r--engines/adl/hires2.cpp183
-rw-r--r--engines/adl/hires2.h66
-rw-r--r--engines/adl/hires6.cpp444
-rw-r--r--engines/adl/hires6.h92
-rw-r--r--engines/adl/module.mk12
-rw-r--r--engines/adl/speaker.cpp94
-rw-r--r--engines/adl/speaker.h (renamed from engines/sci/graphics/paint.h)28
-rw-r--r--engines/agi/global.cpp1
-rw-r--r--engines/agi/graphics.cpp8
-rw-r--r--engines/agi/lzw.cpp1
-rw-r--r--engines/agi/picture.cpp2
-rw-r--r--engines/agi/preagi.cpp8
-rw-r--r--engines/agi/preagi.h3
-rw-r--r--engines/agi/preagi_mickey.cpp6
-rw-r--r--engines/agi/preagi_mickey.h6
-rw-r--r--engines/agi/sound.cpp11
-rw-r--r--engines/agi/sound.h14
-rw-r--r--engines/agi/sound_2gs.cpp5
-rw-r--r--engines/agi/sound_2gs.h1
-rw-r--r--engines/agi/sound_pcjr.cpp5
-rw-r--r--engines/agi/sound_sarien.cpp6
-rw-r--r--engines/agi/text.cpp2
-rw-r--r--engines/agi/view.h2
-rw-r--r--engines/bbvs/spritemodule.cpp3
-rw-r--r--engines/cge/detection.cpp2
-rw-r--r--engines/cge2/detection.cpp2
-rw-r--r--engines/engine.cpp9
-rw-r--r--engines/gnap/character.cpp20
-rw-r--r--engines/gnap/configure.engine2
-rw-r--r--engines/gnap/datarchive.cpp1
-rw-r--r--engines/gnap/detection.cpp9
-rw-r--r--engines/gnap/fontdata.h192
-rw-r--r--engines/gnap/gamesys.cpp24
-rw-r--r--engines/gnap/gamesys.h2
-rw-r--r--engines/gnap/gnap.cpp103
-rw-r--r--engines/gnap/gnap.h8
-rw-r--r--engines/gnap/resource.cpp4
-rw-r--r--engines/gnap/scenes/arcade.cpp18
-rw-r--r--engines/gnap/scenes/arcade.h2
-rw-r--r--engines/gnap/scenes/group0.cpp4
-rw-r--r--engines/gnap/scenes/group1.cpp3
-rw-r--r--engines/gnap/scenes/group2.cpp1
-rw-r--r--engines/gnap/scenes/intro.cpp14
-rw-r--r--engines/gnap/sound.cpp22
-rw-r--r--engines/hopkins/detection.cpp2
-rw-r--r--engines/lab/detection.cpp2
-rw-r--r--engines/mads/detection.cpp2
-rw-r--r--engines/mohawk/bitmap.cpp10
-rw-r--r--engines/mohawk/cstime.h2
-rw-r--r--engines/mohawk/cstime_game.cpp5
-rw-r--r--engines/mohawk/cstime_ui.cpp4
-rw-r--r--engines/mohawk/cstime_view.cpp8
-rw-r--r--engines/mohawk/cursors.cpp14
-rw-r--r--engines/mohawk/cursors.h2
-rw-r--r--engines/mohawk/graphics.h8
-rw-r--r--engines/mohawk/myst_graphics.cpp81
-rw-r--r--engines/mohawk/myst_graphics.h4
-rw-r--r--engines/mohawk/riven.cpp14
-rw-r--r--engines/mohawk/riven_graphics.cpp2
-rw-r--r--engines/mohawk/view.cpp52
-rw-r--r--engines/mohawk/view.h17
-rw-r--r--engines/mortevielle/saveload.cpp2
-rw-r--r--engines/neverhood/detection.cpp17
-rw-r--r--engines/parallaction/dialogue.cpp2
-rw-r--r--engines/parallaction/font.cpp2
-rw-r--r--engines/parallaction/input.cpp2
-rw-r--r--engines/parallaction/parallaction_ns.cpp4
-rw-r--r--engines/pegasus/input.h2
-rw-r--r--engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp8
-rw-r--r--engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp2
-rw-r--r--engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp1
-rw-r--r--engines/pegasus/neighborhood/mars/mars.cpp8
-rw-r--r--engines/pegasus/neighborhood/norad/alpha/noradalpha.cpp1
-rw-r--r--engines/pegasus/neighborhood/norad/delta/globegame.cpp3
-rw-r--r--engines/pegasus/neighborhood/norad/pressuretracker.cpp1
-rw-r--r--engines/pegasus/neighborhood/wsc/wsc.cpp6
-rw-r--r--engines/prince/debugger.cpp2
-rw-r--r--engines/prince/graphics.cpp2
-rw-r--r--engines/prince/mob.h2
-rw-r--r--engines/prince/prince.cpp2
-rw-r--r--engines/prince/saveload.cpp2
-rw-r--r--engines/prince/script.cpp7
-rw-r--r--engines/prince/sound.cpp1
-rw-r--r--engines/saga/interface.cpp2
-rw-r--r--engines/saga/saveload.cpp2
-rw-r--r--engines/sci/console.cpp5
-rw-r--r--engines/sci/detection_tables.h2
-rw-r--r--engines/sci/engine/gc.cpp10
-rw-r--r--engines/sci/engine/kernel.h48
-rw-r--r--engines/sci/engine/kernel_tables.h188
-rw-r--r--engines/sci/engine/kfile.cpp50
-rw-r--r--engines/sci/engine/kgraphics.cpp2
-rw-r--r--engines/sci/engine/kgraphics32.cpp476
-rw-r--r--engines/sci/engine/kmisc.cpp12
-rw-r--r--engines/sci/engine/kpathing.cpp2
-rw-r--r--engines/sci/engine/ksound.cpp238
-rw-r--r--engines/sci/engine/savegame.cpp30
-rw-r--r--engines/sci/engine/savegame.h3
-rw-r--r--engines/sci/engine/script_patches.cpp215
-rw-r--r--engines/sci/engine/workarounds.cpp14
-rw-r--r--engines/sci/engine/workarounds.h2
-rw-r--r--engines/sci/graphics/celobj32.cpp109
-rw-r--r--engines/sci/graphics/celobj32.h14
-rw-r--r--engines/sci/graphics/controls32.cpp495
-rw-r--r--engines/sci/graphics/controls32.h390
-rw-r--r--engines/sci/graphics/frameout.cpp523
-rw-r--r--engines/sci/graphics/frameout.h49
-rw-r--r--engines/sci/graphics/helpers.h8
-rw-r--r--engines/sci/graphics/paint16.h4
-rw-r--r--engines/sci/graphics/paint32.cpp167
-rw-r--r--engines/sci/graphics/paint32.h44
-rw-r--r--engines/sci/graphics/palette32.cpp49
-rw-r--r--engines/sci/graphics/palette32.h13
-rw-r--r--engines/sci/graphics/plane32.cpp565
-rw-r--r--engines/sci/graphics/plane32.h141
-rw-r--r--engines/sci/graphics/remap.cpp297
-rw-r--r--engines/sci/graphics/remap.h107
-rw-r--r--engines/sci/graphics/remap32.cpp468
-rw-r--r--engines/sci/graphics/remap32.h400
-rw-r--r--engines/sci/graphics/screen_item32.cpp73
-rw-r--r--engines/sci/graphics/screen_item32.h32
-rw-r--r--engines/sci/graphics/text32.cpp130
-rw-r--r--engines/sci/graphics/text32.h43
-rw-r--r--engines/sci/graphics/view.h1
-rw-r--r--engines/sci/module.mk5
-rw-r--r--engines/sci/resource.cpp29
-rw-r--r--engines/sci/resource.h13
-rw-r--r--engines/sci/resource_audio.cpp4
-rw-r--r--engines/sci/sci.cpp43
-rw-r--r--engines/sci/sci.h8
-rw-r--r--engines/sci/sound/audio.cpp49
-rw-r--r--engines/sci/sound/audio.h12
-rw-r--r--engines/sci/sound/audio32.cpp997
-rw-r--r--engines/sci/sound/audio32.h591
-rw-r--r--engines/sci/sound/decoders/sol.cpp273
-rw-r--r--engines/sci/sound/decoders/sol.h89
-rw-r--r--engines/sci/sound/drivers/amigamac.cpp1
-rw-r--r--engines/sci/sound/music.cpp74
-rw-r--r--engines/sci/sound/music.h4
-rw-r--r--engines/sci/sound/soundcmd.cpp116
-rw-r--r--engines/sci/sound/sync.cpp76
-rw-r--r--engines/sci/sound/sync.h (renamed from engines/sci/graphics/paint.cpp)41
-rw-r--r--engines/scumm/he/cup_player_he.cpp3
-rw-r--r--engines/scumm/he/intern_he.h17
-rw-r--r--engines/scumm/he/moonbase/ai_defenseunit.cpp32
-rw-r--r--engines/scumm/he/moonbase/ai_main.cpp13
-rw-r--r--engines/scumm/he/moonbase/ai_targetacquisition.cpp22
-rw-r--r--engines/scumm/he/moonbase/ai_targetacquisition.h2
-rw-r--r--engines/scumm/he/moonbase/ai_traveller.cpp14
-rw-r--r--engines/scumm/he/moonbase/ai_tree.cpp7
-rw-r--r--engines/scumm/he/moonbase/ai_types.h2
-rw-r--r--engines/scumm/he/moonbase/distortion.cpp218
-rw-r--r--engines/scumm/he/moonbase/moonbase.h4
-rw-r--r--engines/scumm/he/moonbase/moonbase_fow.cpp32
-rw-r--r--engines/scumm/he/script_v100he.cpp85
-rw-r--r--engines/scumm/he/script_v70he.cpp40
-rw-r--r--engines/scumm/he/sound_he.cpp28
-rw-r--r--engines/scumm/he/sound_he.h6
-rw-r--r--engines/scumm/he/wiz_he.cpp20
-rw-r--r--engines/scumm/module.mk1
-rw-r--r--engines/scumm/scumm.cpp2
-rw-r--r--engines/scumm/sound.cpp12
-rw-r--r--engines/scumm/sound.h7
-rw-r--r--engines/sherlock/music.cpp1
-rw-r--r--engines/sherlock/objects.cpp4
-rw-r--r--engines/sherlock/saveload.cpp2
-rw-r--r--engines/sherlock/scalpel/scalpel_user_interface.h2
-rw-r--r--engines/sherlock/surface.h2
-rw-r--r--engines/sword1/sound.cpp31
-rw-r--r--engines/sword1/sound.h2
-rw-r--r--engines/sword25/detection.cpp2
-rw-r--r--engines/tinsel/music.cpp6
-rw-r--r--engines/tinsel/palette.cpp2
-rw-r--r--engines/tinsel/play.cpp3
-rw-r--r--engines/tinsel/saveload.cpp4
-rw-r--r--engines/tinsel/tinsel.cpp3
-rw-r--r--engines/toltecs/animation.cpp12
-rw-r--r--engines/toltecs/menu.cpp10
-rw-r--r--engines/toltecs/segmap.cpp5
-rw-r--r--engines/toltecs/segmap.h2
-rw-r--r--engines/tony/font.cpp6
-rw-r--r--engines/tony/game.cpp7
-rw-r--r--engines/tony/gfxcore.h1
-rw-r--r--engines/tony/mpal/mpalutils.cpp2
-rw-r--r--engines/tony/sound.cpp1
-rw-r--r--engines/tony/tony.cpp2
-rw-r--r--engines/toon/toon.cpp13
-rw-r--r--engines/tsage/detection.cpp2
-rw-r--r--engines/voyeur/detection.cpp2
-rw-r--r--engines/wage/design.cpp54
-rw-r--r--engines/wage/design.h15
-rw-r--r--engines/wage/detection.cpp2
-rw-r--r--engines/wintermute/detection.cpp2
217 files changed, 12139 insertions, 2805 deletions
diff --git a/engines/access/detection.cpp b/engines/access/detection.cpp
index 2cd7e50f0f..368753f117 100644
--- a/engines/access/detection.cpp
+++ b/engines/access/detection.cpp
@@ -94,7 +94,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "Access Engine (c) 1989-1994 Access Software";
+ return "Access Engine (C) 1989-1994 Access Software";
}
virtual bool hasFeature(MetaEngineFeature f) const;
diff --git a/engines/access/sound.cpp b/engines/access/sound.cpp
index 448f630501..cf52bc58c5 100644
--- a/engines/access/sound.cpp
+++ b/engines/access/sound.cpp
@@ -35,10 +35,12 @@
namespace Access {
SoundManager::SoundManager(AccessEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
+ _effectsHandle = new Audio::SoundHandle();
}
SoundManager::~SoundManager() {
clearSounds();
+ delete _effectsHandle;
}
void SoundManager::clearSounds() {
@@ -49,8 +51,8 @@ void SoundManager::clearSounds() {
_soundTable.clear();
- if (_mixer->isSoundHandleActive(_effectsHandle))
- _mixer->stopHandle(_effectsHandle);
+ if (_mixer->isSoundHandleActive(*_effectsHandle))
+ _mixer->stopHandle(*_effectsHandle);
while (_queue.size()) {
delete _queue[0]._stream;
@@ -159,8 +161,8 @@ void SoundManager::playSound(Resource *res, int priority, bool loop, int soundIn
_queue.push_back(QueuedSound(audioStream, soundIndex));
}
- if (!_mixer->isSoundHandleActive(_effectsHandle))
- _mixer->playStream(Audio::Mixer::kSFXSoundType, &_effectsHandle,
+ if (!_mixer->isSoundHandleActive(*_effectsHandle))
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, _effectsHandle,
_queue[0]._stream, -1, _mixer->kMaxChannelVolume, 0,
DisposeAfterUse::NO);
}
@@ -168,20 +170,20 @@ void SoundManager::playSound(Resource *res, int priority, bool loop, int soundIn
void SoundManager::checkSoundQueue() {
debugC(5, kDebugSound, "checkSoundQueue");
- if (_queue.empty() || _mixer->isSoundHandleActive(_effectsHandle))
+ if (_queue.empty() || _mixer->isSoundHandleActive(*_effectsHandle))
return;
delete _queue[0]._stream;
_queue.remove_at(0);
if (_queue.size() && _queue[0]._stream)
- _mixer->playStream(Audio::Mixer::kSFXSoundType, &_effectsHandle,
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, _effectsHandle,
_queue[0]._stream, -1, _mixer->kMaxChannelVolume, 0,
DisposeAfterUse::NO);
}
bool SoundManager::isSFXPlaying() {
- return _mixer->isSoundHandleActive(_effectsHandle);
+ return _mixer->isSoundHandleActive(*_effectsHandle);
}
void SoundManager::loadSounds(Common::Array<RoomInfo::SoundIdent> &sounds) {
@@ -198,7 +200,7 @@ void SoundManager::loadSounds(Common::Array<RoomInfo::SoundIdent> &sounds) {
void SoundManager::stopSound() {
debugC(3, kDebugSound, "stopSound");
- _mixer->stopHandle(_effectsHandle);
+ _mixer->stopHandle(*_effectsHandle);
}
void SoundManager::freeSounds() {
diff --git a/engines/access/sound.h b/engines/access/sound.h
index d82ee956b1..d75540dd13 100644
--- a/engines/access/sound.h
+++ b/engines/access/sound.h
@@ -24,7 +24,6 @@
#define ACCESS_SOUND_H
#include "common/scummsys.h"
-#include "audio/mixer.h"
#include "access/files.h"
#include "audio/midiplayer.h"
@@ -32,6 +31,7 @@
namespace Audio {
class AudioStream;
+class SoundHandle;
}
namespace Access {
@@ -57,7 +57,7 @@ class SoundManager {
private:
AccessEngine *_vm;
Audio::Mixer *_mixer;
- Audio::SoundHandle _effectsHandle;
+ Audio::SoundHandle *_effectsHandle;
Common::Array<QueuedSound> _queue;
void clearSounds();
diff --git a/engines/adl/POTFILES b/engines/adl/POTFILES
new file mode 100644
index 0000000000..ca485932f7
--- /dev/null
+++ b/engines/adl/POTFILES
@@ -0,0 +1 @@
+engines/adl/detection.cpp
diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp
index 1ab74c3cf6..b6af54962e 100644
--- a/engines/adl/adl.cpp
+++ b/engines/adl/adl.cpp
@@ -38,25 +38,61 @@
#include "adl/adl.h"
#include "adl/display.h"
#include "adl/detection.h"
+#include "adl/graphics.h"
+#include "adl/speaker.h"
namespace Adl {
AdlEngine::~AdlEngine() {
delete _display;
+ delete _graphics;
+ delete _speaker;
+ delete _console;
+ delete _dumpFile;
}
AdlEngine::AdlEngine(OSystem *syst, const AdlGameDescription *gd) :
Engine(syst),
+ _dumpFile(nullptr),
_display(nullptr),
- _gameDescription(gd),
+ _graphics(nullptr),
_isRestarting(false),
_isRestoring(false),
+ _skipOneCommand(false),
+ _gameDescription(gd),
_saveVerb(0),
_saveNoun(0),
_restoreVerb(0),
_restoreNoun(0),
_canSaveNow(false),
_canRestoreNow(false) {
+
+ DebugMan.addDebugChannel(kDebugChannelScript, "Script", "Trace script execution");
+}
+
+bool AdlEngine::pollEvent(Common::Event &event) const {
+ _console->onFrame();
+
+ if (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type != Common::EVENT_KEYDOWN)
+ return false;
+
+ if (event.kbd.flags & Common::KBD_CTRL) {
+ if (event.kbd.keycode == Common::KEYCODE_q) {
+ quitGame();
+ return false;
+ }
+
+ if (event.kbd.keycode == Common::KEYCODE_d) {
+ _console->attach();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
}
Common::String AdlEngine::readString(Common::ReadStream &stream, byte until) const {
@@ -82,33 +118,28 @@ Common::String AdlEngine::readStringAt(Common::SeekableReadStream &stream, uint
return readString(stream, until);
}
-void AdlEngine::printMessage(uint idx, bool wait) const {
- Common::String msg = _messages[idx - 1];
- wordWrap(msg);
- _display->printString(msg);
+void AdlEngine::openFile(Common::File &file, const Common::String &name) const {
+ if (!file.open(name))
+ error("Error opening '%s'", name.c_str());
+}
- if (wait)
- delay(14 * 166018 / 1000);
+void AdlEngine::printMessage(uint idx) {
+ printString(loadMessage(idx));
}
-void AdlEngine::delay(uint32 ms) const {
- Common::EventManager *ev = g_system->getEventManager();
+Common::String AdlEngine::getItemDescription(const Item &item) const {
+ if (item.description > 0)
+ return loadMessage(item.description);
+ else
+ return Common::String();
+}
+void AdlEngine::delay(uint32 ms) const {
uint32 start = g_system->getMillis();
- while (!g_engine->shouldQuit() && g_system->getMillis() - start < ms) {
+ while (!shouldQuit() && g_system->getMillis() - start < ms) {
Common::Event event;
- if (ev->pollEvent(event)) {
- if (event.type == Common::EVENT_KEYDOWN && (event.kbd.flags & Common::KBD_CTRL)) {
- switch(event.kbd.keycode) {
- case Common::KEYCODE_q:
- g_engine->quitGame();
- break;
- default:
- break;
- }
- }
- }
+ pollEvent(event);
g_system->delayMillis(16);
}
}
@@ -122,7 +153,7 @@ Common::String AdlEngine::inputString(byte prompt) const {
while (1) {
byte b = inputKey();
- if (g_engine->shouldQuit() || _isRestoring)
+ if (shouldQuit() || _isRestoring)
return 0;
if (b == 0)
@@ -153,25 +184,18 @@ Common::String AdlEngine::inputString(byte prompt) const {
}
}
-byte AdlEngine::inputKey() const {
- Common::EventManager *ev = g_system->getEventManager();
-
+byte AdlEngine::inputKey(bool showCursor) const {
byte key = 0;
- _display->showCursor(true);
+ if (showCursor)
+ _display->showCursor(true);
- while (!g_engine->shouldQuit() && !_isRestoring && key == 0) {
+ while (!shouldQuit() && !_isRestoring && key == 0) {
Common::Event event;
- if (ev->pollEvent(event)) {
+ if (pollEvent(event)) {
if (event.type != Common::EVENT_KEYDOWN)
continue;
- if (event.kbd.flags & Common::KBD_CTRL) {
- if (event.kbd.keycode == Common::KEYCODE_q)
- g_engine->quitGame();
- continue;
- }
-
switch (event.kbd.keycode) {
case Common::KEYCODE_BACKSPACE:
case Common::KEYCODE_RETURN:
@@ -192,9 +216,12 @@ byte AdlEngine::inputKey() const {
return key;
}
-void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const {
+void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri) const {
uint index = 0;
+ map.clear();
+ pri.clear();
+
while (1) {
++index;
@@ -208,6 +235,8 @@ void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const {
if (!map.contains(word))
map[word] = index;
+ pri.push_back(Console::toAscii(word));
+
byte synonyms = stream.readByte();
if (stream.err() || stream.eos())
@@ -229,6 +258,8 @@ void AdlEngine::loadWords(Common::ReadStream &stream, WordMap &map) const {
}
void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) {
+ commands.clear();
+
while (1) {
Command command;
command.room = stream.readByte();
@@ -264,10 +295,238 @@ void AdlEngine::readCommands(Common::ReadStream &stream, Commands &commands) {
}
}
+void AdlEngine::checkInput(byte verb, byte noun) {
+ // Try room-local command list first
+ if (doOneCommand(_roomData.commands, verb, noun))
+ return;
+
+ // If no match was found, try the global list
+ if (!doOneCommand(_roomCommands, verb, noun))
+ printMessage(_messageIds.dontUnderstand);
+}
+
+bool AdlEngine::isInputValid(byte verb, byte noun, bool &is_any) {
+ if (isInputValid(_roomData.commands, verb, noun, is_any))
+ return true;
+ return isInputValid(_roomCommands, verb, noun, is_any);
+}
+
+bool AdlEngine::isInputValid(const Commands &commands, byte verb, byte noun, bool &is_any) {
+ Commands::const_iterator cmd;
+
+ is_any = false;
+ for (cmd = commands.begin(); cmd != commands.end(); ++cmd) {
+ ScriptEnv env(*cmd, _state.room, verb, noun);
+ if (matchCommand(env)) {
+ if (cmd->verb == IDI_ANY || cmd->noun == IDI_ANY)
+ is_any = true;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine> OpcodeV1;
+#define SetOpcodeTable(x) table = &x;
+#define Opcode(x) table->push_back(new OpcodeV1(this, &AdlEngine::x))
+#define OpcodeUnImpl() table->push_back(new OpcodeV1(this, 0))
+
+void AdlEngine::setupOpcodeTables() {
+ Common::Array<const Opcode *> *table = 0;
+
+ SetOpcodeTable(_condOpcodes);
+ // 0x00
+ OpcodeUnImpl();
+ OpcodeUnImpl();
+ OpcodeUnImpl();
+ Opcode(o1_isItemInRoom);
+ // 0x04
+ OpcodeUnImpl();
+ Opcode(o1_isMovesGT);
+ Opcode(o1_isVarEQ);
+ OpcodeUnImpl();
+ // 0x08
+ OpcodeUnImpl();
+ Opcode(o1_isCurPicEQ);
+ Opcode(o1_isItemPicEQ);
+
+ SetOpcodeTable(_actOpcodes);
+ // 0x00
+ OpcodeUnImpl();
+ Opcode(o1_varAdd);
+ Opcode(o1_varSub);
+ Opcode(o1_varSet);
+ // 0x04
+ Opcode(o1_listInv);
+ Opcode(o1_moveItem);
+ Opcode(o1_setRoom);
+ Opcode(o1_setCurPic);
+ // 0x08
+ Opcode(o1_setPic);
+ Opcode(o1_printMsg);
+ Opcode(o1_setLight);
+ Opcode(o1_setDark);
+ // 0x0c
+ OpcodeUnImpl();
+ Opcode(o1_quit);
+ OpcodeUnImpl();
+ Opcode(o1_save);
+ // 0x10
+ Opcode(o1_restore);
+ Opcode(o1_restart);
+ Opcode(o1_placeItem);
+ Opcode(o1_setItemPic);
+ // 0x14
+ Opcode(o1_resetPic);
+ Opcode(o1_goDirection<IDI_DIR_NORTH>);
+ Opcode(o1_goDirection<IDI_DIR_SOUTH>);
+ Opcode(o1_goDirection<IDI_DIR_EAST>);
+ // 0x18
+ Opcode(o1_goDirection<IDI_DIR_WEST>);
+ Opcode(o1_goDirection<IDI_DIR_UP>);
+ Opcode(o1_goDirection<IDI_DIR_DOWN>);
+ Opcode(o1_takeItem);
+ // 0x1c
+ Opcode(o1_dropItem);
+ Opcode(o1_setRoomPic);
+}
+
+void AdlEngine::initState() {
+ _state = State();
+
+ initGameState();
+}
+
+byte AdlEngine::roomArg(byte room) const {
+ return room;
+}
+
+void AdlEngine::clearScreen() const {
+ _display->setMode(DISPLAY_MODE_MIXED);
+ _display->clear(0x00);
+}
+
+void AdlEngine::drawPic(byte pic, Common::Point pos) const {
+ if (_roomData.pictures.contains(pic))
+ _graphics->drawPic(*_roomData.pictures[pic]->createReadStream(), pos);
+ else
+ _graphics->drawPic(*_pictures[pic]->createReadStream(), pos);
+}
+
+void AdlEngine::bell(uint count) const {
+ _speaker->bell(count);
+}
+
+const Room &AdlEngine::getRoom(uint i) const {
+ if (i < 1 || i > _state.rooms.size())
+ error("Room %i out of range [1, %i]", i, _state.rooms.size());
+
+ return _state.rooms[i - 1];
+}
+
+Room &AdlEngine::getRoom(uint i) {
+ if (i < 1 || i > _state.rooms.size())
+ error("Room %i out of range [1, %i]", i, _state.rooms.size());
+
+ return _state.rooms[i - 1];
+}
+
+const Room &AdlEngine::getCurRoom() const {
+ return getRoom(_state.room);
+}
+
+Room &AdlEngine::getCurRoom() {
+ return getRoom(_state.room);
+}
+
+const Item &AdlEngine::getItem(uint i) const {
+ Common::List<Item>::const_iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->id == i)
+ return *item;
+
+ error("Item %i not found", i);
+}
+
+Item &AdlEngine::getItem(uint i) {
+ Common::List<Item>::iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->id == i)
+ return *item;
+
+ error("Item %i not found", i);
+}
+
+byte AdlEngine::getVar(uint i) const {
+ if (i >= _state.vars.size())
+ error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1);
+
+ return _state.vars[i];
+}
+
+void AdlEngine::setVar(uint i, byte value) {
+ if (i >= _state.vars.size())
+ error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1);
+
+ _state.vars[i] = value;
+}
+
+void AdlEngine::takeItem(byte noun) {
+ Common::List<Item>::iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ if (item->noun != noun || item->room != _state.room)
+ continue;
+
+ if (item->state == IDI_ITEM_DOESNT_MOVE) {
+ printMessage(_messageIds.itemDoesntMove);
+ return;
+ }
+
+ if (item->state == IDI_ITEM_DROPPED) {
+ item->room = IDI_ANY;
+ return;
+ }
+
+ Common::Array<byte>::const_iterator pic;
+ for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) {
+ if (*pic == getCurRoom().curPicture) {
+ item->room = IDI_ANY;
+ item->state = IDI_ITEM_DROPPED;
+ return;
+ }
+ }
+ }
+
+ printMessage(_messageIds.itemNotHere);
+}
+
+void AdlEngine::dropItem(byte noun) {
+ Common::List<Item>::iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ if (item->noun != noun || item->room != IDI_ANY)
+ continue;
+
+ item->room = _state.room;
+ item->state = IDI_ITEM_DROPPED;
+ return;
+ }
+
+ printMessage(_messageIds.dontUnderstand);
+}
+
Common::Error AdlEngine::run() {
+ _console = new Console(this);
+ _speaker = new Speaker();
_display = new Display();
- loadData();
+ setupOpcodeTables();
+
+ init();
int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot >= 0) {
@@ -278,13 +537,14 @@ Common::Error AdlEngine::run() {
} else {
runIntro();
initState();
+ _display->printAsciiString(_strings.lineFeeds);
}
_display->setMode(DISPLAY_MODE_MIXED);
- _display->printAsciiString("\r\r\r\r\r");
while (1) {
uint verb = 0, noun = 0;
+ _isRestarting = false;
// When restoring from the launcher, we don't read
// input on the first iteration. This is needed to
@@ -292,9 +552,11 @@ Common::Error AdlEngine::run() {
// restoring in-game brings us to the same game state.
// (Also see comment below.)
if (!_isRestoring) {
- clearScreen();
showRoom();
+ if (_isRestarting)
+ continue;
+
_canSaveNow = _canRestoreNow = true;
getInput(verb, noun);
_canSaveNow = _canRestoreNow = false;
@@ -305,8 +567,7 @@ Common::Error AdlEngine::run() {
// If we just restored from the GMM, we skip this command
// set, as no command has been input by the user
if (!_isRestoring)
- if (!doOneCommand(_roomCommands, verb, noun))
- printMessage(_messageIds.dontUnderstand);
+ checkInput(verb, noun);
}
if (_isRestoring) {
@@ -323,12 +584,15 @@ Common::Error AdlEngine::run() {
}
// Restarting does end command processing
- if (_isRestarting) {
- _isRestarting = false;
+ if (_isRestarting)
continue;
- }
doAllCommands(_globalCommands, verb, noun);
+
+ if (_isRestarting)
+ continue;
+
+ advanceClock();
_state.moves++;
}
@@ -382,6 +646,8 @@ Common::Error AdlEngine::loadGameState(int slot) {
_state.room = inFile->readByte();
_state.moves = inFile->readByte();
_state.isDark = inFile->readByte();
+ _state.time.hours = inFile->readByte();
+ _state.time.minutes = inFile->readByte();
uint32 size = inFile->readUint32BE();
if (size != _state.rooms.size())
@@ -390,18 +656,20 @@ Common::Error AdlEngine::loadGameState(int slot) {
for (uint i = 0; i < size; ++i) {
_state.rooms[i].picture = inFile->readByte();
_state.rooms[i].curPicture = inFile->readByte();
+ _state.rooms[i].isFirstTime = inFile->readByte();
}
size = inFile->readUint32BE();
if (size != _state.items.size())
error("Item count mismatch (expected %i; found %i)", _state.items.size(), size);
- for (uint i = 0; i < size; ++i) {
- _state.items[i].room = inFile->readByte();
- _state.items[i].picture = inFile->readByte();
- _state.items[i].position.x = inFile->readByte();
- _state.items[i].position.y = inFile->readByte();
- _state.items[i].state = inFile->readByte();
+ Common::List<Item>::iterator item;
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ item->room = inFile->readByte();
+ item->picture = inFile->readByte();
+ item->position.x = inFile->readByte();
+ item->position.y = inFile->readByte();
+ item->state = inFile->readByte();
}
size = inFile->readUint32BE();
@@ -467,20 +735,24 @@ Common::Error AdlEngine::saveGameState(int slot, const Common::String &desc) {
outFile->writeByte(_state.room);
outFile->writeByte(_state.moves);
outFile->writeByte(_state.isDark);
+ outFile->writeByte(_state.time.hours);
+ outFile->writeByte(_state.time.minutes);
outFile->writeUint32BE(_state.rooms.size());
for (uint i = 0; i < _state.rooms.size(); ++i) {
outFile->writeByte(_state.rooms[i].picture);
outFile->writeByte(_state.rooms[i].curPicture);
+ outFile->writeByte(_state.rooms[i].isFirstTime);
}
outFile->writeUint32BE(_state.items.size());
- for (uint i = 0; i < _state.items.size(); ++i) {
- outFile->writeByte(_state.items[i].room);
- outFile->writeByte(_state.items[i].picture);
- outFile->writeByte(_state.items[i].position.x);
- outFile->writeByte(_state.items[i].position.y);
- outFile->writeByte(_state.items[i].state);
+ Common::List<Item>::const_iterator item;
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ outFile->writeByte(item->room);
+ outFile->writeByte(item->picture);
+ outFile->writeByte(item->position.x);
+ outFile->writeByte(item->position.y);
+ outFile->writeByte(item->state);
}
outFile->writeUint32BE(_state.vars.size());
@@ -509,29 +781,14 @@ bool AdlEngine::canSaveGameStateCurrently() {
// "SAVE GAME". This prevents saving via the GMM in situations where
// it wouldn't otherwise be possible to do so.
for (cmd = _roomCommands.begin(); cmd != _roomCommands.end(); ++cmd) {
- uint offset;
- if (matchCommand(*cmd, _saveVerb, _saveNoun, &offset))
- return cmd->script[offset] == IDO_ACT_SAVE;
+ ScriptEnv env(*cmd, _state.room, _saveVerb, _saveNoun);
+ if (matchCommand(env))
+ return env.op() == IDO_ACT_SAVE;
}
return false;
}
-void AdlEngine::wordWrap(Common::String &str) const {
- uint end = 39;
-
- while (1) {
- if (str.size() <= end)
- return;
-
- while (str[end] != APPLECHAR(' '))
- --end;
-
- str.setChar(APPLECHAR('\r'), end);
- end += 40;
- }
-}
-
byte AdlEngine::convertKey(uint16 ascii) const {
ascii = toupper(ascii);
@@ -598,6 +855,22 @@ Common::String AdlEngine::getWord(const Common::String &line, uint &index) const
}
}
+Common::String AdlEngine::formatVerbError(const Common::String &verb) const {
+ Common::String err = _strings.verbError;
+ for (uint i = 0; i < verb.size(); ++i)
+ err.setChar(verb[i], i + 19);
+ return err;
+}
+
+Common::String AdlEngine::formatNounError(const Common::String &verb, const Common::String &noun) const {
+ Common::String err = _strings.nounError;
+ for (uint i = 0; i < verb.size(); ++i)
+ err.setChar(verb[i], i + 19);
+ for (uint i = 0; i < noun.size(); ++i)
+ err.setChar(noun[i], i + 30);
+ return err;
+}
+
void AdlEngine::getInput(uint &verb, uint &noun) {
while (1) {
_display->printString(_strings.enterCommand);
@@ -607,421 +880,349 @@ void AdlEngine::getInput(uint &verb, uint &noun) {
return;
uint index = 0;
- Common::String verbStr = getWord(line, index);
+ Common::String verbString = getWord(line, index);
- if (!_verbs.contains(verbStr)) {
- Common::String err = _strings.verbError;
- for (uint i = 0; i < verbStr.size(); ++i)
- err.setChar(verbStr[i], i + 19);
- _display->printString(err);
+ if (!_verbs.contains(verbString)) {
+ _display->printString(formatVerbError(verbString));
continue;
}
- verb = _verbs[verbStr];
+ verb = _verbs[verbString];
- Common::String nounStr = getWord(line, index);
+ Common::String nounString = getWord(line, index);
- if (!_nouns.contains(nounStr)) {
- Common::String err = _strings.nounError;
- for (uint i = 0; i < verbStr.size(); ++i)
- err.setChar(verbStr[i], i + 19);
- for (uint i = 0; i < nounStr.size(); ++i)
- err.setChar(nounStr[i], i + 30);
- _display->printString(err);
+ if (!_nouns.contains(nounString)) {
+ _display->printString(formatNounError(verbString, nounString));
continue;
}
- noun = _nouns[nounStr];
+ noun = _nouns[nounString];
return;
}
}
-void AdlEngine::showRoom() const {
- if (!_state.isDark) {
- drawPic(getCurRoom().curPicture);
- drawItems();
+bool AdlEngine::op_debug(const char *fmt, ...) const {
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) {
+ va_list va;
+ va_start(va, fmt);
+ Common::String output = Common::String::vformat(fmt, va);
+ va_end(va);
+
+ output += '\n';
+ if (_dumpFile) {
+ _dumpFile->write(output.c_str(), output.size());
+ return true;
+ } else
+ debugN("%s", output.c_str());
}
- _display->updateHiResScreen();
- printMessage(getCurRoom().description, false);
+ return false;
}
-void AdlEngine::clearScreen() const {
- _display->setMode(DISPLAY_MODE_MIXED);
- _display->clear(0x00);
+int AdlEngine::o1_isItemInRoom(ScriptEnv &e) {
+ OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
+
+ if (getItem(e.arg(1)).room == roomArg(e.arg(2)))
+ return 2;
+
+ return -1;
}
-void AdlEngine::drawItems() const {
- Common::Array<Item>::const_iterator item;
+int AdlEngine::o1_isMovesGT(ScriptEnv &e) {
+ OP_DEBUG_1("\t&& MOVES > %d", e.arg(1));
- uint dropped = 0;
+ if (_state.moves > e.arg(1))
+ return 1;
- for (item = _state.items.begin(); item != _state.items.end(); ++item) {
- if (item->room != _state.room)
- continue;
+ return -1;
+}
- if (item->state == IDI_ITEM_MOVED) {
- if (getCurRoom().picture == getCurRoom().curPicture) {
- const Common::Point &p = _itemOffsets[dropped];
- if (item->isLineArt)
- drawLineArt(_lineArt[item->picture - 1], p);
- else
- drawPic(item->picture, p);
- ++dropped;
- }
- continue;
- }
+int AdlEngine::o1_isVarEQ(ScriptEnv &e) {
+ OP_DEBUG_2("\t&& VARS[%d] == %d", e.arg(1), e.arg(2));
- Common::Array<byte>::const_iterator pic;
+ if (getVar(e.arg(1)) == e.arg(2))
+ return 2;
- for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) {
- if (*pic == getCurRoom().curPicture) {
- if (item->isLineArt)
- drawLineArt(_lineArt[item->picture - 1], item->position);
- else
- drawPic(item->picture, item->position);
- continue;
- }
- }
- }
+ return -1;
}
-void AdlEngine::drawNextPixel(Common::Point &p, byte color, byte bits, byte quadrant) const {
- if (bits & 4)
- _display->putPixel(p, color);
+int AdlEngine::o1_isCurPicEQ(ScriptEnv &e) {
+ OP_DEBUG_1("\t&& GET_CURPIC() == %d", e.arg(1));
- bits += quadrant;
+ if (getCurRoom().curPicture == e.arg(1))
+ return 1;
- if (bits & 1)
- p.x += (bits & 2 ? -1 : 1);
- else
- p.y += (bits & 2 ? 1 : -1);
+ return -1;
}
-void AdlEngine::drawLineArt(const Common::Array<byte> &lineArt, const Common::Point &pos, byte rotation, byte scaling, byte color) const {
- const byte stepping[] = {
- 0xff, 0xfe, 0xfa, 0xf4, 0xec, 0xe1, 0xd4, 0xc5,
- 0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x49, 0x31, 0x18,
- 0xff
- };
+int AdlEngine::o1_isItemPicEQ(ScriptEnv &e) {
+ OP_DEBUG_2("\t&& GET_ITEM_PIC(%s) == %d", itemStr(e.arg(1)).c_str(), e.arg(2));
- byte quadrant = rotation >> 4;
- rotation &= 0xf;
- byte xStep = stepping[rotation];
- byte yStep = stepping[(rotation ^ 0xf) + 1] + 1;
-
- Common::Point p(pos);
-
- for (uint i = 0; i < lineArt.size(); ++i) {
- byte b = lineArt[i];
-
- do {
- byte xFrac = 0x80;
- byte yFrac = 0x80;
- for (uint j = 0; j < scaling; ++j) {
- if (xFrac + xStep + 1 > 255)
- drawNextPixel(p, color, b, quadrant);
- xFrac += xStep + 1;
- if (yFrac + yStep > 255)
- drawNextPixel(p, color, b, quadrant + 1);
- yFrac += yStep;
- }
- b >>= 3;
- } while (b != 0);
- }
+ if (getItem(e.arg(1)).picture == e.arg(2))
+ return 2;
+
+ return -1;
}
-const Room &AdlEngine::getRoom(uint i) const {
- if (i < 1 || i > _state.rooms.size())
- error("Room %i out of range [1, %i]", i, _state.rooms.size());
+int AdlEngine::o1_varAdd(ScriptEnv &e) {
+ OP_DEBUG_2("\tVARS[%d] += %d", e.arg(2), e.arg(1));
- return _state.rooms[i - 1];
+ setVar(e.arg(2), getVar(e.arg(2) + e.arg(1)));
+ return 2;
}
-Room &AdlEngine::getRoom(uint i) {
- if (i < 1 || i > _state.rooms.size())
- error("Room %i out of range [1, %i]", i, _state.rooms.size());
+int AdlEngine::o1_varSub(ScriptEnv &e) {
+ OP_DEBUG_2("\tVARS[%d] -= %d", e.arg(2), e.arg(1));
- return _state.rooms[i - 1];
+ setVar(e.arg(2), getVar(e.arg(2)) - e.arg(1));
+ return 2;
}
-const Room &AdlEngine::getCurRoom() const {
- return getRoom(_state.room);
+int AdlEngine::o1_varSet(ScriptEnv &e) {
+ OP_DEBUG_2("\tVARS[%d] = %d", e.arg(1), e.arg(2));
+
+ setVar(e.arg(1), e.arg(2));
+ return 2;
}
-Room &AdlEngine::getCurRoom() {
- return getRoom(_state.room);
+int AdlEngine::o1_listInv(ScriptEnv &e) {
+ OP_DEBUG_0("\tLIST_INVENTORY()");
+
+ Common::List<Item>::const_iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->room == IDI_ANY)
+ printMessage(item->description);
+
+ return 0;
}
-const Item &AdlEngine::getItem(uint i) const {
- if (i < 1 || i > _state.items.size())
- error("Item %i out of range [1, %i]", i, _state.items.size());
+int AdlEngine::o1_moveItem(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
- return _state.items[i - 1];
+ getItem(e.arg(1)).room = e.arg(2);
+ return 2;
}
-Item &AdlEngine::getItem(uint i) {
- if (i < 1 || i > _state.items.size())
- error("Item %i out of range [1, %i]", i, _state.items.size());
+int AdlEngine::o1_setRoom(ScriptEnv &e) {
+ OP_DEBUG_1("\tROOM = %d", e.arg(1));
- return _state.items[i - 1];
+ getCurRoom().curPicture = getCurRoom().picture;
+ _state.room = e.arg(1);
+ return 1;
}
-byte AdlEngine::getVar(uint i) const {
- if (i >= _state.vars.size())
- error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1);
+int AdlEngine::o1_setCurPic(ScriptEnv &e) {
+ OP_DEBUG_1("\tSET_CURPIC(%d)", e.arg(1));
- return _state.vars[i];
+ getCurRoom().curPicture = e.arg(1);
+ return 1;
}
-void AdlEngine::setVar(uint i, byte value) {
- if (i >= _state.vars.size())
- error("Variable %i out of range [0, %i]", i, _state.vars.size() - 1);
+int AdlEngine::o1_setPic(ScriptEnv &e) {
+ OP_DEBUG_1("\tSET_PIC(%d)", e.arg(1));
- _state.vars[i] = value;
+ getCurRoom().picture = getCurRoom().curPicture = e.arg(1);
+ return 1;
}
-void AdlEngine::takeItem(byte noun) {
- Common::Array<Item>::iterator item;
+int AdlEngine::o1_printMsg(ScriptEnv &e) {
+ OP_DEBUG_1("\tPRINT(%s)", msgStr(e.arg(1)).c_str());
- for (item = _state.items.begin(); item != _state.items.end(); ++item) {
- if (item->noun != noun || item->room != _state.room)
- continue;
+ printMessage(e.arg(1));
+ return 1;
+}
- if (item->state == IDI_ITEM_DOESNT_MOVE) {
- printMessage(_messageIds.itemDoesntMove);
- return;
- }
+int AdlEngine::o1_setLight(ScriptEnv &e) {
+ OP_DEBUG_0("\tLIGHT()");
- if (item->state == IDI_ITEM_MOVED) {
- item->room = IDI_NONE;
- return;
- }
+ _state.isDark = false;
+ return 0;
+}
- Common::Array<byte>::const_iterator pic;
- for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) {
- if (*pic == getCurRoom().curPicture) {
- item->room = IDI_NONE;
- item->state = IDI_ITEM_MOVED;
- return;
- }
- }
+int AdlEngine::o1_setDark(ScriptEnv &e) {
+ OP_DEBUG_0("\tDARK()");
+
+ _state.isDark = true;
+ return 0;
+}
+
+int AdlEngine::o1_save(ScriptEnv &e) {
+ OP_DEBUG_0("\tSAVE_GAME()");
+
+ saveGameState(0, "");
+ return 0;
+}
+
+int AdlEngine::o1_restore(ScriptEnv &e) {
+ OP_DEBUG_0("\tRESTORE_GAME()");
+
+ loadGameState(0);
+ _isRestoring = false;
+ return 0;
+}
+
+int AdlEngine::o1_restart(ScriptEnv &e) {
+ OP_DEBUG_0("\tRESTART_GAME()");
+
+ _display->printString(_strings.playAgain);
+ Common::String input = inputString();
+
+ if (input.size() == 0 || input[0] != APPLECHAR('N')) {
+ _isRestarting = true;
+ _display->clear(0x00);
+ _display->updateHiResScreen();
+ _display->printString(_strings.pressReturn);
+ initState();
+ _display->printAsciiString(_strings.lineFeeds);
+ return -1;
}
- printMessage(_messageIds.itemNotHere);
+ return o1_quit(e);
}
-void AdlEngine::dropItem(byte noun) {
- Common::Array<Item>::iterator item;
+int AdlEngine::o1_quit(ScriptEnv &e) {
+ OP_DEBUG_0("\tQUIT_GAME()");
- for (item = _state.items.begin(); item != _state.items.end(); ++item) {
- if (item->noun != noun || item->room != IDI_NONE)
- continue;
+ printMessage(_messageIds.thanksForPlaying);
+ quitGame();
+ return -1;
+}
- item->room = _state.room;
- item->state = IDI_ITEM_MOVED;
- return;
+int AdlEngine::o1_placeItem(ScriptEnv &e) {
+ OP_DEBUG_4("\tPLACE_ITEM(%s, %s, (%d, %d))", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str(), e.arg(3), e.arg(4));
+
+ Item &item = getItem(e.arg(1));
+
+ item.room = roomArg(e.arg(2));
+ item.position.x = e.arg(3);
+ item.position.y = e.arg(4);
+ return 4;
+}
+
+int AdlEngine::o1_setItemPic(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_ITEM_PIC(%s, %d)", itemStr(e.arg(2)).c_str(), e.arg(1));
+
+ getItem(e.arg(2)).picture = e.arg(1);
+ return 2;
+}
+
+int AdlEngine::o1_resetPic(ScriptEnv &e) {
+ OP_DEBUG_0("\tRESET_PIC()");
+
+ getCurRoom().curPicture = getCurRoom().picture;
+ return 0;
+}
+
+template <Direction D>
+int AdlEngine::o1_goDirection(ScriptEnv &e) {
+ OP_DEBUG_0((Common::String("\tGO_") + dirStr(D) + "()").c_str());
+
+ byte room = getCurRoom().connections[D];
+
+ if (room == 0) {
+ printMessage(_messageIds.cantGoThere);
+ return -1;
}
- printMessage(_messageIds.dontUnderstand);
+ getCurRoom().curPicture = getCurRoom().picture;
+ _state.room = room;
+ return -1;
}
-#define ARG(N) (command.script[offset + (N)])
+int AdlEngine::o1_takeItem(ScriptEnv &e) {
+ OP_DEBUG_0("\tTAKE_ITEM()");
-bool AdlEngine::matchCommand(const Command &command, byte verb, byte noun, uint *actions) const {
- if (command.room != IDI_NONE && command.room != _state.room)
- return false;
+ takeItem(e.getNoun());
+ return 0;
+}
- if (command.verb != IDI_NONE && command.verb != verb)
- return false;
+int AdlEngine::o1_dropItem(ScriptEnv &e) {
+ OP_DEBUG_0("\tDROP_ITEM()");
+
+ dropItem(e.getNoun());
+ return 0;
+}
+
+int AdlEngine::o1_setRoomPic(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_ROOM_PIC(%d, %d)", e.arg(1), e.arg(2));
- if (command.noun != IDI_NONE && command.noun != noun)
+ getRoom(e.arg(1)).picture = getRoom(e.arg(1)).curPicture = e.arg(2);
+ return 2;
+}
+
+bool AdlEngine::matchCommand(ScriptEnv &env) const {
+ if (!env.isMatch() && !_dumpFile)
return false;
- uint offset = 0;
- for (uint i = 0; i < command.numCond; ++i) {
- switch (ARG(0)) {
- case IDO_CND_ITEM_IN_ROOM:
- if (getItem(ARG(1)).room != ARG(2))
- return false;
- offset += 3;
- break;
- case IDO_CND_MOVES_GE:
- if (ARG(1) > _state.moves)
- return false;
- offset += 2;
- break;
- case IDO_CND_VAR_EQ:
- if (getVar(ARG(1)) != ARG(2))
- return false;
- offset += 3;
- break;
- case IDO_CND_CUR_PIC_EQ:
- if (getCurRoom().curPicture != ARG(1))
- return false;
- offset += 2;
- break;
- case IDO_CND_ITEM_PIC_EQ:
- if (getItem(ARG(1)).picture != ARG(2))
- return false;
- offset += 3;
- break;
- default:
- error("Invalid condition opcode %02x", command.script[offset]);
- }
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript)) {
+ op_debug("IF\n\tROOM == %s", roomStr(env.getCommand().room).c_str());
+ op_debug("\t&& SAID(%s, %s)", verbStr(env.getCommand().verb).c_str(), nounStr(env.getCommand().noun).c_str());
}
- if (actions)
- *actions = offset;
+ for (uint i = 0; i < env.getCondCount(); ++i) {
+ byte op = env.op();
+
+ if (op >= _condOpcodes.size() || !_condOpcodes[op] || !_condOpcodes[op]->isValid())
+ error("Unimplemented condition opcode %02x", op);
+
+ int numArgs = (*_condOpcodes[op])(env);
+
+ if (numArgs < 0) {
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript))
+ op_debug("FAIL\n");
+ return false;
+ }
+
+ env.skip(numArgs + 1);
+ }
return true;
}
-void AdlEngine::doActions(const Command &command, byte noun, byte offset) {
- for (uint i = 0; i < command.numAct; ++i) {
- switch (ARG(0)) {
- case IDO_ACT_VAR_ADD:
- setVar(ARG(2), getVar(ARG(2) + ARG(1)));
- offset += 3;
- break;
- case IDO_ACT_VAR_SUB:
- setVar(ARG(2), getVar(ARG(2)) - ARG(1));
- offset += 3;
- break;
- case IDO_ACT_VAR_SET:
- setVar(ARG(1), ARG(2));
- offset += 3;
- break;
- case IDO_ACT_LIST_ITEMS: {
- Common::Array<Item>::const_iterator item;
+void AdlEngine::doActions(ScriptEnv &env) {
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript))
+ op_debug("THEN");
- for (item = _state.items.begin(); item != _state.items.end(); ++item)
- if (item->room == IDI_NONE)
- printMessage(item->description);
+ for (uint i = 0; i < env.getActCount(); ++i) {
+ byte op = env.op();
- ++offset;
- break;
- }
- case IDO_ACT_MOVE_ITEM:
- getItem(ARG(1)).room = ARG(2);
- offset += 3;
- break;
- case IDO_ACT_SET_ROOM:
- getCurRoom().curPicture = getCurRoom().picture;
- _state.room = ARG(1);
- offset += 2;
- break;
- case IDO_ACT_SET_CUR_PIC:
- getCurRoom().curPicture = ARG(1);
- offset += 2;
- break;
- case IDO_ACT_SET_PIC:
- getCurRoom().picture = getCurRoom().curPicture = ARG(1);
- offset += 2;
- break;
- case IDO_ACT_PRINT_MSG:
- printMessage(ARG(1));
- offset += 2;
- break;
- case IDO_ACT_SET_LIGHT:
- _state.isDark = false;
- ++offset;
- break;
- case IDO_ACT_SET_DARK:
- _state.isDark = true;
- ++offset;
- break;
- case IDO_ACT_SAVE:
- saveGameState(0, "");
- ++offset;
- break;
- case IDO_ACT_LOAD:
- loadGameState(0);
- ++offset;
- // Original engine does not jump out of the loop,
- // so we don't either.
- // We reset the restore flag, as the restore game
- // process is complete
- _isRestoring = false;
- break;
- case IDO_ACT_RESTART: {
- _display->printString(_strings.playAgain);
- Common::String input = inputString();
- if (input.size() == 0 || input[0] != APPLECHAR('N')) {
- _isRestarting = true;
- _display->clear(0x00);
- _display->updateHiResScreen();
- restartGame();
- return;
- }
- // Fall-through
- }
- case IDO_ACT_QUIT:
- printMessage(_messageIds.thanksForPlaying);
- quitGame();
- return;
- case IDO_ACT_PLACE_ITEM:
- getItem(ARG(1)).room = ARG(2);
- getItem(ARG(1)).position.x = ARG(3);
- getItem(ARG(1)).position.y = ARG(4);
- offset += 5;
- break;
- case IDO_ACT_SET_ITEM_PIC:
- getItem(ARG(2)).picture = ARG(1);
- offset += 3;
- break;
- case IDO_ACT_RESET_PIC:
- getCurRoom().curPicture = getCurRoom().picture;
- ++offset;
- break;
- case IDO_ACT_GO_NORTH:
- case IDO_ACT_GO_SOUTH:
- case IDO_ACT_GO_EAST:
- case IDO_ACT_GO_WEST:
- case IDO_ACT_GO_UP:
- case IDO_ACT_GO_DOWN: {
- byte room = getCurRoom().connections[ARG(0) - IDO_ACT_GO_NORTH];
-
- if (room == 0) {
- printMessage(_messageIds.cantGoThere);
- return;
- }
+ if (op >= _actOpcodes.size() || !_actOpcodes[op] || !_actOpcodes[op]->isValid())
+ error("Unimplemented action opcode %02x", op);
+
+ int numArgs = (*_actOpcodes[op])(env);
- getCurRoom().curPicture = getCurRoom().picture;
- _state.room = room;
+ if (numArgs < 0) {
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript))
+ op_debug("ABORT\n");
return;
}
- case IDO_ACT_TAKE_ITEM:
- takeItem(noun);
- ++offset;
- break;
- case IDO_ACT_DROP_ITEM:
- dropItem(noun);
- ++offset;
- break;
- case IDO_ACT_SET_ROOM_PIC:
- getRoom(ARG(1)).picture = getRoom(ARG(1)).curPicture = ARG(2);
- offset += 3;
- break;
- default:
- error("Invalid action opcode %02x", ARG(0));
- }
+
+ env.skip(numArgs + 1);
}
-}
-#undef ARG
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript))
+ op_debug("END\n");
+}
bool AdlEngine::doOneCommand(const Commands &commands, byte verb, byte noun) {
Commands::const_iterator cmd;
for (cmd = commands.begin(); cmd != commands.end(); ++cmd) {
- uint offset = 0;
- if (matchCommand(*cmd, verb, noun, &offset)) {
- doActions(*cmd, noun, offset);
+
+ if (_skipOneCommand) {
+ _skipOneCommand = false;
+ continue;
+ }
+
+ ScriptEnv env(*cmd, _state.room, verb, noun);
+ if (matchCommand(env)) {
+ doActions(env);
return true;
}
}
+ _skipOneCommand = false;
return false;
}
@@ -1029,10 +1230,87 @@ void AdlEngine::doAllCommands(const Commands &commands, byte verb, byte noun) {
Commands::const_iterator cmd;
for (cmd = commands.begin(); cmd != commands.end(); ++cmd) {
- uint offset = 0;
- if (matchCommand(*cmd, verb, noun, &offset))
- doActions(*cmd, noun, offset);
+ if (_skipOneCommand) {
+ _skipOneCommand = false;
+ continue;
+ }
+
+ ScriptEnv env(*cmd, _state.room, verb, noun);
+ if (matchCommand(env)) {
+ doActions(env);
+ // The original long jumps on restart, so we need to abort here
+ if (_isRestarting)
+ return;
+ }
+ }
+
+ _skipOneCommand = false;
+}
+
+Common::String AdlEngine::toAscii(const Common::String &str) {
+ Common::String ascii = Console::toAscii(str);
+ if (ascii.lastChar() == '\r')
+ ascii.deleteLastChar();
+ // FIXME: remove '\r's inside string?
+ return ascii;
+}
+
+Common::String AdlEngine::itemStr(uint i) const {
+ byte desc = getItem(i).description;
+ byte noun = getItem(i).noun;
+ Common::String name = Common::String::format("%d", i);
+ if (noun > 0) {
+ name += "/";
+ name += _priNouns[noun - 1];
+ }
+ if (desc > 0) {
+ name += "/";
+ name += toAscii(loadMessage(desc));
}
+ return name;
+}
+
+Common::String AdlEngine::itemRoomStr(uint i) const {
+ switch (i) {
+ case IDI_ANY:
+ return "CARRYING";
+ case IDI_VOID_ROOM:
+ return "GONE";
+ case IDI_CUR_ROOM:
+ return "HERE";
+ default:
+ return Common::String::format("%d", i);
+ }
+}
+
+Common::String AdlEngine::roomStr(uint i) const {
+ if (i == IDI_ANY)
+ return "*";
+ else
+ return Common::String::format("%d", i);
+}
+
+Common::String AdlEngine::verbStr(uint i) const {
+ if (i == IDI_ANY)
+ return "*";
+ else
+ return Common::String::format("%d/%s", i, _priVerbs[i - 1].c_str());
+}
+
+Common::String AdlEngine::nounStr(uint i) const {
+ if (i == IDI_ANY)
+ return "*";
+ else
+ return Common::String::format("%d/%s", i, _priNouns[i - 1].c_str());
+}
+
+Common::String AdlEngine::msgStr(uint i) const {
+ return Common::String::format("%d/%s", i, toAscii(loadMessage(i)).c_str());
+}
+
+Common::String AdlEngine::dirStr(Direction dir) const {
+ static const char *dirs[] = { "NORTH", "SOUTH", "EAST", "WEST", "UP", "DOWN" };
+ return dirs[dir];
}
} // End of namespace Adl
diff --git a/engines/adl/adl.h b/engines/adl/adl.h
index 4ea7566669..c9d77fcc62 100644
--- a/engines/adl/adl.h
+++ b/engines/adl/adl.h
@@ -23,88 +23,125 @@
#ifndef ADL_ADL_H
#define ADL_ADL_H
+#include "common/debug-channels.h"
#include "common/array.h"
#include "common/rect.h"
#include "common/str.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/func.h"
+#include "common/scummsys.h"
#include "engines/engine.h"
+#include "audio/mixer.h"
+#include "audio/softsynth/pcspk.h"
+
+#include "adl/console.h"
+#include "adl/disk.h"
+
namespace Common {
class ReadStream;
class SeekableReadStream;
+class File;
+struct Event;
}
namespace Adl {
+class Console;
class Display;
+class GraphicsMan;
+class Speaker;
struct AdlGameDescription;
+class ScriptEnv;
-// Conditional opcodes
-#define IDO_CND_ITEM_IN_ROOM 0x03
-#define IDO_CND_MOVES_GE 0x05
-#define IDO_CND_VAR_EQ 0x06
-#define IDO_CND_CUR_PIC_EQ 0x09
-#define IDO_CND_ITEM_PIC_EQ 0x0a
-
-// Action opcodes
-#define IDO_ACT_VAR_ADD 0x01
-#define IDO_ACT_VAR_SUB 0x02
-#define IDO_ACT_VAR_SET 0x03
-#define IDO_ACT_LIST_ITEMS 0x04
-#define IDO_ACT_MOVE_ITEM 0x05
-#define IDO_ACT_SET_ROOM 0x06
-#define IDO_ACT_SET_CUR_PIC 0x07
-#define IDO_ACT_SET_PIC 0x08
-#define IDO_ACT_PRINT_MSG 0x09
-#define IDO_ACT_SET_LIGHT 0x0a
-#define IDO_ACT_SET_DARK 0x0b
-#define IDO_ACT_QUIT 0x0d
+enum kDebugChannels {
+ kDebugChannelScript = 1 << 0
+};
+
+// Save and restore opcodes
#define IDO_ACT_SAVE 0x0f
#define IDO_ACT_LOAD 0x10
-#define IDO_ACT_RESTART 0x11
-#define IDO_ACT_PLACE_ITEM 0x12
-#define IDO_ACT_SET_ITEM_PIC 0x13
-#define IDO_ACT_RESET_PIC 0x14
-#define IDO_ACT_GO_NORTH 0x15
-#define IDO_ACT_GO_SOUTH 0x16
-#define IDO_ACT_GO_EAST 0x17
-#define IDO_ACT_GO_WEST 0x18
-#define IDO_ACT_GO_UP 0x19
-#define IDO_ACT_GO_DOWN 0x1a
-#define IDO_ACT_TAKE_ITEM 0x1b
-#define IDO_ACT_DROP_ITEM 0x1c
-#define IDO_ACT_SET_ROOM_PIC 0x1d
+
+#define IDI_CUR_ROOM 0xfc
+#define IDI_VOID_ROOM 0xfd
+#define IDI_ANY 0xfe
#define IDI_WORD_SIZE 8
+enum Direction {
+ IDI_DIR_NORTH,
+ IDI_DIR_SOUTH,
+ IDI_DIR_EAST,
+ IDI_DIR_WEST,
+ IDI_DIR_UP,
+ IDI_DIR_DOWN,
+ IDI_DIR_TOTAL
+};
+
struct Room {
+ Room() :
+ description(0),
+ picture(0),
+ curPicture(0) {
+ memset(connections, 0, sizeof(connections));
+ }
+
byte description;
- byte connections[6];
+ byte connections[IDI_DIR_TOTAL];
+ DataBlockPtr data;
byte picture;
byte curPicture;
+ bool isFirstTime;
};
-struct Picture {
- byte block;
- uint16 offset;
-};
+typedef Common::HashMap<byte, DataBlockPtr> PictureMap;
+
+typedef Common::Array<byte> Script;
struct Command {
byte room;
byte verb, noun;
byte numCond, numAct;
- Common::Array<byte> script;
+ Script script;
+};
+
+class ScriptEnv {
+public:
+ ScriptEnv(const Command &cmd, byte room, byte verb, byte noun) :
+ _cmd(cmd), _room(room), _verb(verb), _noun(noun), _ip(0) { }
+
+ byte op() const { return _cmd.script[_ip]; }
+ // We keep this 1-based for easier comparison with the original engine
+ byte arg(uint i) const { return _cmd.script[_ip + i]; }
+ void skip(uint i) { _ip += i; }
+
+ bool isMatch() const {
+ return (_cmd.room == IDI_ANY || _cmd.room == _room) &&
+ (_cmd.verb == IDI_ANY || _cmd.verb == _verb) &&
+ (_cmd.noun == IDI_ANY || _cmd.noun == _noun);
+ }
+
+ byte getCondCount() const { return _cmd.numCond; }
+ byte getActCount() const { return _cmd.numAct; }
+ byte getNoun() const { return _noun; }
+ const Command &getCommand() const { return _cmd; }
+
+private:
+ const Command &_cmd;
+ const byte _room, _verb, _noun;
+ byte _ip;
};
enum {
IDI_ITEM_NOT_MOVED,
- IDI_ITEM_MOVED,
+ IDI_ITEM_DROPPED,
IDI_ITEM_DOESNT_MOVE
};
-#define IDI_NONE 0xfe
-
struct Item {
+ byte id;
byte noun;
byte room;
byte picture;
@@ -113,67 +150,200 @@ struct Item {
int state;
byte description;
Common::Array<byte> roomPictures;
+ bool isOnScreen;
+};
+
+struct Time {
+ byte hours, minutes;
+
+ Time() : hours(12), minutes(0) { }
};
struct State {
Common::Array<Room> rooms;
- Common::Array<Item> items;
+ Common::List<Item> items;
Common::Array<byte> vars;
byte room;
uint16 moves;
bool isDark;
+ Time time;
- State() : room(1), moves(0), isDark(false) { }
+ State() : room(1), moves(1), isDark(false) { }
};
typedef Common::List<Command> Commands;
typedef Common::HashMap<Common::String, uint> WordMap;
+struct RoomData {
+ Common::String description;
+ PictureMap pictures;
+ Commands commands;
+};
+
+// Opcode debugging macros
+#define OP_DEBUG_0(F) do { \
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F)) \
+ return 0; \
+} while (0)
+
+#define OP_DEBUG_1(F, P1) do { \
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1)) \
+ return 1; \
+} while (0)
+
+#define OP_DEBUG_2(F, P1, P2) do { \
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2)) \
+ return 2; \
+} while (0)
+
+#define OP_DEBUG_3(F, P1, P2, P3) do { \
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2, P3)) \
+ return 3; \
+} while (0)
+
+#define OP_DEBUG_4(F, P1, P2, P3, P4) do { \
+ if (DebugMan.isDebugChannelEnabled(kDebugChannelScript) && op_debug(F, P1, P2, P3, P4)) \
+ return 4; \
+} while (0)
+
class AdlEngine : public Engine {
+friend class Console;
public:
virtual ~AdlEngine();
+ bool pollEvent(Common::Event &event) const;
+
protected:
AdlEngine(OSystem *syst, const AdlGameDescription *gd);
+ // Engine
+ Common::Error loadGameState(int slot);
+ Common::Error saveGameState(int slot, const Common::String &desc);
+
Common::String readString(Common::ReadStream &stream, byte until = 0) const;
Common::String readStringAt(Common::SeekableReadStream &stream, uint offset, byte until = 0) const;
+ void openFile(Common::File &file, const Common::String &name) const;
- virtual void printMessage(uint idx, bool wait = true) const;
+ virtual void printString(const Common::String &str) = 0;
+ virtual Common::String loadMessage(uint idx) const = 0;
+ virtual void printMessage(uint idx);
+ virtual Common::String getItemDescription(const Item &item) const;
void delay(uint32 ms) const;
Common::String inputString(byte prompt = 0) const;
- byte inputKey() const;
+ byte inputKey(bool showCursor = true) const;
- void loadWords(Common::ReadStream &stream, WordMap &map) const;
+ virtual Common::String formatVerbError(const Common::String &verb) const;
+ virtual Common::String formatNounError(const Common::String &verb, const Common::String &noun) const;
+ void loadWords(Common::ReadStream &stream, WordMap &map, Common::StringArray &pri) const;
void readCommands(Common::ReadStream &stream, Commands &commands);
+ void checkInput(byte verb, byte noun);
+ virtual bool isInputValid(byte verb, byte noun, bool &is_any);
+ virtual bool isInputValid(const Commands &commands, byte verb, byte noun, bool &is_any);
+
+ virtual void setupOpcodeTables();
+ virtual void initState();
+ virtual byte roomArg(byte room) const;
+ virtual void advanceClock() { }
+
+ // Opcodes
+ int o1_isItemInRoom(ScriptEnv &e);
+ int o1_isMovesGT(ScriptEnv &e);
+ int o1_isVarEQ(ScriptEnv &e);
+ int o1_isCurPicEQ(ScriptEnv &e);
+ int o1_isItemPicEQ(ScriptEnv &e);
+
+ int o1_varAdd(ScriptEnv &e);
+ int o1_varSub(ScriptEnv &e);
+ int o1_varSet(ScriptEnv &e);
+ int o1_listInv(ScriptEnv &e);
+ int o1_moveItem(ScriptEnv &e);
+ int o1_setRoom(ScriptEnv &e);
+ int o1_setCurPic(ScriptEnv &e);
+ int o1_setPic(ScriptEnv &e);
+ int o1_printMsg(ScriptEnv &e);
+ int o1_setLight(ScriptEnv &e);
+ int o1_setDark(ScriptEnv &e);
+ int o1_save(ScriptEnv &e);
+ int o1_restore(ScriptEnv &e);
+ int o1_restart(ScriptEnv &e);
+ int o1_quit(ScriptEnv &e);
+ int o1_placeItem(ScriptEnv &e);
+ int o1_setItemPic(ScriptEnv &e);
+ int o1_resetPic(ScriptEnv &e);
+ template <Direction D>
+ int o1_goDirection(ScriptEnv &e);
+ int o1_takeItem(ScriptEnv &e);
+ int o1_dropItem(ScriptEnv &e);
+ int o1_setRoomPic(ScriptEnv &e);
+
+ // Graphics
+ void clearScreen() const;
+ void drawPic(byte pic, Common::Point pos = Common::Point()) const;
+
+ // Sound
+ void bell(uint count = 1) const;
+
+ // Game state functions
+ const Room &getRoom(uint i) const;
+ Room &getRoom(uint i);
+ const Room &getCurRoom() const;
+ Room &getCurRoom();
+ const Item &getItem(uint i) const;
+ Item &getItem(uint i);
+ byte getVar(uint i) const;
+ void setVar(uint i, byte value);
+ virtual void takeItem(byte noun);
+ void dropItem(byte noun);
+ bool matchCommand(ScriptEnv &env) const;
+ void doActions(ScriptEnv &env);
+ bool doOneCommand(const Commands &commands, byte verb, byte noun);
+ void doAllCommands(const Commands &commands, byte verb, byte noun);
+
+ // Debug functions
+ static Common::String toAscii(const Common::String &str);
+ Common::String itemStr(uint i) const;
+ Common::String roomStr(uint i) const;
+ Common::String itemRoomStr(uint i) const;
+ Common::String verbStr(uint i) const;
+ Common::String nounStr(uint i) const;
+ Common::String msgStr(uint i) const;
+ Common::String dirStr(Direction dir) const;
+ bool op_debug(const char *fmt, ...) const;
+ Common::DumpFile *_dumpFile;
Display *_display;
+ GraphicsMan *_graphics;
+ Speaker *_speaker;
+ // Opcodes
+ typedef Common::Functor1<ScriptEnv &, int> Opcode;
+ Common::Array<const Opcode *> _condOpcodes, _actOpcodes;
// Message strings in data file
- Common::Array<Common::String> _messages;
+ Common::Array<DataBlockPtr> _messages;
// Picture data
- Common::Array<Picture> _pictures;
+ PictureMap _pictures;
// Dropped item screen offsets
Common::Array<Common::Point> _itemOffsets;
- // Drawings consisting of horizontal and vertical lines only, but
- // supporting scaling and rotation
- Common::Array<Common::Array<byte> > _lineArt;
// <room, verb, noun, script> lists
Commands _roomCommands;
Commands _globalCommands;
+ // Data related to the current room
+ RoomData _roomData;
WordMap _verbs;
WordMap _nouns;
+ Common::StringArray _priVerbs;
+ Common::StringArray _priNouns;
struct {
Common::String enterCommand;
- Common::String dontHaveIt;
- Common::String gettingDark;
Common::String verbError;
Common::String nounError;
Common::String playAgain;
+ Common::String pressReturn;
+ Common::String lineFeeds;
} _strings;
struct {
@@ -187,61 +357,37 @@ protected:
// Game state
State _state;
+ bool _isRestarting, _isRestoring;
+ bool _skipOneCommand;
+
private:
virtual void runIntro() const { }
- virtual void loadData() = 0;
- virtual void initState() = 0;
- virtual void restartGame() = 0;
- virtual void drawPic(byte pic, Common::Point pos = Common::Point()) const = 0;
+ virtual void init() = 0;
+ virtual void initGameState() = 0;
+ virtual void drawItems() = 0;
+ virtual void drawItem(Item &item, const Common::Point &pos) = 0;
+ virtual void loadRoom(byte roomNr) = 0;
+ virtual void showRoom() = 0;
// Engine
Common::Error run();
bool hasFeature(EngineFeature f) const;
- Common::Error loadGameState(int slot);
bool canLoadGameStateCurrently();
- Common::Error saveGameState(int slot, const Common::String &desc);
bool canSaveGameStateCurrently();
- // Text output
- void wordWrap(Common::String &str) const;
-
// Text input
byte convertKey(uint16 ascii) const;
Common::String getLine() const;
Common::String getWord(const Common::String &line, uint &index) const;
void getInput(uint &verb, uint &noun);
- // Graphics
- void showRoom() const;
- void clearScreen() const;
- void drawItems() const;
- void drawNextPixel(Common::Point &p, byte color, byte bits, byte quadrant) const;
- void drawLineArt(const Common::Array<byte> &lineArt, const Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const;
-
- // Game state functions
- const Room &getRoom(uint i) const;
- Room &getRoom(uint i);
- const Room &getCurRoom() const;
- Room &getCurRoom();
- const Item &getItem(uint i) const;
- Item &getItem(uint i);
- byte getVar(uint i) const;
- void setVar(uint i, byte value);
- void takeItem(byte noun);
- void dropItem(byte noun);
- bool matchCommand(const Command &command, byte verb, byte noun, uint *actions = nullptr) const;
- void doActions(const Command &command, byte noun, byte offset);
- bool doOneCommand(const Commands &commands, byte verb, byte noun);
- void doAllCommands(const Commands &commands, byte verb, byte noun);
-
+ Console *_console;
+ GUI::Debugger *getDebugger() { return _console; }
const AdlGameDescription *_gameDescription;
- bool _isRestarting, _isRestoring;
byte _saveVerb, _saveNoun, _restoreVerb, _restoreNoun;
bool _canSaveNow, _canRestoreNow;
};
-AdlEngine *HiRes1Engine__create(OSystem *syst, const AdlGameDescription *gd);
-
} // End of namespace Adl
#endif
diff --git a/engines/adl/adl_v2.cpp b/engines/adl/adl_v2.cpp
new file mode 100644
index 0000000000..4fdf796701
--- /dev/null
+++ b/engines/adl/adl_v2.cpp
@@ -0,0 +1,539 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/random.h"
+#include "common/error.h"
+
+#include "adl/adl_v2.h"
+#include "adl/display.h"
+#include "adl/graphics.h"
+
+namespace Adl {
+
+AdlEngine_v2::~AdlEngine_v2() {
+ delete _random;
+ delete _disk;
+}
+
+AdlEngine_v2::AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine(syst, gd),
+ _linesPrinted(0),
+ _disk(nullptr),
+ _itemRemoved(false),
+ _roomOnScreen(0),
+ _picOnScreen(0),
+ _itemsOnScreen(0) {
+ _random = new Common::RandomSource("adl");
+}
+
+typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v2> OpcodeV2;
+#define SetOpcodeTable(x) table = &x;
+#define Opcode(x) table->push_back(new OpcodeV2(this, &AdlEngine_v2::x))
+#define OpcodeUnImpl() table->push_back(new OpcodeV2(this, 0))
+
+void AdlEngine_v2::setupOpcodeTables() {
+ Common::Array<const Opcode *> *table = 0;
+
+ SetOpcodeTable(_condOpcodes);
+ // 0x00
+ OpcodeUnImpl();
+ Opcode(o2_isFirstTime);
+ Opcode(o2_isRandomGT);
+ Opcode(o1_isItemInRoom);
+ // 0x04
+ Opcode(o2_isNounNotInRoom);
+ Opcode(o1_isMovesGT);
+ Opcode(o1_isVarEQ);
+ Opcode(o2_isCarryingSomething);
+ // 0x08
+ OpcodeUnImpl();
+ Opcode(o1_isCurPicEQ);
+ Opcode(o1_isItemPicEQ);
+
+ SetOpcodeTable(_actOpcodes);
+ // 0x00
+ OpcodeUnImpl();
+ Opcode(o1_varAdd);
+ Opcode(o1_varSub);
+ Opcode(o1_varSet);
+ // 0x04
+ Opcode(o1_listInv);
+ Opcode(o2_moveItem);
+ Opcode(o1_setRoom);
+ Opcode(o1_setCurPic);
+ // 0x08
+ Opcode(o1_setPic);
+ Opcode(o1_printMsg);
+ Opcode(o1_setLight);
+ Opcode(o1_setDark);
+ // 0x0c
+ Opcode(o2_moveAllItems);
+ Opcode(o1_quit);
+ OpcodeUnImpl();
+ Opcode(o2_save);
+ // 0x10
+ Opcode(o2_restore);
+ Opcode(o1_restart);
+ Opcode(o2_placeItem);
+ Opcode(o1_setItemPic);
+ // 0x14
+ Opcode(o1_resetPic);
+ Opcode(o1_goDirection<IDI_DIR_NORTH>);
+ Opcode(o1_goDirection<IDI_DIR_SOUTH>);
+ Opcode(o1_goDirection<IDI_DIR_EAST>);
+ // 0x18
+ Opcode(o1_goDirection<IDI_DIR_WEST>);
+ Opcode(o1_goDirection<IDI_DIR_UP>);
+ Opcode(o1_goDirection<IDI_DIR_DOWN>);
+ Opcode(o1_takeItem);
+ // 0x1c
+ Opcode(o1_dropItem);
+ Opcode(o1_setRoomPic);
+ Opcode(o2_tellTime);
+ Opcode(o2_setRoomFromVar);
+ // 0x20
+ Opcode(o2_initDisk);
+}
+
+void AdlEngine_v2::initState() {
+ AdlEngine::initState();
+
+ _linesPrinted = 0;
+ _picOnScreen = 0;
+ _roomOnScreen = 0;
+ _itemRemoved = false;
+ _itemsOnScreen = 0;
+}
+
+byte AdlEngine_v2::roomArg(byte room) const {
+ if (room == IDI_CUR_ROOM)
+ return _state.room;
+ return room;
+}
+
+void AdlEngine_v2::advanceClock() {
+ Time &time = _state.time;
+
+ time.minutes += 5;
+
+ if (time.minutes == 60) {
+ time.minutes = 0;
+
+ ++time.hours;
+
+ if (time.hours == 13)
+ time.hours = 1;
+ }
+}
+
+void AdlEngine_v2::checkTextOverflow(char c) {
+ if (c != APPLECHAR('\r'))
+ return;
+
+ ++_linesPrinted;
+
+ if (_linesPrinted < 4)
+ return;
+
+ _linesPrinted = 0;
+ _display->updateTextScreen();
+ bell();
+
+ while (true) {
+ char key = inputKey(false);
+
+ if (shouldQuit())
+ return;
+
+ if (key == APPLECHAR('\r'))
+ break;
+
+ bell(3);
+ }
+}
+
+Common::String AdlEngine_v2::loadMessage(uint idx) const {
+ if (_messages[idx]) {
+ StreamPtr strStream(_messages[idx]->createReadStream());
+ return readString(*strStream, 0xff);
+ }
+
+ return Common::String();
+}
+
+void AdlEngine_v2::printString(const Common::String &str) {
+ Common::String s(str);
+ byte endPos = TEXT_WIDTH - 1;
+ byte pos = 0;
+
+ while (true) {
+ while (pos <= endPos && pos != s.size()) {
+ s.setChar(APPLECHAR(s[pos]), pos);
+ ++pos;
+ }
+
+ if (pos == s.size())
+ break;
+
+ while (s[pos] != APPLECHAR(' ') && s[pos] != APPLECHAR('\r'))
+ --pos;
+
+ s.setChar(APPLECHAR('\r'), pos);
+ endPos = pos + TEXT_WIDTH;
+ ++pos;
+ }
+
+ pos = 0;
+ while (pos != s.size()) {
+ checkTextOverflow(s[pos]);
+ _display->printChar(s[pos]);
+ ++pos;
+ }
+
+ checkTextOverflow(APPLECHAR('\r'));
+ _display->printChar(APPLECHAR('\r'));
+ _display->updateTextScreen();
+}
+
+void AdlEngine_v2::drawItem(Item &item, const Common::Point &pos) {
+ item.isOnScreen = true;
+ StreamPtr stream(_itemPics[item.picture - 1]->createReadStream());
+ stream->readByte(); // Skip clear opcode
+ _graphics->drawPic(*stream, pos);
+}
+
+void AdlEngine_v2::loadRoom(byte roomNr) {
+ Room &room = getRoom(roomNr);
+ StreamPtr stream(room.data->createReadStream());
+
+ uint16 descOffset = stream->readUint16LE();
+ uint16 commandOffset = stream->readUint16LE();
+
+ _roomData.pictures.clear();
+ // There's no picture count. The original engine always checks at most
+ // five pictures. We use the description offset to bound our search.
+ uint16 picCount = (descOffset - 4) / 5;
+
+ for (uint i = 0; i < picCount; ++i) {
+ byte nr = stream->readByte();
+ _roomData.pictures[nr] = readDataBlockPtr(*stream);
+ }
+
+ _roomData.description = readStringAt(*stream, descOffset, 0xff);
+
+ _roomData.commands.clear();
+ if (commandOffset != 0) {
+ stream->seek(commandOffset);
+ readCommands(*stream, _roomData.commands);
+ }
+}
+
+void AdlEngine_v2::showRoom() {
+ bool redrawPic = false;
+
+ if (_state.room != _roomOnScreen) {
+ loadRoom(_state.room);
+ clearScreen();
+
+ if (!_state.isDark)
+ redrawPic = true;
+ } else {
+ if (getCurRoom().curPicture != _picOnScreen || _itemRemoved)
+ redrawPic = true;
+ }
+
+ if (redrawPic) {
+ _roomOnScreen = _state.room;
+ _picOnScreen = getCurRoom().curPicture;
+
+ drawPic(getCurRoom().curPicture);
+ _itemRemoved = false;
+ _itemsOnScreen = 0;
+
+ Common::List<Item>::iterator item;
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ item->isOnScreen = false;
+ }
+
+ if (!_state.isDark)
+ drawItems();
+
+ _display->updateHiResScreen();
+ printString(_roomData.description);
+
+ // FIXME: move to main loop?
+ _linesPrinted = 0;
+}
+
+void AdlEngine_v2::takeItem(byte noun) {
+ Common::List<Item>::iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ if (item->noun != noun || item->room != _state.room)
+ continue;
+
+ if (item->state == IDI_ITEM_DOESNT_MOVE) {
+ printMessage(_messageIds.itemDoesntMove);
+ return;
+ }
+
+ if (item->state == IDI_ITEM_DROPPED) {
+ item->room = IDI_ANY;
+ _itemRemoved = true;
+ return;
+ }
+
+ Common::Array<byte>::const_iterator pic;
+ for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) {
+ if (*pic == getCurRoom().curPicture || *pic == IDI_ANY) {
+ item->room = IDI_ANY;
+ _itemRemoved = true;
+ item->state = IDI_ITEM_DROPPED;
+ return;
+ }
+ }
+ }
+
+ printMessage(_messageIds.itemNotHere);
+}
+
+void AdlEngine_v2::drawItems() {
+ Common::List<Item>::iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ // Skip items not in this room
+ if (item->room != _state.room)
+ continue;
+
+ if (item->isOnScreen)
+ continue;
+
+ if (item->state == IDI_ITEM_DROPPED) {
+ // Draw dropped item if in normal view
+ if (getCurRoom().picture == getCurRoom().curPicture)
+ drawItem(*item, _itemOffsets[_itemsOnScreen++]);
+ } else {
+ // Draw fixed item if current view is in the pic list
+ Common::Array<byte>::const_iterator pic;
+
+ for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) {
+ if (*pic == getCurRoom().curPicture || *pic == IDI_ANY) {
+ drawItem(*item, item->position);
+ break;
+ }
+ }
+ }
+ }
+}
+
+DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const {
+ byte track = f.readByte();
+ byte sector = f.readByte();
+ byte offset = f.readByte();
+ byte size = f.readByte();
+
+ if (f.eos() || f.err())
+ error("Error reading DataBlockPtr");
+
+ if (track == 0 && sector == 0 && offset == 0 && size == 0)
+ return DataBlockPtr();
+
+ return _disk->getDataBlock(track, sector, offset, size);
+}
+
+int AdlEngine_v2::o2_isFirstTime(ScriptEnv &e) {
+ OP_DEBUG_0("\t&& IS_FIRST_TIME()");
+
+ bool oldFlag = getCurRoom().isFirstTime;
+
+ getCurRoom().isFirstTime = false;
+
+ if (!oldFlag)
+ return -1;
+
+ return 0;
+}
+
+int AdlEngine_v2::o2_isRandomGT(ScriptEnv &e) {
+ OP_DEBUG_1("\t&& RAND() > %d", e.arg(1));
+
+ byte rnd = _random->getRandomNumber(255);
+
+ if (rnd > e.arg(1))
+ return 1;
+
+ return -1;
+}
+
+int AdlEngine_v2::o2_isNounNotInRoom(ScriptEnv &e) {
+ OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
+
+ Common::List<Item>::const_iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->noun == e.getNoun() && (item->room == roomArg(e.arg(1))))
+ return -1;
+
+ return 1;
+}
+
+int AdlEngine_v2::o2_isCarryingSomething(ScriptEnv &e) {
+ OP_DEBUG_0("\t&& IS_CARRYING_SOMETHING()");
+
+ Common::List<Item>::const_iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->room == IDI_ANY)
+ return 0;
+ return -1;
+}
+
+int AdlEngine_v2::o2_moveItem(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
+
+ byte room = roomArg(e.arg(2));
+
+ Item &item = getItem(e.arg(1));
+
+ if (item.room == _roomOnScreen)
+ _picOnScreen = 0;
+
+ // Set items that move from inventory to a room to state "dropped"
+ if (item.room == IDI_ANY && room != IDI_VOID_ROOM)
+ item.state = IDI_ITEM_DROPPED;
+
+ item.room = room;
+ return 2;
+}
+
+int AdlEngine_v2::o2_moveAllItems(ScriptEnv &e) {
+ OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
+
+ byte room1 = roomArg(e.arg(1));
+
+ if (room1 == _state.room)
+ _picOnScreen = 0;
+
+ byte room2 = roomArg(e.arg(2));
+
+ Common::List<Item>::iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->room == room1) {
+ item->room = room2;
+ if (room1 == IDI_ANY)
+ item->state = IDI_ITEM_DROPPED;
+ }
+
+ return 2;
+}
+
+int AdlEngine_v2::o2_save(ScriptEnv &e) {
+ OP_DEBUG_0("\tSAVE_GAME()");
+
+ int slot = askForSlot(_strings_v2.saveInsert);
+
+ if (slot < 0)
+ return -1;
+
+ saveGameState(slot, "");
+
+ _display->printString(_strings_v2.saveReplace);
+ inputString();
+ return 0;
+}
+
+int AdlEngine_v2::o2_restore(ScriptEnv &e) {
+ OP_DEBUG_0("\tRESTORE_GAME()");
+
+ int slot = askForSlot(_strings_v2.restoreInsert);
+
+ if (slot < 0)
+ return -1;
+
+ loadGameState(slot);
+ _isRestoring = false;
+
+ _display->printString(_strings_v2.restoreReplace);
+ inputString();
+ _picOnScreen = 0;
+ _roomOnScreen = 0;
+ return 0;
+}
+
+int AdlEngine_v2::o2_placeItem(ScriptEnv &e) {
+ OP_DEBUG_4("\tPLACE_ITEM(%s, %s, (%d, %d))", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str(), e.arg(3), e.arg(4));
+
+ Item &item = getItem(e.arg(1));
+
+ item.room = roomArg(e.arg(2));
+ item.position.x = e.arg(3);
+ item.position.y = e.arg(4);
+ item.state = IDI_ITEM_NOT_MOVED;
+
+ return 4;
+}
+
+int AdlEngine_v2::o2_tellTime(ScriptEnv &e) {
+ OP_DEBUG_0("\tTELL_TIME()");
+
+ Common::String time = _strings_v2.time;
+
+ time.setChar(APPLECHAR('0') + _state.time.hours / 10, 12);
+ time.setChar(APPLECHAR('0') + _state.time.hours % 10, 13);
+ time.setChar(APPLECHAR('0') + _state.time.minutes / 10, 15);
+ time.setChar(APPLECHAR('0') + _state.time.minutes % 10, 16);
+
+ printString(time);
+
+ return 0;
+}
+
+int AdlEngine_v2::o2_setRoomFromVar(ScriptEnv &e) {
+ OP_DEBUG_1("\tROOM = VAR[%d]", e.arg(1));
+ getCurRoom().curPicture = getCurRoom().picture;
+ _state.room = getVar(e.arg(1));
+ return 1;
+}
+
+int AdlEngine_v2::o2_initDisk(ScriptEnv &e) {
+ OP_DEBUG_0("\tINIT_DISK()");
+
+ _display->printAsciiString("NOT REQUIRED\r");
+ return 0;
+}
+
+int AdlEngine_v2::askForSlot(const Common::String &question) {
+ while (1) {
+ _display->printString(question);
+
+ Common::String input = inputString();
+
+ if (shouldQuit())
+ return -1;
+
+ if (input.size() > 0 && input[0] >= APPLECHAR('A') && input[0] <= APPLECHAR('O'))
+ return input[0] - APPLECHAR('A');
+ }
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/adl_v2.h b/engines/adl/adl_v2.h
new file mode 100644
index 0000000000..f18972b74b
--- /dev/null
+++ b/engines/adl/adl_v2.h
@@ -0,0 +1,95 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ADL_ADL_V2_H
+#define ADL_ADL_V2_H
+
+#include "adl/adl.h"
+
+// Note: this version of ADL redraws only when necessary, but
+// this is not currently implemented.
+
+namespace Common {
+class RandomSource;
+}
+
+namespace Adl {
+
+class AdlEngine_v2 : public AdlEngine {
+public:
+ virtual ~AdlEngine_v2();
+
+protected:
+ AdlEngine_v2(OSystem *syst, const AdlGameDescription *gd);
+
+ // AdlEngine
+ virtual void setupOpcodeTables();
+ virtual void initState();
+ byte roomArg(byte room) const;
+ void advanceClock();
+ virtual void printString(const Common::String &str);
+ virtual Common::String loadMessage(uint idx) const;
+ void drawItems();
+ void drawItem(Item &item, const Common::Point &pos);
+ void loadRoom(byte roomNr);
+ virtual void showRoom();
+ void takeItem(byte noun);
+
+ virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
+
+ void checkTextOverflow(char c);
+
+ int o2_isFirstTime(ScriptEnv &e);
+ int o2_isRandomGT(ScriptEnv &e);
+ int o2_isNounNotInRoom(ScriptEnv &e);
+ int o2_isCarryingSomething(ScriptEnv &e);
+
+ int o2_moveItem(ScriptEnv &e);
+ int o2_moveAllItems(ScriptEnv &e);
+ int o2_save(ScriptEnv &e);
+ int o2_restore(ScriptEnv &e);
+ int o2_placeItem(ScriptEnv &e);
+ int o2_tellTime(ScriptEnv &e);
+ int o2_setRoomFromVar(ScriptEnv &e);
+ int o2_initDisk(ScriptEnv &e);
+
+ struct {
+ Common::String time;
+ Common::String saveInsert, saveReplace;
+ Common::String restoreInsert, restoreReplace;
+ } _strings_v2;
+
+ uint _linesPrinted;
+ DiskImage *_disk;
+ Common::Array<DataBlockPtr> _itemPics;
+ bool _itemRemoved;
+ byte _roomOnScreen, _picOnScreen, _itemsOnScreen;
+
+private:
+ int askForSlot(const Common::String &question);
+
+ Common::RandomSource *_random;
+};
+
+} // End of namespace Adl
+
+#endif
diff --git a/engines/adl/adl_v3.cpp b/engines/adl/adl_v3.cpp
new file mode 100644
index 0000000000..623db661bc
--- /dev/null
+++ b/engines/adl/adl_v3.cpp
@@ -0,0 +1,259 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/random.h"
+#include "common/error.h"
+
+#include "adl/adl_v3.h"
+#include "adl/display.h"
+#include "adl/graphics.h"
+
+namespace Adl {
+
+AdlEngine_v3::AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine_v2(syst, gd),
+ _curDisk(0) {
+}
+
+Common::String AdlEngine_v3::loadMessage(uint idx) const {
+ Common::String str = AdlEngine_v2::loadMessage(idx);
+
+ for (uint i = 0; i < str.size(); ++i) {
+ const char *xorStr = "AVISDURGAN";
+ str.setChar(str[i] ^ xorStr[i % strlen(xorStr)], i);
+ }
+
+ return str;
+}
+
+Common::String AdlEngine_v3::getItemDescription(const Item &item) const {
+ return _itemDesc[item.id - 1];
+}
+
+void AdlEngine_v3::applyDiskOffset(byte &track, byte &sector) const {
+ sector += _diskOffsets[_curDisk].sector;
+ if (sector >= 16) {
+ sector -= 16;
+ ++track;
+ }
+
+ track += _diskOffsets[_curDisk].track;
+}
+
+DataBlockPtr AdlEngine_v3::readDataBlockPtr(Common::ReadStream &f) const {
+ byte track = f.readByte();
+ byte sector = f.readByte();
+ byte offset = f.readByte();
+ byte size = f.readByte();
+
+ if (f.eos() || f.err())
+ error("Error reading DataBlockPtr");
+
+ if (track == 0 && sector == 0 && offset == 0 && size == 0)
+ return DataBlockPtr();
+
+ applyDiskOffset(track, sector);
+
+ return _disk->getDataBlock(track, sector, offset, size);
+}
+
+typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v3> OpcodeV3;
+#define SetOpcodeTable(x) table = &x;
+#define Opcode(x) table->push_back(new OpcodeV3(this, &AdlEngine_v3::x))
+#define OpcodeUnImpl() table->push_back(new OpcodeV3(this, 0))
+
+void AdlEngine_v3::setupOpcodeTables() {
+ Common::Array<const Opcode *> *table = 0;
+
+ SetOpcodeTable(_condOpcodes);
+ // 0x00
+ OpcodeUnImpl();
+ Opcode(o2_isFirstTime);
+ Opcode(o2_isRandomGT);
+ Opcode(o3_isItemInRoom);
+ // 0x04
+ Opcode(o3_isNounNotInRoom);
+ Opcode(o1_isMovesGT);
+ Opcode(o1_isVarEQ);
+ Opcode(o2_isCarryingSomething);
+ // 0x08
+ Opcode(o3_isVarGT);
+ Opcode(o1_isCurPicEQ);
+ Opcode(o3_skipOneCommand);
+
+ SetOpcodeTable(_actOpcodes);
+ // 0x00
+ OpcodeUnImpl();
+ Opcode(o1_varAdd);
+ Opcode(o1_varSub);
+ Opcode(o1_varSet);
+ // 0x04
+ Opcode(o1_listInv);
+ Opcode(o3_moveItem);
+ Opcode(o1_setRoom);
+ Opcode(o1_setCurPic);
+ // 0x08
+ Opcode(o1_setPic);
+ Opcode(o1_printMsg);
+ Opcode(o3_dummy);
+ Opcode(o3_setTextMode);
+ // 0x0c
+ Opcode(o2_moveAllItems);
+ Opcode(o1_quit);
+ Opcode(o3_dummy);
+ Opcode(o2_save);
+ // 0x10
+ Opcode(o2_restore);
+ Opcode(o1_restart);
+ Opcode(o3_setDisk);
+ Opcode(o3_dummy);
+ // 0x14
+ Opcode(o1_resetPic);
+ Opcode(o1_goDirection<IDI_DIR_NORTH>);
+ Opcode(o1_goDirection<IDI_DIR_SOUTH>);
+ Opcode(o1_goDirection<IDI_DIR_EAST>);
+ // 0x18
+ Opcode(o1_goDirection<IDI_DIR_WEST>);
+ Opcode(o1_goDirection<IDI_DIR_UP>);
+ Opcode(o1_goDirection<IDI_DIR_DOWN>);
+ Opcode(o1_takeItem);
+ // 0x1c
+ Opcode(o1_dropItem);
+ Opcode(o1_setRoomPic);
+ Opcode(o3_sound);
+ OpcodeUnImpl();
+ // 0x20
+ Opcode(o2_initDisk);
+}
+
+int AdlEngine_v3::o3_isVarGT(ScriptEnv &e) {
+ OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2));
+
+ if (getVar(e.arg(1)) > e.arg(2))
+ return 2;
+
+ return -1;
+}
+
+int AdlEngine_v3::o3_skipOneCommand(ScriptEnv &e) {
+ OP_DEBUG_0("\t&& SKIP_ONE_COMMAND()");
+
+ _skipOneCommand = true;
+ setVar(2, 0);
+
+ return -1;
+}
+
+// FIXME: Rename "isLineArt" and look at code duplication
+int AdlEngine_v3::o3_isItemInRoom(ScriptEnv &e) {
+ OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
+
+ const Item &item = getItem(e.arg(1));
+
+ if (e.arg(2) != IDI_ANY && item.isLineArt != _curDisk)
+ return -1;
+
+ if (item.room == roomArg(e.arg(2)))
+ return 2;
+
+ return -1;
+}
+
+int AdlEngine_v3::o3_isNounNotInRoom(ScriptEnv &e) {
+ OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
+
+ Common::List<Item>::const_iterator item;
+
+ setVar(24, 0);
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->noun == e.getNoun()) {
+ setVar(24, 1);
+
+ if (item->room == roomArg(e.arg(1)))
+ return -1;
+ }
+
+ return 1;
+}
+
+int AdlEngine_v3::o3_moveItem(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
+
+ byte room = roomArg(e.arg(2));
+
+ Item &item = getItem(e.arg(1));
+
+ if (item.room == _roomOnScreen)
+ _picOnScreen = 0;
+
+ // Set items that move from inventory to a room to state "dropped"
+ if (item.room == IDI_ANY && room != IDI_VOID_ROOM)
+ item.state = IDI_ITEM_DROPPED;
+
+ item.room = room;
+ item.isLineArt = _curDisk;
+ return 2;
+}
+
+int AdlEngine_v3::o3_dummy(ScriptEnv &e) {
+ OP_DEBUG_0("\tDUMMY()");
+
+ return 0;
+}
+
+int AdlEngine_v3::o3_setTextMode(ScriptEnv &e) {
+ OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1));
+
+ // TODO
+ // 1: 4-line mode
+ // 2: 24-line mode
+
+ switch (e.arg(1)) {
+ case 3:
+ // We re-use the restarting flag here, to simulate a long jump
+ _isRestarting = true;
+ return -1;
+ }
+
+ return 1;
+}
+
+int AdlEngine_v3::o3_setDisk(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_DISK(%d, %d)", e.arg(1), e.arg(2));
+
+ // TODO
+ // Arg 1: disk
+ // Arg 2: room
+
+ return 2;
+}
+
+int AdlEngine_v3::o3_sound(ScriptEnv &e) {
+ OP_DEBUG_0("\tSOUND()");
+
+ // TODO
+
+ return 0;
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/adl_v3.h b/engines/adl/adl_v3.h
new file mode 100644
index 0000000000..61dd5852e7
--- /dev/null
+++ b/engines/adl/adl_v3.h
@@ -0,0 +1,76 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ADL_ADL_V3_H
+#define ADL_ADL_V3_H
+
+#include "adl/adl_v2.h"
+
+// Note: this version of ADL redraws only when necessary, but
+// this is not currently implemented.
+
+namespace Common {
+class RandomSource;
+}
+
+struct DiskOffset {
+ byte track;
+ byte sector;
+};
+
+namespace Adl {
+
+class AdlEngine_v3 : public AdlEngine_v2 {
+public:
+ virtual ~AdlEngine_v3() { }
+
+protected:
+ AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd);
+
+ // AdlEngine
+ virtual void setupOpcodeTables();
+ virtual Common::String loadMessage(uint idx) const;
+ Common::String getItemDescription(const Item &item) const;
+
+ // AdlEngine_v2
+ virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
+
+ void applyDiskOffset(byte &track, byte &sector) const;
+
+ int o3_isVarGT(ScriptEnv &e);
+ int o3_isItemInRoom(ScriptEnv &e);
+ int o3_isNounNotInRoom(ScriptEnv &e);
+ int o3_skipOneCommand(ScriptEnv &e);
+ int o3_moveItem(ScriptEnv &e);
+ int o3_dummy(ScriptEnv &e);
+ int o3_setTextMode(ScriptEnv &e);
+ int o3_setDisk(ScriptEnv &e);
+ int o3_sound(ScriptEnv &e);
+
+ Common::Array<Common::String> _itemDesc;
+ byte _curDisk;
+ Common::Array<DiskOffset> _diskOffsets;
+};
+
+} // End of namespace Adl
+
+#endif
diff --git a/engines/adl/console.cpp b/engines/adl/console.cpp
new file mode 100644
index 0000000000..c35e8b02aa
--- /dev/null
+++ b/engines/adl/console.cpp
@@ -0,0 +1,333 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/debug-channels.h"
+
+#include "adl/console.h"
+#include "adl/display.h"
+#include "adl/adl.h"
+
+namespace Adl {
+
+Console::Console(AdlEngine *engine) : GUI::Debugger() {
+ _engine = engine;
+
+ registerCmd("nouns", WRAP_METHOD(Console, Cmd_Nouns));
+ registerCmd("verbs", WRAP_METHOD(Console, Cmd_Verbs));
+ registerCmd("dump_scripts", WRAP_METHOD(Console, Cmd_DumpScripts));
+ registerCmd("valid_cmds", WRAP_METHOD(Console, Cmd_ValidCommands));
+ registerCmd("room", WRAP_METHOD(Console, Cmd_Room));
+ registerCmd("items", WRAP_METHOD(Console, Cmd_Items));
+ registerCmd("give_item", WRAP_METHOD(Console, Cmd_GiveItem));
+ registerCmd("vars", WRAP_METHOD(Console, Cmd_Vars));
+ registerCmd("var", WRAP_METHOD(Console, Cmd_Var));
+}
+
+Common::String Console::toAscii(const Common::String &str) {
+ Common::String ascii(str);
+
+ for (uint i = 0; i < ascii.size(); ++i)
+ ascii.setChar(ascii[i] & 0x7f, i);
+
+ return ascii;
+}
+
+Common::String Console::toAppleWord(const Common::String &str) {
+ Common::String apple(str);
+
+ if (apple.size() > IDI_WORD_SIZE)
+ apple.erase(IDI_WORD_SIZE);
+ apple.toUppercase();
+
+ for (uint i = 0; i < apple.size(); ++i)
+ apple.setChar(APPLECHAR(apple[i]), i);
+
+ while (apple.size() < IDI_WORD_SIZE)
+ apple += APPLECHAR(' ');
+
+ return apple;
+}
+
+bool Console::Cmd_Verbs(int argc, const char **argv) {
+ if (argc != 1) {
+ debugPrintf("Usage: %s\n", argv[0]);
+ return true;
+ }
+
+ debugPrintf("Verbs in alphabetical order:\n");
+ printWordMap(_engine->_verbs);
+ return true;
+}
+
+bool Console::Cmd_Nouns(int argc, const char **argv) {
+ if (argc != 1) {
+ debugPrintf("Usage: %s\n", argv[0]);
+ return true;
+ }
+
+ debugPrintf("Nouns in alphabetical order:\n");
+ printWordMap(_engine->_nouns);
+ return true;
+}
+
+bool Console::Cmd_ValidCommands(int argc, const char **argv) {
+ if (argc != 1) {
+ debugPrintf("Usage: %s\n", argv[0]);
+ return true;
+ }
+
+ WordMap::const_iterator verb, noun;
+ bool is_any;
+
+ for (verb = _engine->_verbs.begin(); verb != _engine->_verbs.end(); ++verb) {
+ for (noun = _engine->_nouns.begin(); noun != _engine->_nouns.end(); ++noun) {
+ if (_engine->isInputValid(verb->_value, noun->_value, is_any) && !is_any)
+ debugPrintf("%s %s\n", toAscii(verb->_key).c_str(), toAscii(noun->_key).c_str());
+ }
+ if (_engine->isInputValid(verb->_value, IDI_ANY, is_any))
+ debugPrintf("%s *\n", toAscii(verb->_key).c_str());
+ }
+ if (_engine->isInputValid(IDI_ANY, IDI_ANY, is_any))
+ debugPrintf("* *\n");
+
+ return true;
+}
+
+bool Console::Cmd_DumpScripts(int argc, const char **argv) {
+ if (argc != 1) {
+ debugPrintf("Usage: %s\n", argv[0]);
+ return true;
+ }
+
+ bool oldFlag = DebugMan.isDebugChannelEnabled(kDebugChannelScript);
+
+ DebugMan.enableDebugChannel("Script");
+
+ _engine->_dumpFile = new Common::DumpFile();
+
+ for (byte roomNr = 1; roomNr <= _engine->_state.rooms.size(); ++roomNr) {
+ _engine->loadRoom(roomNr);
+ if (_engine->_roomData.commands.size() != 0) {
+ _engine->_dumpFile->open(Common::String::format("%03d.ADL", roomNr).c_str());
+ _engine->doAllCommands(_engine->_roomData.commands, IDI_ANY, IDI_ANY);
+ _engine->_dumpFile->close();
+ }
+ }
+ _engine->loadRoom(_engine->_state.room);
+
+ _engine->_dumpFile->open("GLOBAL.ADL");
+ _engine->doAllCommands(_engine->_globalCommands, IDI_ANY, IDI_ANY);
+ _engine->_dumpFile->close();
+
+ _engine->_dumpFile->open("RESPONSE.ADL");
+ _engine->doAllCommands(_engine->_roomCommands, IDI_ANY, IDI_ANY);
+ _engine->_dumpFile->close();
+
+ delete _engine->_dumpFile;
+ _engine->_dumpFile = nullptr;
+
+ if (!oldFlag)
+ DebugMan.disableDebugChannel("Script");
+
+ return true;
+}
+
+bool Console::Cmd_Room(int argc, const char **argv) {
+ if (argc > 2) {
+ debugPrintf("Usage: %s [<new_room>]\n", argv[0]);
+ return true;
+ }
+
+ if (argc == 2) {
+ if (!_engine->_canRestoreNow) {
+ debugPrintf("Cannot change rooms right now\n");
+ return true;
+ }
+
+ uint roomCount = _engine->_state.rooms.size();
+ uint room = strtoul(argv[1], NULL, 0);
+ if (room < 1 || room > roomCount) {
+ debugPrintf("Room %u out of valid range [1, %u]\n", room, roomCount);
+ return true;
+ }
+
+ _engine->_state.room = room;
+ _engine->clearScreen();
+ _engine->loadRoom(_engine->_state.room);
+ _engine->showRoom();
+ _engine->_display->updateTextScreen();
+ _engine->_display->updateHiResScreen();
+ }
+
+ debugPrintf("Current room: %u\n", _engine->_state.room);
+
+ return true;
+}
+
+bool Console::Cmd_Items(int argc, const char **argv) {
+ if (argc != 1) {
+ debugPrintf("Usage: %s\n", argv[0]);
+ return true;
+ }
+
+ Common::List<Item>::const_iterator item;
+
+ for (item = _engine->_state.items.begin(); item != _engine->_state.items.end(); ++item)
+ printItem(*item);
+
+ return true;
+}
+
+bool Console::Cmd_GiveItem(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Usage: %s <ID | name>\n", argv[0]);
+ return true;
+ }
+
+ Common::List<Item>::iterator item;
+
+ char *end;
+ uint id = strtoul(argv[1], &end, 0);
+
+ if (*end != 0) {
+ Common::Array<Item *> matches;
+
+ Common::String name = toAppleWord(argv[1]);
+
+ if (!_engine->_nouns.contains(name)) {
+ debugPrintf("Item '%s' not found\n", argv[1]);
+ return true;
+ }
+
+ byte noun = _engine->_nouns[name];
+
+ for (item = _engine->_state.items.begin(); item != _engine->_state.items.end(); ++item) {
+ if (item->noun == noun)
+ matches.push_back(&*item);
+ }
+
+ if (matches.size() == 0) {
+ debugPrintf("Item '%s' not found\n", argv[1]);
+ return true;
+ }
+
+ if (matches.size() > 1) {
+ debugPrintf("Multiple matches found, please use item ID:\n");
+ for (uint i = 0; i < matches.size(); ++i)
+ printItem(*matches[i]);
+ return true;
+ }
+
+ matches[0]->room = IDI_ANY;
+ debugPrintf("OK\n");
+ return true;
+ }
+
+ for (item = _engine->_state.items.begin(); item != _engine->_state.items.end(); ++item)
+ if (item->id == id) {
+ item->room = IDI_ANY;
+ debugPrintf("OK\n");
+ return true;
+ }
+
+ debugPrintf("Item %i not found\n", id);
+ return true;
+}
+
+bool Console::Cmd_Vars(int argc, const char **argv) {
+ if (argc != 1) {
+ debugPrintf("Usage: %s\n", argv[0]);
+ return true;
+ }
+
+ Common::StringArray vars;
+ for (uint i = 0; i < _engine->_state.vars.size(); ++i)
+ vars.push_back(Common::String::format("%3d: %3d", i, _engine->_state.vars[i]));
+
+ debugPrintf("Variables:\n");
+ debugPrintColumns(vars);
+
+ return true;
+}
+
+bool Console::Cmd_Var(int argc, const char **argv) {
+ if (argc < 2 || argc > 3) {
+ debugPrintf("Usage: %s <index> [<value>]\n", argv[0]);
+ return true;
+ }
+
+ uint varCount = _engine->_state.vars.size();
+ uint var = strtoul(argv[1], NULL, 0);
+
+ if (var >= varCount) {
+ debugPrintf("Variable %u out of valid range [0, %u]\n", var, varCount - 1);
+ return true;
+ }
+
+ if (argc == 3) {
+ uint value = strtoul(argv[2], NULL, 0);
+ _engine->_state.vars[var] = value;
+ }
+
+ debugPrintf("%3d: %3d\n", var, _engine->_state.vars[var]);
+
+ return true;
+}
+
+void Console::printItem(const Item &item) {
+ Common::String name, desc, state;
+
+ if (item.noun > 0)
+ name = _engine->_priNouns[item.noun - 1];
+
+ desc = toAscii(_engine->getItemDescription(item));
+ if (desc.lastChar() == '\r')
+ desc.deleteLastChar();
+
+ switch (item.state) {
+ case IDI_ITEM_NOT_MOVED:
+ state = "PLACED";
+ break;
+ case IDI_ITEM_DROPPED:
+ state = "DROPPED";
+ break;
+ case IDI_ITEM_DOESNT_MOVE:
+ state = "FIXED";
+ break;
+ }
+
+ debugPrintf("%3d %s %-30s %-10s %-8s (%3d, %3d)\n", item.id, name.c_str(), desc.c_str(), _engine->itemRoomStr(item.room).c_str(), state.c_str(), item.position.x, item.position.y);
+}
+
+void Console::printWordMap(const WordMap &wordMap) {
+ Common::StringArray words;
+ WordMap::const_iterator verb;
+
+ for (verb = wordMap.begin(); verb != wordMap.end(); ++verb)
+ words.push_back(Common::String::format("%s: %3d", toAscii(verb->_key).c_str(), wordMap[verb->_key]));
+
+ Common::sort(words.begin(), words.end());
+
+ debugPrintColumns(words);
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/console.h b/engines/adl/console.h
new file mode 100644
index 0000000000..a8c6adc1cc
--- /dev/null
+++ b/engines/adl/console.h
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ADL_CONSOLE_H
+#define ADL_CONSOLE_H
+
+#include "gui/debugger.h"
+
+#include "common/hashmap.h"
+
+namespace Common {
+class String;
+}
+
+namespace Adl {
+
+class AdlEngine;
+struct Item;
+
+class Console : public GUI::Debugger {
+public:
+ Console(AdlEngine *engine);
+
+ static Common::String toAscii(const Common::String &str);
+ static Common::String toAppleWord(const Common::String &str);
+
+private:
+ bool Cmd_Nouns(int argc, const char **argv);
+ bool Cmd_Verbs(int argc, const char **argv);
+ bool Cmd_DumpScripts(int argc, const char **argv);
+ bool Cmd_ValidCommands(int argc, const char **argv);
+ bool Cmd_Room(int argc, const char **argv);
+ bool Cmd_Items(int argc, const char **argv);
+ bool Cmd_GiveItem(int argc, const char **argv);
+ bool Cmd_Vars(int argc, const char **argv);
+ bool Cmd_Var(int argc, const char **argv);
+
+ void printItem(const Item &item);
+ void printWordMap(const Common::HashMap<Common::String, uint> &wordMap);
+
+ AdlEngine *_engine;
+};
+
+} // End of namespace Adl
+
+#endif
diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp
index 1a8c5025e8..12405e7c5e 100644
--- a/engines/adl/detection.cpp
+++ b/engines/adl/detection.cpp
@@ -32,8 +32,9 @@
namespace Adl {
-#define GAMEOPTION_COLOR GUIO_GAMEOPTIONS1
-#define GAMEOPTION_SCANLINES GUIO_GAMEOPTIONS2
+#define GAMEOPTION_COLOR GUIO_GAMEOPTIONS1
+#define GAMEOPTION_SCANLINES GUIO_GAMEOPTIONS2
+#define GAMEOPTION_MONO GUIO_GAMEOPTIONS3
static const ADExtraGuiOptionsMap optionsList[] = {
{
@@ -47,6 +48,16 @@ static const ADExtraGuiOptionsMap optionsList[] = {
},
{
+ GAMEOPTION_MONO,
+ {
+ _s("Color mode"),
+ _s("Use color graphics"),
+ "color",
+ true
+ }
+ },
+
+ {
GAMEOPTION_SCANLINES,
{
_s("Scanlines"),
@@ -60,27 +71,89 @@ static const ADExtraGuiOptionsMap optionsList[] = {
};
static const PlainGameDescriptor adlGames[] = {
- {"hires1", "Hi-Res Adventure #1: Mystery House"},
- {0, 0}
+ { "hires0", "Hi-Res Adventure #0: Mission Asteroid" },
+ { "hires1", "Hi-Res Adventure #1: Mystery House" },
+ { "hires2", "Hi-Res Adventure #2: Wizard and the Princess" },
+ { "hires6", "Hi-Res Adventure #6: The Dark Crystal" },
+ { 0, 0 }
};
static const AdlGameDescription gameDescriptions[] = {
- { // Hi-Res Adventure #1: Mystery House - Apple II - 1987 PD release
+ { // Hi-Res Adventure #1: Mystery House - Apple II - 1987 PD release - Plain files
{
"hires1", 0,
{
- {"ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952},
- {"AUTO LOAD OBJ", 0, "23bfccfe9fcff9b22cf6c41bde9078ac", 12291},
- {"MYSTERY.HELLO", 0, "2289b7fea300b506e902a4c597968369", 836},
+ { "ADVENTURE", 0, "22d9e63a11d69fa033ba1738715ad09a", 29952 },
+ { "AUTO LOAD OBJ", 0, "23bfccfe9fcff9b22cf6c41bde9078ac", 12291 },
+ { "MYSTERY.HELLO", 0, "2289b7fea300b506e902a4c597968369", 836 },
AD_LISTEND
},
Common::EN_ANY,
Common::kPlatformApple2GS, // FIXME
- ADGF_NO_FLAGS,
+ ADGF_UNSTABLE,
GUIO2(GAMEOPTION_COLOR, GAMEOPTION_SCANLINES)
},
GAME_TYPE_HIRES1
},
+ { // Hi-Res Adventure #1: Mystery House - Apple II - 1987 PD release - .DSK format
+ {
+ "hires1", 0,
+ {
+ { "MYSTHOUS.DSK", 0, "34ba05e62bf51404c4475c349ca48921", 143360 },
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformApple2GS, // FIXME
+ ADGF_UNSTABLE,
+ GUIO2(GAMEOPTION_COLOR, GAMEOPTION_SCANLINES)
+ },
+ GAME_TYPE_HIRES1
+ },
+ { // Hi-Res Adventure #2: Wizard and the Princess - Apple II - Roberta Williams Anthology
+ {
+ "hires2", 0,
+ {
+ { "WIZARD.DSK", 0, "816fdfc35e25496213c8db40ecf26569", 143360 },
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformApple2GS, // FIXME
+ ADGF_UNSTABLE,
+ GUIO2(GAMEOPTION_MONO, GAMEOPTION_SCANLINES)
+ },
+ GAME_TYPE_HIRES2
+ },
+ { // Hi-Res Adventure #0: Mission Asteroid - Apple II - Roberta Williams Anthology
+ {
+ "hires0", 0,
+ {
+ { "MISSION.NIB", 0, "b158f6f79681d4edd651e1932f9e01d7", 232960 },
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformApple2GS, // FIXME
+ ADGF_UNSTABLE,
+ GUIO2(GAMEOPTION_MONO, GAMEOPTION_SCANLINES)
+ },
+ GAME_TYPE_HIRES0
+ },
+ { // Hi-Res Adventure #6: The Dark Crystal - Apple II - Roberta Williams Anthology
+ {
+ "hires6", 0,
+ {
+ { "DARK1A.DSK", 0, "00c2646d6943d1405717332a6f42d493", 143360 },
+ { "DARK2A.NIB", 0, "271eb92db107e8d5829437f8ba77991e", 232960 },
+ { "DARK1B.NIB", 0, "dbedd736617343ade0e6bead8bf2b10c", 232960 },
+ { "DARK2B.NIB", 0, "cb72044a9b391c4285f4752f746bea2e", 232960 },
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformApple2GS, // FIXME
+ ADGF_UNSTABLE,
+ GUIO2(GAMEOPTION_MONO, GAMEOPTION_SCANLINES)
+ },
+ GAME_TYPE_HIRES6
+ },
{ AD_TABLE_END_MARKER, GAME_TYPE_NONE }
};
@@ -220,6 +293,8 @@ void AdlMetaEngine::removeSaveState(const char *target, int slot) const {
}
Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd);
+Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd);
+Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd);
bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const {
if (!gd)
@@ -231,6 +306,12 @@ bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameD
case GAME_TYPE_HIRES1:
*engine = HiRes1Engine_create(syst, adlGd);
break;
+ case GAME_TYPE_HIRES2:
+ *engine = HiRes2Engine_create(syst, adlGd);
+ break;
+ case GAME_TYPE_HIRES6:
+ *engine = HiRes6Engine_create(syst, adlGd);
+ break;
default:
error("Unknown GameType");
}
diff --git a/engines/adl/detection.h b/engines/adl/detection.h
index c646aeb5b9..533466c094 100644
--- a/engines/adl/detection.h
+++ b/engines/adl/detection.h
@@ -32,7 +32,10 @@ namespace Adl {
enum GameType {
GAME_TYPE_NONE,
- GAME_TYPE_HIRES1
+ GAME_TYPE_HIRES0,
+ GAME_TYPE_HIRES1,
+ GAME_TYPE_HIRES2,
+ GAME_TYPE_HIRES6
};
struct AdlGameDescription {
diff --git a/engines/adl/disk.cpp b/engines/adl/disk.cpp
new file mode 100644
index 0000000000..214f76aeae
--- /dev/null
+++ b/engines/adl/disk.cpp
@@ -0,0 +1,460 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/substream.h"
+#include "common/memstream.h"
+
+#include "adl/disk.h"
+
+namespace Adl {
+
+const DataBlockPtr DiskImage_DSK::getDataBlock(uint track, uint sector, uint offset, uint size) const {
+ return Common::SharedPtr<DiskImage::DataBlock>(new DiskImage::DataBlock(this, track, sector, offset, size));
+}
+
+Common::SeekableReadStream *DiskImage_DSK::createReadStream(uint track, uint sector, uint offset, uint size) const {
+ _f->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
+ Common::SeekableReadStream *stream = _f->readStream(size * _bytesPerSector + _bytesPerSector - offset);
+
+ if (_f->eos() || _f->err())
+ error("Error reading disk image");
+
+ return stream;
+}
+
+bool DiskImage_DSK::open(const Common::String &filename) {
+ assert(!_f->isOpen());
+
+ if (!_f->open(filename))
+ return false;
+
+ uint filesize = _f->size();
+ switch (filesize) {
+ case 143360:
+ _tracks = 35;
+ _sectorsPerTrack = 16;
+ _bytesPerSector = 256;
+ break;
+ default:
+ warning("Unrecognized disk image '%s' of size %d bytes", filename.c_str(), filesize);
+ return false;
+ }
+
+ return true;
+}
+
+const DataBlockPtr DiskImage_NIB::getDataBlock(uint track, uint sector, uint offset, uint size) const {
+ return Common::SharedPtr<DiskImage::DataBlock>(new DiskImage::DataBlock(this, track, sector, offset, size));
+}
+
+Common::SeekableReadStream *DiskImage_NIB::createReadStream(uint track, uint sector, uint offset, uint size) const {
+ _memStream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
+ Common::SeekableReadStream *stream = _memStream->readStream(size * _bytesPerSector + _bytesPerSector - offset);
+
+ if (_memStream->eos() || _memStream->err())
+ error("Error reading NIB image");
+
+ return stream;
+}
+
+// 4-and-4 encoding (odd-even)
+static uint8 read44(Common::SeekableReadStream *f) {
+ // 1s in the other fields, so we can just AND
+ uint8 ret = f->readByte();
+ return ((ret << 1) | 1) & f->readByte();
+}
+
+bool DiskImage_NIB::open(const Common::String &filename) {
+ assert(!_f->isOpen());
+
+ if (!_f->open(filename))
+ return false;
+
+ uint filesize = _f->size();
+ switch (filesize) {
+ case 232960:
+ _tracks = 35;
+ _sectorsPerTrack = 16; // we always pad it out
+ _bytesPerSector = 256;
+ break;
+ default:
+ error("Unrecognized NIB image '%s' of size %d bytes", filename.c_str(), filesize);
+ }
+
+ // starting at 0xaa, 32 is invalid (see below)
+ const byte c_5and3_lookup[] = { 32, 0, 32, 1, 2, 3, 32, 32, 32, 32, 32, 4, 5, 6, 32, 32, 7, 8, 32, 9, 10, 11, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 12, 13, 32, 32, 14, 15, 32, 16, 17, 18, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 19, 20, 32, 21, 22, 23, 32, 32, 32, 32, 32, 24, 25, 26, 32, 32, 27, 28, 32, 29, 30, 31 };
+ // starting at 0x96, 64 is invalid (see below)
+ const byte c_6and2_lookup[] = { 0, 1, 64, 64, 2, 3, 64, 4, 5, 6, 64, 64, 64, 64, 64, 64, 7, 8, 64, 64, 64, 9, 10, 11, 12, 13, 64, 64, 14, 15, 16, 17, 18, 19, 64, 20, 21, 22, 23, 24, 25, 26, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 27, 64, 28, 29, 30, 64, 64, 64, 31, 64, 64, 32, 33, 64, 34, 35, 36, 37, 38, 39, 40, 64, 64, 64, 64, 64, 41, 42, 43, 64, 44, 45, 46, 47, 48, 49, 50, 64, 64, 51, 52, 53, 54, 55, 56, 64, 57, 58, 59, 60, 61, 62, 63 };
+
+ uint32 diskSize = _tracks * _sectorsPerTrack * _bytesPerSector;
+ byte *diskImage = (byte *)calloc(diskSize, 1);
+ _memStream = new Common::MemoryReadStream(diskImage, diskSize, DisposeAfterUse::YES);
+
+ bool sawAddress = false;
+ uint8 volNo, track, sector;
+ bool newStyle;
+
+ while (_f->pos() < _f->size()) {
+ // Read until we find two sync bytes.
+ if (_f->readByte() != 0xd5 || _f->readByte() != 0xaa)
+ continue;
+
+ byte prologue = _f->readByte();
+
+ if (sawAddress && (prologue == 0xb5 || prologue == 0x96)) {
+ warning("NIB: data for %02x/%02x/%02x missing", volNo, track, sector);
+ sawAddress = false;
+ }
+
+ if (!sawAddress) {
+ sawAddress = true;
+ newStyle = false;
+
+ // We should always find the address field first.
+ if (prologue != 0xb5) {
+ // Accept a DOS 3.3(?) header at the start.
+ if (prologue == 0x96) {
+ newStyle = true;
+ } else {
+ error("unknown NIB field prologue %02x", prologue);
+ }
+ }
+
+ volNo = read44(_f);
+ track = read44(_f);
+ sector = read44(_f);
+ uint8 checksum = read44(_f);
+ if ((volNo ^ track ^ sector) != checksum)
+ error("invalid NIB checksum");
+
+ // FIXME: This is a hires0/hires2-specific hack.
+ if (volNo == 0xfe) {
+ if (track == 1)
+ track = 2;
+ else if (track == 2)
+ track = 1;
+ }
+
+ // Epilogue is de/aa plus a gap, but we don't care.
+ continue;
+ }
+
+ sawAddress = false;
+
+ // We should always find the data field after an address field.
+ // TODO: we ignore volNo?
+ byte *output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector;
+
+ if (newStyle) {
+ // We hardcode the DOS 3.3 mapping here. TODO: Do we also need raw/prodos?
+ int raw2dos[16] = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 };
+ sector = raw2dos[sector];
+ output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector;
+
+ // 6-and-2 uses 342 on-disk bytes
+ byte inbuffer[342];
+ _f->read(inbuffer, 342);
+
+ byte oldVal = 0;
+ for (uint n = 0; n < 342; ++n) {
+ // expand
+ assert(inbuffer[n] >= 0x96); // corrupt file (TODO: assert?)
+ byte val = c_6and2_lookup[inbuffer[n] - 0x96];
+ if (val == 0x40) {
+ error("NIB: invalid nibble value %02x", inbuffer[n]);
+ }
+ // undo checksum
+ oldVal = val ^ oldVal;
+ inbuffer[n] = oldVal;
+ }
+
+ byte checksum = _f->readByte();
+ if (checksum < 0x96 || oldVal != c_6and2_lookup[checksum - 0x96])
+ warning("NIB: checksum mismatch @ (%x, %x)", track, sector);
+
+ for (uint n = 0; n < 256; ++n) {
+ output[n] = inbuffer[86 + n] << 2;
+ if (n < 86) { // use first pair of bits
+ output[n] |= ((inbuffer[n] & 1) << 1);
+ output[n] |= ((inbuffer[n] & 2) >> 1);
+ } else if (n < 86*2) { // second pair
+ output[n] |= ((inbuffer[n-86] & 4) >> 1);
+ output[n] |= ((inbuffer[n-86] & 8) >> 3);
+ } else { // third pair
+ output[n] |= ((inbuffer[n-86*2] & 0x10) >> 3);
+ output[n] |= ((inbuffer[n-86*2] & 0x20) >> 5);
+ }
+ }
+ } else {
+ // 5-and-3 uses 410 on-disk bytes, decoding to just over 256 bytes
+ byte inbuffer[410];
+ _f->read(inbuffer, 410);
+
+ bool truncated = false;
+ byte oldVal = 0;
+ for (uint n = 0; n < 410; ++n) {
+ // expand
+ assert(inbuffer[n] >= 0xaa); // corrupt file (TODO: assert?)
+ if (inbuffer[n] == 0xd5) {
+ // Early end of block.
+ truncated = true;
+ _f->seek(-(410 - n), SEEK_CUR);
+ warning("NIB: early end of block @ 0x%x (%x, %x)", _f->pos(), track, sector);
+ break;
+ }
+ byte val = c_5and3_lookup[inbuffer[n] - 0xaa];
+ if (val == 0x20) {
+ // Badly-encoded nibbles, stop trying to decode here.
+ truncated = true;
+ warning("NIB: bad nibble %02x @ 0x%x (%x, %x)", inbuffer[n], _f->pos(), track, sector);
+ _f->seek(-(410 - n), SEEK_CUR);
+ break;
+ }
+ // undo checksum
+ oldVal = val ^ oldVal;
+ inbuffer[n] = oldVal;
+ }
+ if (!truncated) {
+ byte checksum = _f->readByte();
+ if (checksum < 0xaa || oldVal != c_5and3_lookup[checksum - 0xaa])
+ warning("NIB: checksum mismatch @ (%x, %x)", track, sector);
+ }
+
+ // 8 bytes of nibbles expand to 5 bytes
+ // so we have 51 of these batches (255 bytes), plus 2 bytes of 'leftover' nibbles for byte 256
+ for (uint n = 0; n < 51; ++n) {
+ // e.g. figure 3.18 of Beneath Apple DOS
+ byte lowbits1 = inbuffer[51*3 - n];
+ byte lowbits2 = inbuffer[51*2 - n];
+ byte lowbits3 = inbuffer[51*1 - n];
+ byte lowbits4 = (lowbits1 & 2) << 1 | (lowbits2 & 2) | (lowbits3 & 2) >> 1;
+ byte lowbits5 = (lowbits1 & 1) << 2 | (lowbits2 & 1) << 1 | (lowbits3 & 1);
+ output[250 - 5*n] = (inbuffer[n + 51*3 + 1] << 3) | ((lowbits1 >> 2) & 0x7);
+ output[251 - 5*n] = (inbuffer[n + 51*4 + 1] << 3) | ((lowbits2 >> 2) & 0x7);
+ output[252 - 5*n] = (inbuffer[n + 51*5 + 1] << 3) | ((lowbits3 >> 2) & 0x7);
+ output[253 - 5*n] = (inbuffer[n + 51*6 + 1] << 3) | lowbits4;
+ output[254 - 5*n] = (inbuffer[n + 51*7 + 1] << 3) | lowbits5;
+ }
+ output[255] = (inbuffer[409] << 3) | (inbuffer[0] & 0x7);
+ }
+ }
+
+ return true;
+}
+
+const DataBlockPtr Files_Plain::getDataBlock(const Common::String &filename, uint offset) const {
+ return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
+}
+
+Common::SeekableReadStream *Files_Plain::createReadStream(const Common::String &filename, uint offset) const {
+ Common::File *f(new Common::File());
+
+ if (!f->open(filename))
+ error("Failed to open '%s'", filename.c_str());
+
+ if (offset == 0)
+ return f;
+ else
+ return new Common::SeekableSubReadStream(f, offset, f->size(), DisposeAfterUse::YES);
+}
+
+Files_DOS33::~Files_DOS33() {
+ delete _disk;
+}
+
+Files_DOS33::Files_DOS33() :
+ _disk(nullptr) {
+}
+
+void Files_DOS33::readSectorList(TrackSector start, Common::Array<TrackSector> &list) {
+ TrackSector index = start;
+
+ while (index.track != 0) {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(index.track, index.sector));
+
+ stream->readByte();
+ index.track = stream->readByte();
+ index.sector = stream->readByte();
+
+ stream->seek(9, SEEK_CUR);
+
+ // This only handles sequential files
+ TrackSector ts;
+ ts.track = stream->readByte();
+ ts.sector = stream->readByte();
+
+ while (ts.track != 0) {
+ list.push_back(ts);
+
+ ts.track = stream->readByte();
+ ts.sector = stream->readByte();
+
+ if (stream->err())
+ error("Error reading sector list");
+
+ if (stream->eos())
+ break;
+ }
+ }
+}
+
+void Files_DOS33::readVTOC() {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(0x11, 0x00));
+ stream->readByte();
+ byte track = stream->readByte();
+ byte sector = stream->readByte();
+
+ while (track != 0) {
+ char name[kFilenameLen + 1] = { };
+
+ stream.reset(_disk->createReadStream(track, sector));
+ stream->readByte();
+ track = stream->readByte();
+ sector = stream->readByte();
+ stream->seek(8, SEEK_CUR);
+
+ for (uint i = 0; i < 7; ++i) {
+ TOCEntry entry;
+ TrackSector sectorList;
+ sectorList.track = stream->readByte();
+ sectorList.sector = stream->readByte();
+ entry.type = stream->readByte();
+ stream->read(name, kFilenameLen);
+
+ // Convert to ASCII
+ for (uint j = 0; j < kFilenameLen; j++)
+ name[j] &= 0x7f;
+
+ // Strip trailing spaces
+ for (int j = kFilenameLen - 1; j >= 0; --j) {
+ if (name[j] == ' ')
+ name[j] = 0;
+ else
+ break;
+ }
+
+ entry.totalSectors = stream->readUint16BE();
+
+ if (sectorList.track != 0) {
+ readSectorList(sectorList, entry.sectors);
+ _toc[name] = entry;
+ }
+ }
+ }
+}
+
+const DataBlockPtr Files_DOS33::getDataBlock(const Common::String &filename, uint offset) const {
+ return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
+}
+
+Common::SeekableReadStream *Files_DOS33::createReadStreamText(const TOCEntry &entry) const {
+ byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize);
+ byte *p = buf;
+
+ for (uint i = 0; i < entry.sectors.size(); ++i) {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[i].track, entry.sectors[i].sector));
+
+ assert(stream->size() == kSectorSize);
+
+ while (true) {
+ byte textChar = stream->readByte();
+
+ if (stream->eos() || textChar == 0)
+ break;
+
+ if (stream->err())
+ error("Error reading text file");
+
+ *p++ = textChar;
+ }
+ }
+
+ return new Common::MemoryReadStream(buf, p - buf, DisposeAfterUse::YES);
+}
+
+Common::SeekableReadStream *Files_DOS33::createReadStreamBinary(const TOCEntry &entry) const {
+ byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize);
+
+ Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[0].track, entry.sectors[0].sector));
+
+ if (entry.type == kFileTypeBinary)
+ stream->readUint16LE(); // Skip start address
+
+ uint16 size = stream->readUint16LE();
+ uint16 offset = 0;
+ uint16 sectorIdx = 1;
+
+ while (true) {
+ offset += stream->read(buf + offset, size - offset);
+
+ if (offset == size)
+ break;
+
+ if (stream->err())
+ error("Error reading binary file");
+
+ assert(stream->eos());
+
+ if (sectorIdx == entry.sectors.size())
+ error("Not enough sectors for binary file size");
+
+ stream.reset(_disk->createReadStream(entry.sectors[sectorIdx].track, entry.sectors[sectorIdx].sector));
+ ++sectorIdx;
+ }
+
+ return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
+}
+
+Common::SeekableReadStream *Files_DOS33::createReadStream(const Common::String &filename, uint offset) const {
+ if (!_toc.contains(filename))
+ error("Failed to locate '%s'", filename.c_str());
+
+ const TOCEntry &entry = _toc[filename];
+
+ Common::SeekableReadStream *stream;
+
+ switch(entry.type) {
+ case kFileTypeText:
+ stream = createReadStreamText(entry);
+ break;
+ case kFileTypeAppleSoft:
+ case kFileTypeBinary:
+ stream = createReadStreamBinary(entry);
+ break;
+ default:
+ error("Unsupported file type %i", entry.type);
+ }
+
+ return new Common::SeekableSubReadStream(stream, offset, stream->size(), DisposeAfterUse::YES);
+}
+
+bool Files_DOS33::open(const Common::String &filename) {
+ _disk = new DiskImage_DSK();
+ if (!_disk->open(filename))
+ return false;
+
+ readVTOC();
+ return true;
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/disk.h b/engines/adl/disk.h
new file mode 100644
index 0000000000..43b9e387ba
--- /dev/null
+++ b/engines/adl/disk.h
@@ -0,0 +1,188 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/ptr.h"
+#include "common/file.h"
+#include "common/debug.h"
+
+#ifndef ADL_DISK_H
+#define ADL_DISK_H
+
+namespace Common {
+class SeekableReadStream;
+class String;
+}
+
+namespace Adl {
+
+class DataBlock {
+public:
+ virtual ~DataBlock() { }
+
+ virtual Common::SeekableReadStream *createReadStream() const = 0;
+};
+
+typedef Common::SharedPtr<DataBlock> DataBlockPtr;
+typedef Common::ScopedPtr<Common::SeekableReadStream> StreamPtr;
+
+class Files {
+public:
+ virtual ~Files() { }
+
+ virtual const DataBlockPtr getDataBlock(const Common::String &filename, uint offset = 0) const = 0;
+ virtual Common::SeekableReadStream *createReadStream(const Common::String &filename, uint offset = 0) const = 0;
+
+protected:
+ class DataBlock : public Adl::DataBlock {
+ public:
+ DataBlock(const Files *files, const Common::String &filename, uint offset) :
+ _files(files),
+ _filename(filename),
+ _offset(offset) { }
+
+ Common::SeekableReadStream *createReadStream() const {
+ return _files->createReadStream(_filename, _offset);
+ }
+
+ private:
+ const Common::String _filename;
+ uint _offset;
+ const Files *_files;
+ };
+};
+
+class DiskImage {
+public:
+ DiskImage() :
+ _tracks(0),
+ _sectorsPerTrack(0),
+ _bytesPerSector(0) {
+ _f = new Common::File();
+ }
+
+ virtual ~DiskImage() {
+ delete _f;
+ }
+
+ virtual bool open(const Common::String &filename) = 0;
+ virtual const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const = 0;
+ virtual Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const = 0;
+
+protected:
+ class DataBlock : public Adl::DataBlock {
+ public:
+ DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size) :
+ _track(track),
+ _sector(sector),
+ _offset(offset),
+ _size(size),
+ _disk(disk) { }
+
+ Common::SeekableReadStream *createReadStream() const {
+ return _disk->createReadStream(_track, _sector, _offset, _size);
+ }
+
+ private:
+ uint _track, _sector, _offset, _size;
+ const DiskImage *_disk;
+ };
+
+ Common::File *_f;
+ uint _tracks, _sectorsPerTrack, _bytesPerSector;
+};
+
+// Data in plain files
+class Files_Plain : public Files {
+public:
+ const DataBlockPtr getDataBlock(const Common::String &filename, uint offset = 0) const;
+ Common::SeekableReadStream *createReadStream(const Common::String &filename, uint offset = 0) const;
+};
+
+// .DSK disk image - 35 tracks, 16 sectors per track, 256 bytes per sector
+class DiskImage_DSK : public DiskImage {
+public:
+ bool open(const Common::String &filename);
+ const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
+ Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const;
+};
+
+// .NIB disk image
+class DiskImage_NIB : public DiskImage {
+public:
+ DiskImage_NIB() : _memStream(nullptr) { }
+ virtual ~DiskImage_NIB() {
+ delete _memStream;
+ }
+
+ bool open(const Common::String &filename);
+ const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
+ Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const;
+
+private:
+ Common::SeekableReadStream *_memStream;
+};
+
+// Data in files contained in Apple DOS 3.3 disk image
+class Files_DOS33 : public Files {
+public:
+ Files_DOS33();
+ ~Files_DOS33();
+
+ bool open(const Common::String &filename);
+ const DataBlockPtr getDataBlock(const Common::String &filename, uint offset = 0) const;
+ Common::SeekableReadStream *createReadStream(const Common::String &filename, uint offset = 0) const;
+
+private:
+ enum FileType {
+ kFileTypeText = 0,
+ kFileTypeAppleSoft = 2,
+ kFileTypeBinary = 4
+ };
+
+ enum {
+ kSectorSize = 256,
+ kFilenameLen = 30
+ };
+
+ struct TrackSector {
+ byte track;
+ byte sector;
+ };
+
+ struct TOCEntry {
+ byte type;
+ uint16 totalSectors;
+ Common::Array<TrackSector> sectors;
+ };
+
+ void readVTOC();
+ void readSectorList(TrackSector start, Common::Array<TrackSector> &list);
+ Common::SeekableReadStream *createReadStreamText(const TOCEntry &entry) const;
+ Common::SeekableReadStream *createReadStreamBinary(const TOCEntry &entry) const;
+
+ DiskImage *_disk;
+ Common::HashMap<Common::String, TOCEntry> _toc;
+};
+
+} // End of namespace Adl
+
+#endif
diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp
index 6342504bc3..b7f6eb9923 100644
--- a/engines/adl/display.cpp
+++ b/engines/adl/display.cpp
@@ -41,8 +41,6 @@ namespace Adl {
#define DISPLAY_PITCH (DISPLAY_WIDTH / 7)
#define DISPLAY_SIZE (DISPLAY_PITCH * DISPLAY_HEIGHT)
-#define TEXT_WIDTH 40
-#define TEXT_HEIGHT 24
#define TEXT_BUF_SIZE (TEXT_WIDTH * TEXT_HEIGHT)
#define COLOR_PALETTE_ENTRIES 8
@@ -221,17 +219,35 @@ void Display::loadFrameBuffer(Common::ReadStream &stream) {
void Display::putPixel(const Common::Point &p, byte color) {
byte offset = p.x / 7;
+ byte mask = 0x80 | (1 << (p.x % 7));
+ // Since white and black are in both palettes, we leave
+ // the palette bit alone
+ if ((color & 0x7f) == 0x7f || (color & 0x7f) == 0)
+ mask &= 0x7f;
+
+ // Adjust colors starting with bits '01' or '10' for
+ // odd offsets
if (offset & 1) {
byte c = color << 1;
if (c >= 0x40 && c < 0xc0)
color ^= 0x7f;
}
- byte *b = _frameBuf + p.y * DISPLAY_PITCH + offset;
- color ^= *b;
- color &= 1 << (p.x % 7);
- *b ^= color;
+ writeFrameBuffer(p, color, mask);
+}
+
+void Display::setPixelBit(const Common::Point &p, byte color) {
+ writeFrameBuffer(p, color, 1 << (p.x % 7));
+}
+
+void Display::setPixelPalette(const Common::Point &p, byte color) {
+ writeFrameBuffer(p, color, 0x80);
+}
+
+bool Display::getPixelBit(const Common::Point &p) const {
+ byte *b = _frameBuf + p.y * DISPLAY_PITCH + p.x / 7;
+ return *b & (1 << (p.x % 7));
}
void Display::clear(byte color) {
@@ -271,21 +287,23 @@ void Display::moveCursorTo(const Common::Point &pos) {
error("Cursor position (%i, %i) out of bounds", pos.x, pos.y);
}
+// FIXME: This does not currently update the surfaces
+void Display::printChar(char c) {
+ if (c == APPLECHAR('\r'))
+ _cursorPos = (_cursorPos / TEXT_WIDTH + 1) * TEXT_WIDTH;
+ else if ((byte)c < 0x80 || (byte)c >= 0xa0) {
+ setCharAtCursor(c);
+ ++_cursorPos;
+ }
+
+ if (_cursorPos == TEXT_BUF_SIZE)
+ scrollUp();
+}
+
void Display::printString(const Common::String &str) {
Common::String::const_iterator c;
- for (c = str.begin(); c != str.end(); ++c) {
- byte b = *c;
-
- if (*c == APPLECHAR('\r'))
- _cursorPos = (_cursorPos / TEXT_WIDTH + 1) * TEXT_WIDTH;
- else if (b < 0x80 || b >= 0xa0) {
- setCharAtCursor(b);
- ++_cursorPos;
- }
-
- if (_cursorPos == TEXT_BUF_SIZE)
- scrollUp();
- }
+ for (c = str.begin(); c != str.end(); ++c)
+ printChar(*c);
updateTextScreen();
}
@@ -308,6 +326,13 @@ void Display::showCursor(bool enable) {
_showCursor = enable;
}
+void Display::writeFrameBuffer(const Common::Point &p, byte color, byte mask) {
+ byte *b = _frameBuf + p.y * DISPLAY_PITCH + p.x / 7;
+ color ^= *b;
+ color &= mask;
+ *b ^= color;
+}
+
void Display::showScanlines(bool enable) {
byte pal[COLOR_PALETTE_ENTRIES * 3] = { };
diff --git a/engines/adl/display.h b/engines/adl/display.h
index ff01e3faf2..bc27b7cb6b 100644
--- a/engines/adl/display.h
+++ b/engines/adl/display.h
@@ -40,6 +40,8 @@ namespace Adl {
#define DISPLAY_WIDTH 280
#define DISPLAY_HEIGHT 192
+#define TEXT_WIDTH 40
+#define TEXT_HEIGHT 24
enum DisplayMode {
DISPLAY_MODE_HIRES,
@@ -62,6 +64,9 @@ public:
// Graphics
void loadFrameBuffer(Common::ReadStream &stream);
void putPixel(const Common::Point &p, byte color);
+ void setPixelBit(const Common::Point &p, byte color);
+ void setPixelPalette(const Common::Point &p, byte color);
+ bool getPixelBit(const Common::Point &p) const;
void clear(byte color);
// Text
@@ -69,12 +74,14 @@ public:
void moveCursorTo(const Common::Point &pos);
void moveCursorForward();
void moveCursorBackward();
+ void printChar(char c);
void printString(const Common::String &str);
void printAsciiString(const Common::String &str);
void setCharAtCursor(byte c);
void showCursor(bool enable);
private:
+ void writeFrameBuffer(const Common::Point &p, byte color, byte mask);
void updateHiResSurface();
void showScanlines(bool enable);
diff --git a/engines/adl/graphics.cpp b/engines/adl/graphics.cpp
new file mode 100644
index 0000000000..f9af442a9f
--- /dev/null
+++ b/engines/adl/graphics.cpp
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/rect.h"
+
+#include "adl/display.h"
+#include "adl/graphics.h"
+
+namespace Adl {
+
+// Draws a four-connected line
+void GraphicsMan::drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const {
+ int16 deltaX = p2.x - p1.x;
+ int8 xStep = 1;
+
+ if (deltaX < 0) {
+ deltaX = -deltaX;
+ xStep = -1;
+ }
+
+ int16 deltaY = p2.y - p1.y;
+ int8 yStep = -1;
+
+ if (deltaY > 0) {
+ deltaY = -deltaY;
+ yStep = 1;
+ }
+
+ Common::Point p(p1);
+ int16 steps = deltaX - deltaY + 1;
+ int16 err = deltaX + deltaY;
+
+ while (true) {
+ _display.putPixel(p, color);
+
+ if (--steps == 0)
+ return;
+
+ if (err < 0) {
+ p.y += yStep;
+ err += deltaX;
+ } else {
+ p.x += xStep;
+ err += deltaY;
+ }
+ }
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/graphics.h b/engines/adl/graphics.h
new file mode 100644
index 0000000000..aab807696c
--- /dev/null
+++ b/engines/adl/graphics.h
@@ -0,0 +1,76 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ADL_PICTURE_H
+#define ADL_PICTURE_H
+
+namespace Common {
+class SeekableReadStream;
+struct Point;
+}
+
+namespace Adl {
+
+class Display;
+
+class GraphicsMan {
+public:
+ virtual ~GraphicsMan() { }
+ virtual void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) = 0;
+
+protected:
+ GraphicsMan(Display &display) : _display(display) { }
+ void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const;
+
+ Display &_display;
+};
+
+class Graphics_v1 : public GraphicsMan {
+public:
+ Graphics_v1(Display &display) : GraphicsMan(display) { }
+ void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos);
+ void drawCorners(Common::ReadStream &corners, const Common::Point &pos, byte rotation = 0, byte scaling = 1, byte color = 0x7f) const;
+
+private:
+ void drawCornerPixel(Common::Point &p, byte color, byte bits, byte quadrant) const;
+};
+
+class Graphics_v2 : public GraphicsMan {
+public:
+ Graphics_v2(Display &display) : GraphicsMan(display), _color(0) { }
+ void drawPic(Common::SeekableReadStream &pic, const Common::Point &pos);
+
+private:
+ void clear();
+ void drawCorners(Common::SeekableReadStream &pic, bool yFirst);
+ void drawRelativeLines(Common::SeekableReadStream &pic);
+ void drawAbsoluteLines(Common::SeekableReadStream &pic);
+ void fillRow(const Common::Point &p, bool fillBit, byte pattern);
+ void fill(Common::SeekableReadStream &pic);
+
+ byte _color;
+ Common::Point _offset;
+};
+
+} // End of namespace Adl
+
+#endif
diff --git a/engines/adl/graphics_v1.cpp b/engines/adl/graphics_v1.cpp
new file mode 100644
index 0000000000..29c5ef47af
--- /dev/null
+++ b/engines/adl/graphics_v1.cpp
@@ -0,0 +1,120 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/rect.h"
+#include "common/textconsole.h"
+
+#include "adl/display.h"
+#include "adl/graphics.h"
+
+namespace Adl {
+
+void Graphics_v1::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) {
+ byte x, y;
+ bool bNewLine = false;
+ byte oldX = 0, oldY = 0;
+ while (1) {
+ x = pic.readByte();
+ y = pic.readByte();
+
+ if (pic.err() || pic.eos())
+ error("Error reading picture");
+
+ if (x == 0xff && y == 0xff)
+ return;
+
+ if (x == 0 && y == 0) {
+ bNewLine = true;
+ continue;
+ }
+
+ x += pos.x;
+ y += pos.y;
+
+ if (y > 160)
+ y = 160;
+
+ if (bNewLine) {
+ _display.putPixel(Common::Point(x, y), 0x7f);
+ bNewLine = false;
+ } else {
+ drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f);
+ }
+
+ oldX = x;
+ oldY = y;
+ }
+}
+
+void Graphics_v1::drawCornerPixel(Common::Point &p, byte color, byte bits, byte quadrant) const {
+ if (bits & 4)
+ _display.putPixel(p, color);
+
+ bits += quadrant;
+
+ if (bits & 1)
+ p.x += (bits & 2 ? -1 : 1);
+ else
+ p.y += (bits & 2 ? 1 : -1);
+}
+
+void Graphics_v1::drawCorners(Common::ReadStream &corners, const Common::Point &pos, byte rotation, byte scaling, byte color) const {
+ const byte stepping[] = {
+ 0xff, 0xfe, 0xfa, 0xf4, 0xec, 0xe1, 0xd4, 0xc5,
+ 0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x49, 0x31, 0x18,
+ 0xff
+ };
+
+ byte quadrant = rotation >> 4;
+ rotation &= 0xf;
+ byte xStep = stepping[rotation];
+ byte yStep = stepping[(rotation ^ 0xf) + 1] + 1;
+
+ Common::Point p(pos);
+
+ while (true) {
+ byte b = corners.readByte();
+
+ if (corners.eos() || corners.err())
+ error("Error reading corners");
+
+ if (b == 0)
+ return;
+
+ do {
+ byte xFrac = 0x80;
+ byte yFrac = 0x80;
+ for (uint j = 0; j < scaling; ++j) {
+ if (xFrac + xStep + 1 > 255)
+ drawCornerPixel(p, color, b, quadrant);
+ xFrac += xStep + 1;
+ if (yFrac + yStep > 255)
+ drawCornerPixel(p, color, b, quadrant + 1);
+ yFrac += yStep;
+ }
+ b >>= 3;
+ } while (b != 0);
+ }
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/graphics_v2.cpp b/engines/adl/graphics_v2.cpp
new file mode 100644
index 0000000000..40936f5b7d
--- /dev/null
+++ b/engines/adl/graphics_v2.cpp
@@ -0,0 +1,304 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/rect.h"
+#include "common/textconsole.h"
+
+#include "adl/display.h"
+#include "adl/graphics.h"
+#include "adl/adl.h"
+
+namespace Adl {
+
+// FIXME: Add clipping
+
+#define NUM_PATTERNS 22
+#define PATTERN_LEN 4
+static const byte fillPatterns[NUM_PATTERNS][PATTERN_LEN] = {
+ { 0x00, 0x00, 0x00, 0x00 },
+ { 0x80, 0x80, 0x80, 0x80 },
+ { 0xff, 0xff, 0xff, 0xff },
+ { 0x7f, 0x7f, 0x7f, 0x7f },
+ { 0x2a, 0x55, 0x2a, 0x55 },
+ { 0xaa, 0xd5, 0xaa, 0xd5 },
+ { 0x55, 0x2a, 0x55, 0x2a },
+ { 0xd5, 0xaa, 0xd5, 0xaa },
+ { 0x33, 0x66, 0x4c, 0x19 },
+ { 0xb3, 0xe6, 0xcc, 0x99 },
+ { 0x22, 0x44, 0x08, 0x11 },
+ { 0xa2, 0xc4, 0x88, 0x91 },
+ { 0x11, 0x22, 0x44, 0x08 },
+ { 0x91, 0xa2, 0xc4, 0x88 },
+ { 0x6e, 0x5d, 0x3b, 0x77 },
+ { 0xee, 0xdd, 0xbb, 0xf7 },
+ { 0x5d, 0x3b, 0x77, 0x6e },
+ { 0xdd, 0xbb, 0xf7, 0xee },
+ { 0x66, 0x4c, 0x19, 0x33 },
+ { 0xe6, 0xcc, 0x99, 0xb3 },
+ { 0x33, 0x66, 0x4c, 0x19 },
+ { 0xb3, 0xe6, 0xcc, 0x99 }
+};
+
+#define MIN_COMMAND 0xe0
+
+#define CHECK_COMMAND(X) \
+ do { \
+ if ((X) >= MIN_COMMAND) { \
+ pic.seek(-1, SEEK_CUR); \
+ return; \
+ } \
+ } while (0)
+
+#define READ_BYTE(b) \
+ do { \
+ b = pic.readByte(); \
+ if (pic.eos() || pic.err()) \
+ error("Error reading picture"); \
+ CHECK_COMMAND(b); \
+ } while (0)
+
+#define READ_POINT(p) \
+ do { \
+ READ_BYTE(p.x); \
+ p.x += _offset.x; \
+ p.x <<= 1; \
+ READ_BYTE(p.y); \
+ p.y += _offset.y; \
+ } while (0)
+
+void Graphics_v2::clear() {
+ _display.clear(0xff);
+ _color = 0;
+}
+
+void Graphics_v2::drawCorners(Common::SeekableReadStream &pic, bool yFirst) {
+ Common::Point p;
+
+ READ_POINT(p);
+
+ if (yFirst)
+ goto doYStep;
+
+ while (true) {
+ int16 n;
+
+ READ_BYTE(n);
+ n += _offset.x;
+
+ _display.putPixel(p, _color);
+
+ n <<= 1;
+ drawLine(p, Common::Point(n, p.y), _color);
+ p.x = n;
+
+doYStep:
+ READ_BYTE(n);
+ n += _offset.y;
+
+ _display.putPixel(p, _color);
+ drawLine(p, Common::Point(p.x, n), _color);
+
+ _display.putPixel(Common::Point(p.x + 1, p.y), _color);
+ drawLine(Common::Point(p.x + 1, p.y), Common::Point(p.x + 1, n), _color);
+
+ p.y = n;
+ }
+}
+
+void Graphics_v2::drawRelativeLines(Common::SeekableReadStream &pic) {
+ Common::Point p1;
+
+ READ_POINT(p1);
+ _display.putPixel(p1, _color);
+
+ while (true) {
+ Common::Point p2(p1);
+
+ byte n;
+ READ_BYTE(n);
+
+ byte h = (n & 0x70) >> 4;
+ byte l = n & 7;
+
+ if (n & 0x80)
+ p2.x -= (h << 1);
+ else
+ p2.x += (h << 1);
+
+ if (n & 8)
+ p2.y -= l;
+ else
+ p2.y += l;
+
+ drawLine(p1, p2, _color);
+ p1 = p2;
+ }
+}
+
+void Graphics_v2::drawAbsoluteLines(Common::SeekableReadStream &pic) {
+ Common::Point p1;
+
+ READ_POINT(p1);
+ _display.putPixel(p1, _color);
+
+ while (true) {
+ Common::Point p2;
+
+ READ_POINT(p2);
+ drawLine(p1, p2, _color);
+ p1 = p2;
+ }
+}
+
+static byte getPatternColor(const Common::Point &p, byte pattern) {
+ if (pattern >= NUM_PATTERNS)
+ error("Invalid fill pattern %i encountered in picture", pattern);
+
+ byte offset = (p.y & 1) << 1;
+ offset += (p.x / 7) & 3;
+
+ return fillPatterns[pattern][offset % PATTERN_LEN];
+}
+
+void Graphics_v2::fillRow(const Common::Point &p, bool stopBit, byte pattern) {
+ const byte color = getPatternColor(p, pattern);
+ _display.setPixelPalette(p, color);
+ _display.setPixelBit(p, color);
+
+ Common::Point q(p);
+ byte c = color;
+
+ while (++q.x < DISPLAY_WIDTH) {
+ if ((q.x % 7) == 0) {
+ c = getPatternColor(q, pattern);
+ // Palette is set before the first bit is tested
+ _display.setPixelPalette(q, c);
+ }
+ if (_display.getPixelBit(q) == stopBit)
+ break;
+ _display.setPixelBit(q, c);
+ }
+
+ q = p;
+ c = color;
+ while (--q.x >= 0) {
+ if ((q.x % 7) == 6) {
+ c = getPatternColor(q, pattern);
+ _display.setPixelPalette(q, c);
+ }
+ if (_display.getPixelBit(q) == stopBit)
+ break;
+ _display.setPixelBit(q, c);
+ }
+}
+
+// Basic flood fill
+void Graphics_v2::fill(Common::SeekableReadStream &pic) {
+ byte pattern;
+ READ_BYTE(pattern);
+
+ while (true) {
+ Common::Point p;
+ READ_POINT(p);
+
+ bool stopBit = !_display.getPixelBit(p);
+
+ while (--p.y >= 0) {
+ if (_display.getPixelBit(p) == stopBit)
+ break;
+ if (_display.getPixelBit(Common::Point(p.x + 1, p.y)) == stopBit)
+ break;
+ }
+
+ while (++p.y < DISPLAY_HEIGHT) {
+ if (_display.getPixelBit(p) == stopBit)
+ break;
+ if (_display.getPixelBit(Common::Point(p.x + 1, p.y)) == stopBit)
+ break;
+ fillRow(p, stopBit, pattern);
+ }
+ }
+}
+
+void Graphics_v2::drawPic(Common::SeekableReadStream &pic, const Common::Point &pos) {
+ _color = 0;
+ _offset = pos;
+
+ while (true) {
+ byte opcode = pic.readByte();
+
+ if (pic.eos() || pic.err())
+ error("Error reading picture");
+
+ switch (opcode) {
+ case 0xe0:
+ drawCorners(pic, false);
+ break;
+ case 0xe1:
+ drawCorners(pic, true);
+ break;
+ case 0xe2:
+ drawRelativeLines(pic);
+ break;
+ case 0xe3:
+ drawAbsoluteLines(pic);
+ break;
+ case 0xe4:
+ fill(pic);
+ break;
+ case 0xe5:
+ clear();
+ break;
+ case 0xf0:
+ _color = 0x00;
+ break;
+ case 0xf1:
+ _color = 0x2a;
+ break;
+ case 0xf2:
+ _color = 0x55;
+ break;
+ case 0xf3:
+ _color = 0x7f;
+ break;
+ case 0xf4:
+ _color = 0x80;
+ break;
+ case 0xf5:
+ _color = 0xaa;
+ break;
+ case 0xf6:
+ _color = 0xd5;
+ break;
+ case 0xf7:
+ _color = 0xff;
+ break;
+ case 0xff:
+ return;
+ default:
+ error("Invalid pic opcode %02x", opcode);
+ }
+ }
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp
index 6e1e31df9f..096d8ef496 100644
--- a/engines/adl/hires1.cpp
+++ b/engines/adl/hires1.cpp
@@ -25,6 +25,7 @@
#include "common/error.h"
#include "common/file.h"
#include "common/stream.h"
+#include "common/ptr.h"
#include "adl/hires1.h"
#include "adl/display.h"
@@ -32,14 +33,11 @@
namespace Adl {
void HiRes1Engine::runIntro() const {
- Common::File file;
+ StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
- if (!file.open(IDS_HR1_EXE_0))
- error("Failed to open file '" IDS_HR1_EXE_0 "'");
-
- file.seek(IDI_HR1_OFS_LOGO_0);
+ stream->seek(IDI_HR1_OFS_LOGO_0);
_display->setMode(DISPLAY_MODE_HIRES);
- _display->loadFrameBuffer(file);
+ _display->loadFrameBuffer(*stream);
_display->updateHiResScreen();
delay(4000);
@@ -48,22 +46,19 @@ void HiRes1Engine::runIntro() const {
_display->setMode(DISPLAY_MODE_TEXT);
- Common::File basic;
- if (!basic.open(IDS_HR1_LOADER))
- error("Failed to open file '" IDS_HR1_LOADER "'");
-
+ StreamPtr basic(_files->createReadStream(IDS_HR1_LOADER));
Common::String str;
- str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_0, '"');
+ str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_0, '"');
_display->printAsciiString(str + '\r');
- str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_1, '"');
+ str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_1, '"');
_display->printAsciiString(str + "\r\r");
- str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_2, '"');
+ str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_2, '"');
_display->printAsciiString(str + "\r\r");
- str = readStringAt(basic, IDI_HR1_OFS_PD_TEXT_3, '"');
+ str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_3, '"');
_display->printAsciiString(str + '\r');
inputKey();
@@ -72,7 +67,7 @@ void HiRes1Engine::runIntro() const {
_display->setMode(DISPLAY_MODE_MIXED);
- str = readStringAt(file, IDI_HR1_OFS_GAME_OR_HELP);
+ str = readStringAt(*stream, IDI_HR1_OFS_GAME_OR_HELP);
bool instructions = false;
@@ -96,7 +91,7 @@ void HiRes1Engine::runIntro() const {
if (instructions) {
_display->setMode(DISPLAY_MODE_TEXT);
- file.seek(IDI_HR1_OFS_INTRO_TEXT);
+ stream->seek(IDI_HR1_OFS_INTRO_TEXT);
const uint pages[] = { 6, 6, 4, 5, 8, 7, 0 };
@@ -106,9 +101,9 @@ void HiRes1Engine::runIntro() const {
uint count = pages[page++];
for (uint i = 0; i < count; ++i) {
- str = readString(file);
+ str = readString(*stream);
_display->printString(str);
- file.seek(3, SEEK_CUR);
+ stream->seek(3, SEEK_CUR);
}
inputString();
@@ -116,54 +111,47 @@ void HiRes1Engine::runIntro() const {
if (g_engine->shouldQuit())
return;
- file.seek(6, SEEK_CUR);
+ stream->seek(6, SEEK_CUR);
}
}
_display->printAsciiString("\r");
- file.close();
-
_display->setMode(DISPLAY_MODE_MIXED);
- if (!file.open(IDS_HR1_EXE_1))
- error("Failed to open file '" IDS_HR1_EXE_1 "'");
-
// Title screen shown during loading
- file.seek(IDI_HR1_OFS_LOGO_1);
- _display->loadFrameBuffer(file);
+ stream.reset(_files->createReadStream(IDS_HR1_EXE_1));
+ stream->seek(IDI_HR1_OFS_LOGO_1);
+ _display->loadFrameBuffer(*stream);
_display->updateHiResScreen();
delay(2000);
}
-void HiRes1Engine::loadData() {
- Common::File f;
-
- if (!f.open(IDS_HR1_MESSAGES))
- error("Failed to open file '" IDS_HR1_MESSAGES "'");
+void HiRes1Engine::init() {
+ if (Common::File::exists("MYSTHOUS.DSK")) {
+ _files = new Files_DOS33();
+ if (!static_cast<Files_DOS33 *>(_files)->open("MYSTHOUS.DSK"))
+ error("Failed to open MYSTHOUS.DSK");
+ } else
+ _files = new Files_Plain();
- for (uint i = 0; i < IDI_HR1_NUM_MESSAGES; ++i)
- _messages.push_back(readString(f, APPLECHAR('\r')) + APPLECHAR('\r'));
+ _graphics = new Graphics_v1(*_display);
- f.close();
-
- if (!f.open(IDS_HR1_EXE_1))
- error("Failed to open file '" IDS_HR1_EXE_1 "'");
+ StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
// Some messages have overrides inside the executable
- _messages[IDI_HR1_MSG_CANT_GO_THERE - 1] = readStringAt(f, IDI_HR1_OFS_STR_CANT_GO_THERE);
- _messages[IDI_HR1_MSG_DONT_HAVE_IT - 1] = readStringAt(f, IDI_HR1_OFS_STR_DONT_HAVE_IT);
- _messages[IDI_HR1_MSG_DONT_UNDERSTAND - 1] = readStringAt(f, IDI_HR1_OFS_STR_DONT_UNDERSTAND);
- _messages[IDI_HR1_MSG_GETTING_DARK - 1] = readStringAt(f, IDI_HR1_OFS_STR_GETTING_DARK);
+ _gameStrings.cantGoThere = readStringAt(*stream, IDI_HR1_OFS_STR_CANT_GO_THERE);
+ _gameStrings.dontHaveIt = readStringAt(*stream, IDI_HR1_OFS_STR_DONT_HAVE_IT);
+ _gameStrings.dontUnderstand = readStringAt(*stream, IDI_HR1_OFS_STR_DONT_UNDERSTAND);
+ _gameStrings.gettingDark = readStringAt(*stream, IDI_HR1_OFS_STR_GETTING_DARK);
// Load other strings from executable
- _strings.enterCommand = readStringAt(f, IDI_HR1_OFS_STR_ENTER_COMMAND);
- _strings.dontHaveIt = readStringAt(f, IDI_HR1_OFS_STR_DONT_HAVE_IT);
- _strings.gettingDark = readStringAt(f, IDI_HR1_OFS_STR_GETTING_DARK);
- _strings.verbError = readStringAt(f, IDI_HR1_OFS_STR_VERB_ERROR);
- _strings.nounError = readStringAt(f, IDI_HR1_OFS_STR_NOUN_ERROR);
- _strings.playAgain = readStringAt(f, IDI_HR1_OFS_STR_PLAY_AGAIN);
- _gameStrings.pressReturn = readStringAt(f, IDI_HR1_OFS_STR_PRESS_RETURN);
+ _strings.enterCommand = readStringAt(*stream, IDI_HR1_OFS_STR_ENTER_COMMAND);
+ _strings.verbError = readStringAt(*stream, IDI_HR1_OFS_STR_VERB_ERROR);
+ _strings.nounError = readStringAt(*stream, IDI_HR1_OFS_STR_NOUN_ERROR);
+ _strings.playAgain = readStringAt(*stream, IDI_HR1_OFS_STR_PLAY_AGAIN);
+ _strings.pressReturn = readStringAt(*stream, IDI_HR1_OFS_STR_PRESS_RETURN);
+ _strings.lineFeeds = readStringAt(*stream, IDI_HR1_OFS_STR_LINE_FEEDS);
// Set message IDs
_messageIds.cantGoThere = IDI_HR1_MSG_CANT_GO_THERE;
@@ -172,129 +160,118 @@ void HiRes1Engine::loadData() {
_messageIds.itemNotHere = IDI_HR1_MSG_ITEM_NOT_HERE;
_messageIds.thanksForPlaying = IDI_HR1_MSG_THANKS_FOR_PLAYING;
+ // Load message offsets
+ stream->seek(IDI_HR1_OFS_MSGS);
+ for (uint i = 0; i < IDI_HR1_NUM_MESSAGES; ++i)
+ _messages.push_back(_files->getDataBlock(IDS_HR1_MESSAGES, stream->readUint16LE()));
+
// Load picture data from executable
- f.seek(IDI_HR1_OFS_PICS);
- for (uint i = 0; i < IDI_HR1_NUM_PICS; ++i) {
- struct Picture pic;
- pic.block = f.readByte();
- pic.offset = f.readUint16LE();
- _pictures.push_back(pic);
+ stream->seek(IDI_HR1_OFS_PICS);
+ for (uint i = 1; i <= IDI_HR1_NUM_PICS; ++i) {
+ byte block = stream->readByte();
+ Common::String name = Common::String::format("BLOCK%i", block);
+ uint16 offset = stream->readUint16LE();
+ _pictures[i] = _files->getDataBlock(name, offset);
}
// Load commands from executable
- f.seek(IDI_HR1_OFS_CMDS_1);
- readCommands(f, _roomCommands);
+ stream->seek(IDI_HR1_OFS_CMDS_1);
+ readCommands(*stream, _roomCommands);
- f.seek(IDI_HR1_OFS_CMDS_0);
- readCommands(f, _globalCommands);
+ stream->seek(IDI_HR1_OFS_CMDS_0);
+ readCommands(*stream, _globalCommands);
// Load dropped item offsets
- f.seek(IDI_HR1_OFS_ITEM_OFFSETS);
+ stream->seek(IDI_HR1_OFS_ITEM_OFFSETS);
for (uint i = 0; i < IDI_HR1_NUM_ITEM_OFFSETS; ++i) {
Common::Point p;
- p.x = f.readByte();
- p.y = f.readByte();
+ p.x = stream->readByte();
+ p.y = stream->readByte();
_itemOffsets.push_back(p);
}
// Load right-angle line art
- f.seek(IDI_HR1_OFS_LINE_ART);
- uint16 lineArtTotal = f.readUint16LE();
- for (uint i = 0; i < lineArtTotal; ++i) {
- f.seek(IDI_HR1_OFS_LINE_ART + 2 + i * 2);
- uint16 offset = f.readUint16LE();
- f.seek(IDI_HR1_OFS_LINE_ART + offset);
-
- Common::Array<byte> lineArt;
- byte b = f.readByte();
- while (b != 0) {
- lineArt.push_back(b);
- b = f.readByte();
- }
- _lineArt.push_back(lineArt);
- }
+ stream->seek(IDI_HR1_OFS_CORNERS);
+ uint16 cornersCount = stream->readUint16LE();
+ for (uint i = 0; i < cornersCount; ++i)
+ _corners.push_back(_files->getDataBlock(IDS_HR1_EXE_1, IDI_HR1_OFS_CORNERS + stream->readUint16LE()));
- if (f.eos() || f.err())
+ if (stream->eos() || stream->err())
error("Failed to read game data from '" IDS_HR1_EXE_1 "'");
- f.seek(IDI_HR1_OFS_VERBS);
- loadWords(f, _verbs);
+ stream->seek(IDI_HR1_OFS_VERBS);
+ loadWords(*stream, _verbs, _priVerbs);
- f.seek(IDI_HR1_OFS_NOUNS);
- loadWords(f, _nouns);
+ stream->seek(IDI_HR1_OFS_NOUNS);
+ loadWords(*stream, _nouns, _priNouns);
}
-void HiRes1Engine::initState() {
- Common::File f;
-
- _state.room = 1;
- _state.moves = 0;
- _state.isDark = false;
-
- _state.vars.clear();
+void HiRes1Engine::initGameState() {
_state.vars.resize(IDI_HR1_NUM_VARS);
- if (!f.open(IDS_HR1_EXE_1))
- error("Failed to open file '" IDS_HR1_EXE_1 "'");
+ StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
// Load room data from executable
- _state.rooms.clear();
- f.seek(IDI_HR1_OFS_ROOMS);
+ _roomDesc.clear();
+ stream->seek(IDI_HR1_OFS_ROOMS);
for (uint i = 0; i < IDI_HR1_NUM_ROOMS; ++i) {
Room room;
- f.readByte();
- room.description = f.readByte();
+ stream->readByte();
+ _roomDesc.push_back(stream->readByte());
for (uint j = 0; j < 6; ++j)
- room.connections[j] = f.readByte();
- room.picture = f.readByte();
- room.curPicture = f.readByte();
+ room.connections[j] = stream->readByte();
+ room.picture = stream->readByte();
+ room.curPicture = stream->readByte();
_state.rooms.push_back(room);
}
// Load item data from executable
- _state.items.clear();
- f.seek(IDI_HR1_OFS_ITEMS);
- while (f.readByte() != 0xff) {
- Item item;
- item.noun = f.readByte();
- item.room = f.readByte();
- item.picture = f.readByte();
- item.isLineArt = f.readByte();
- item.position.x = f.readByte();
- item.position.y = f.readByte();
- item.state = f.readByte();
- item.description = f.readByte();
-
- f.readByte();
-
- byte size = f.readByte();
+ stream->seek(IDI_HR1_OFS_ITEMS);
+ byte id;
+ while ((id = stream->readByte()) != 0xff) {
+ Item item = Item();
+ item.id = id;
+ item.noun = stream->readByte();
+ item.room = stream->readByte();
+ item.picture = stream->readByte();
+ item.isLineArt = stream->readByte();
+ item.position.x = stream->readByte();
+ item.position.y = stream->readByte();
+ item.state = stream->readByte();
+ item.description = stream->readByte();
+
+ stream->readByte();
+
+ byte size = stream->readByte();
for (uint i = 0; i < size; ++i)
- item.roomPictures.push_back(f.readByte());
+ item.roomPictures.push_back(stream->readByte());
_state.items.push_back(item);
}
}
void HiRes1Engine::restartGame() {
+ _display->printString(_strings.pressReturn);
initState();
- _display->printString(_gameStrings.pressReturn);
- inputString(); // Missing in the original
- _display->printAsciiString("\r\r\r\r\r");
+ _display->printAsciiString(_strings.lineFeeds);
}
-void HiRes1Engine::drawPic(byte pic, Common::Point pos) const {
- Common::File f;
- Common::String name = Common::String::format("BLOCK%i", _pictures[pic].block);
+void HiRes1Engine::printString(const Common::String &str) {
+ Common::String wrap = str;
+ wordWrap(wrap);
+ _display->printString(wrap);
- if (!f.open(name))
- error("Failed to open file '%s'", name.c_str());
+ if (_messageDelay)
+ delay(14 * 166018 / 1000);
+}
- f.seek(_pictures[pic].offset);
- drawPic(f, pos);
+Common::String HiRes1Engine::loadMessage(uint idx) const {
+ StreamPtr stream(_messages[idx]->createReadStream());
+ return readString(*stream, APPLECHAR('\r')) + APPLECHAR('\r');
}
-void HiRes1Engine::printMessage(uint idx, bool wait) const {
+void HiRes1Engine::printMessage(uint idx) {
// Messages with hardcoded overrides don't delay after printing.
// It's unclear if this is a bug or not. In some cases the result
// is that these strings will scroll past the four-line text window
@@ -304,88 +281,89 @@ void HiRes1Engine::printMessage(uint idx, bool wait) const {
// that system for this game as well.
switch (idx) {
case IDI_HR1_MSG_CANT_GO_THERE:
+ _display->printString(_gameStrings.cantGoThere);
+ return;
case IDI_HR1_MSG_DONT_HAVE_IT:
+ _display->printString(_gameStrings.dontHaveIt);
+ return;
case IDI_HR1_MSG_DONT_UNDERSTAND:
+ _display->printString(_gameStrings.dontUnderstand);
+ return;
case IDI_HR1_MSG_GETTING_DARK:
- wait = false;
+ _display->printString(_gameStrings.gettingDark);
+ return;
+ default:
+ printString(loadMessage(idx));
}
-
- AdlEngine::printMessage(idx, wait);
}
-void HiRes1Engine::drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const {
- // This draws a four-connected line
-
- int16 deltaX = p2.x - p1.x;
- int8 xStep = 1;
-
- if (deltaX < 0) {
- deltaX = -deltaX;
- xStep = -1;
- }
-
- int16 deltaY = p2.y - p1.y;
- int8 yStep = -1;
-
- if (deltaY > 0) {
- deltaY = -deltaY;
- yStep = 1;
- }
-
- Common::Point p(p1);
- int16 steps = deltaX - deltaY + 1;
- int16 err = deltaX + deltaY;
+void HiRes1Engine::drawItems() {
+ Common::List<Item>::iterator item;
- while (1) {
- _display->putPixel(p, color);
+ uint dropped = 0;
- if (--steps == 0)
- return;
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
+ // Skip items not in this room
+ if (item->room != _state.room)
+ continue;
- if (err < 0) {
- p.y += yStep;
- err += deltaX;
+ if (item->state == IDI_ITEM_DROPPED) {
+ // Draw dropped item if in normal view
+ if (getCurRoom().picture == getCurRoom().curPicture)
+ drawItem(*item, _itemOffsets[dropped++]);
} else {
- p.x += xStep;
- err += deltaY;
+ // Draw fixed item if current view is in the pic list
+ Common::Array<byte>::const_iterator pic;
+
+ for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) {
+ if (*pic == getCurRoom().curPicture) {
+ drawItem(*item, item->position);
+ break;
+ }
+ }
}
}
}
-void HiRes1Engine::drawPic(Common::ReadStream &stream, const Common::Point &pos) const {
- byte x, y;
- bool bNewLine = false;
- byte oldX = 0, oldY = 0;
- while (1) {
- x = stream.readByte();
- y = stream.readByte();
+void HiRes1Engine::drawItem(Item &item, const Common::Point &pos) {
+ if (item.isLineArt) {
+ StreamPtr stream(_corners[item.picture - 1]->createReadStream());
+ static_cast<Graphics_v1 *>(_graphics)->drawCorners(*stream, pos);
+ } else
+ drawPic(item.picture, pos);
+}
+
+void HiRes1Engine::loadRoom(byte roomNr) {
+ _roomData.description = loadMessage(_roomDesc[_state.room - 1]);
+}
- if (stream.err() || stream.eos())
- error("Failed to read picture");
+void HiRes1Engine::showRoom() {
+ clearScreen();
+ loadRoom(_state.room);
- if (x == 0xff && y == 0xff)
- return;
+ if (!_state.isDark) {
+ drawPic(getCurRoom().curPicture);
+ drawItems();
+ }
- if (x == 0 && y == 0) {
- bNewLine = true;
- continue;
- }
+ _display->updateHiResScreen();
+ _messageDelay = false;
+ printString(_roomData.description);
+ _messageDelay = true;
+}
- x += pos.x;
- y += pos.y;
+void HiRes1Engine::wordWrap(Common::String &str) const {
+ uint end = 39;
- if (y > 160)
- y = 160;
+ while (1) {
+ if (str.size() <= end)
+ return;
- if (bNewLine) {
- _display->putPixel(Common::Point(x, y), 0x7f);
- bNewLine = false;
- } else {
- drawLine(Common::Point(oldX, oldY), Common::Point(x, y), 0x7f);
- }
+ while (str[end] != APPLECHAR(' '))
+ --end;
- oldX = x;
- oldY = y;
+ str.setChar(APPLECHAR('\r'), end);
+ end += 40;
}
}
diff --git a/engines/adl/hires1.h b/engines/adl/hires1.h
index 25f4744d26..c060bc892e 100644
--- a/engines/adl/hires1.h
+++ b/engines/adl/hires1.h
@@ -26,6 +26,8 @@
#include "common/str.h"
#include "adl/adl.h"
+#include "adl/graphics.h"
+#include "adl/disk.h"
namespace Common {
class ReadStream;
@@ -40,10 +42,10 @@ namespace Adl {
#define IDS_HR1_MESSAGES "MESSAGES"
#define IDI_HR1_NUM_ROOMS 41
-#define IDI_HR1_NUM_PICS 98
+#define IDI_HR1_NUM_PICS 97
#define IDI_HR1_NUM_VARS 20
#define IDI_HR1_NUM_ITEM_OFFSETS 21
-#define IDI_HR1_NUM_MESSAGES 167
+#define IDI_HR1_NUM_MESSAGES 168
// Messages used outside of scripts
#define IDI_HR1_MSG_CANT_GO_THERE 137
@@ -63,6 +65,7 @@ namespace Adl {
#define IDI_HR1_OFS_STR_DONT_UNDERSTAND 0x6c51
#define IDI_HR1_OFS_STR_GETTING_DARK 0x6c7c
#define IDI_HR1_OFS_STR_PRESS_RETURN 0x5f68
+#define IDI_HR1_OFS_STR_LINE_FEEDS 0x59d4
#define IDI_HR1_OFS_PD_TEXT_0 0x005d
#define IDI_HR1_OFS_PD_TEXT_1 0x012b
@@ -77,34 +80,52 @@ namespace Adl {
#define IDI_HR1_OFS_ITEMS 0x0100
#define IDI_HR1_OFS_ROOMS 0x050a
-#define IDI_HR1_OFS_PICS 0x4b00
+#define IDI_HR1_OFS_PICS 0x4b03
#define IDI_HR1_OFS_CMDS_0 0x3c00
#define IDI_HR1_OFS_CMDS_1 0x3d00
+#define IDI_HR1_OFS_MSGS 0x4d00
#define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff
-#define IDI_HR1_OFS_LINE_ART 0x4f00
+#define IDI_HR1_OFS_CORNERS 0x4f00
#define IDI_HR1_OFS_VERBS 0x3800
#define IDI_HR1_OFS_NOUNS 0x0f00
class HiRes1Engine : public AdlEngine {
public:
- HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) : AdlEngine(syst, gd) { }
+ HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine(syst, gd),
+ _files(nullptr),
+ _messageDelay(true) { }
+ ~HiRes1Engine() { delete _files; }
private:
// AdlEngine
void runIntro() const;
- void loadData();
- void initState();
+ void init();
+ void initGameState();
void restartGame();
- void drawPic(byte pic, Common::Point pos) const;
- void printMessage(uint idx, bool wait = true) const;
-
- void drawLine(const Common::Point &p1, const Common::Point &p2, byte color) const;
- void drawPic(Common::ReadStream &stream, const Common::Point &pos) const;
+ void printString(const Common::String &str);
+ Common::String loadMessage(uint idx) const;
+ void printMessage(uint idx);
+ void drawItems();
+ void drawItem(Item &item, const Common::Point &pos);
+ void loadRoom(byte roomNr);
+ void showRoom();
+
+ void wordWrap(Common::String &str) const;
+
+ Files *_files;
+ Common::File _exe;
+ Common::Array<DataBlockPtr> _corners;
+ Common::Array<byte> _roomDesc;
+ bool _messageDelay;
struct {
- Common::String pressReturn;
+ Common::String cantGoThere;
+ Common::String dontHaveIt;
+ Common::String dontUnderstand;
+ Common::String gettingDark;
} _gameStrings;
};
diff --git a/engines/adl/hires2.cpp b/engines/adl/hires2.cpp
new file mode 100644
index 0000000000..d8e8a65e29
--- /dev/null
+++ b/engines/adl/hires2.cpp
@@ -0,0 +1,183 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "common/debug.h"
+#include "common/error.h"
+#include "common/file.h"
+#include "common/stream.h"
+
+#include "adl/hires2.h"
+#include "adl/display.h"
+#include "adl/graphics.h"
+#include "adl/disk.h"
+
+namespace Adl {
+
+void HiRes2Engine::runIntro() const {
+ StreamPtr stream(_disk->createReadStream(0x00, 0xd, 0x17, 1));
+
+ _display->setMode(DISPLAY_MODE_TEXT);
+
+ Common::String str = readString(*stream);
+
+ if (stream->eos() || stream->err())
+ error("Error reading disk image");
+
+ _display->printString(str);
+ delay(2000);
+}
+
+void HiRes2Engine::init() {
+ _graphics = new Graphics_v2(*_display);
+
+ _disk = new DiskImage_DSK();
+ if (!_disk->open(IDS_HR2_DISK_IMAGE))
+ error("Failed to open disk image '" IDS_HR2_DISK_IMAGE "'");
+
+ StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4));
+
+ for (uint i = 0; i < IDI_HR2_NUM_MESSAGES; ++i)
+ _messages.push_back(readDataBlockPtr(*stream));
+
+ // Read parser messages
+ stream.reset(_disk->createReadStream(0x1a, 0x1));
+ _strings.verbError = readStringAt(*stream, 0x4f);
+ _strings.nounError = readStringAt(*stream, 0x8e);
+ _strings.enterCommand = readStringAt(*stream, 0xbc);
+
+ // Read time string
+ stream.reset(_disk->createReadStream(0x19, 0x7, 0xd7));
+ _strings_v2.time = readString(*stream, 0xff);
+
+ // Read line feeds
+ stream.reset(_disk->createReadStream(0x19, 0xb, 0xf8, 1));
+ _strings.lineFeeds = readString(*stream);
+
+ // Read opcode strings
+ stream.reset(_disk->createReadStream(0x1a, 0x6, 0x00, 2));
+ _strings_v2.saveInsert = readStringAt(*stream, 0x5f);
+ _strings_v2.saveReplace = readStringAt(*stream, 0xe5);
+ _strings_v2.restoreInsert = readStringAt(*stream, 0x132);
+ _strings_v2.restoreReplace = readStringAt(*stream, 0x1c2);
+ _strings.playAgain = readStringAt(*stream, 0x225);
+ _strings.pressReturn = readStringAt(*stream, 0x25f);
+
+ _messageIds.cantGoThere = IDI_HR2_MSG_CANT_GO_THERE;
+ _messageIds.dontUnderstand = IDI_HR2_MSG_DONT_UNDERSTAND;
+ _messageIds.itemDoesntMove = IDI_HR2_MSG_ITEM_DOESNT_MOVE;
+ _messageIds.itemNotHere = IDI_HR2_MSG_ITEM_NOT_HERE;
+ _messageIds.thanksForPlaying = IDI_HR2_MSG_THANKS_FOR_PLAYING;
+
+ // Load global picture data
+ stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0));
+ byte picNr;
+ while ((picNr = stream->readByte()) != 0xff) {
+ if (stream->eos() || stream->err())
+ error("Error reading global pic list");
+
+ _pictures[picNr] = readDataBlockPtr(*stream);
+ }
+
+ // Load item picture data
+ stream.reset(_disk->createReadStream(0x1e, 0x9, 0x05));
+ for (uint i = 0; i < IDI_HR2_NUM_ITEM_PICS; ++i) {
+ stream->readByte(); // number
+ _itemPics.push_back(readDataBlockPtr(*stream));
+ }
+
+ // Load commands from executable
+ stream.reset(_disk->createReadStream(0x1d, 0x7, 0x00, 4));
+ readCommands(*stream, _roomCommands);
+
+ stream.reset(_disk->createReadStream(0x1f, 0x7, 0x00, 2));
+ readCommands(*stream, _globalCommands);
+
+ // Load dropped item offsets
+ stream.reset(_disk->createReadStream(0x1b, 0x4, 0x15));
+ for (uint i = 0; i < IDI_HR2_NUM_ITEM_OFFSETS; ++i) {
+ Common::Point p;
+ p.x = stream->readByte();
+ p.y = stream->readByte();
+ _itemOffsets.push_back(p);
+ }
+
+ // Load verbs
+ stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 3));
+ loadWords(*stream, _verbs, _priVerbs);
+
+ // Load nouns
+ stream.reset(_disk->createReadStream(0x22, 0x2, 0x00, 7));
+ loadWords(*stream, _nouns, _priNouns);
+}
+
+void HiRes2Engine::initGameState() {
+ _state.vars.resize(IDI_HR2_NUM_VARS);
+
+ StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 7));
+
+ for (uint i = 0; i < IDI_HR2_NUM_ROOMS; ++i) {
+ Room room;
+ stream->readByte(); // number
+ for (uint j = 0; j < 6; ++j)
+ room.connections[j] = stream->readByte();
+ room.data = readDataBlockPtr(*stream);
+ room.picture = stream->readByte();
+ room.curPicture = stream->readByte();
+ room.isFirstTime = stream->readByte();
+ _state.rooms.push_back(room);
+ }
+
+ stream.reset(_disk->createReadStream(0x21, 0x0, 0x00, 2));
+
+ byte id;
+ while ((id = stream->readByte()) != 0xff) {
+ Item item = Item();
+ item.id = id;
+ item.noun = stream->readByte();
+ item.room = stream->readByte();
+ item.picture = stream->readByte();
+ item.isLineArt = stream->readByte(); // Is this still used in this way?
+ item.position.x = stream->readByte();
+ item.position.y = stream->readByte();
+ item.state = stream->readByte();
+ item.description = stream->readByte();
+
+ stream->readByte(); // Struct size
+
+ byte picListSize = stream->readByte();
+
+ // Flag to keep track of what has been drawn on the screen
+ stream->readByte();
+
+ for (uint i = 0; i < picListSize; ++i)
+ item.roomPictures.push_back(stream->readByte());
+
+ _state.items.push_back(item);
+ }
+}
+
+Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd) {
+ return new HiRes2Engine(syst, gd);
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/hires2.h b/engines/adl/hires2.h
new file mode 100644
index 0000000000..50016725d6
--- /dev/null
+++ b/engines/adl/hires2.h
@@ -0,0 +1,66 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ADL_HIRES2_H
+#define ADL_HIRES2_H
+
+#include "common/str.h"
+
+#include "adl/adl_v2.h"
+#include "adl/disk.h"
+
+namespace Common {
+class ReadStream;
+struct Point;
+}
+
+namespace Adl {
+
+#define IDS_HR2_DISK_IMAGE "WIZARD.DSK"
+
+#define IDI_HR2_NUM_ROOMS 135
+#define IDI_HR2_NUM_MESSAGES 255
+#define IDI_HR2_NUM_VARS 40
+#define IDI_HR2_NUM_ITEM_PICS 38
+#define IDI_HR2_NUM_ITEM_OFFSETS 16
+
+// Messages used outside of scripts
+#define IDI_HR2_MSG_CANT_GO_THERE 123
+#define IDI_HR2_MSG_DONT_UNDERSTAND 19
+#define IDI_HR2_MSG_ITEM_DOESNT_MOVE 242
+#define IDI_HR2_MSG_ITEM_NOT_HERE 4
+#define IDI_HR2_MSG_THANKS_FOR_PLAYING 239
+
+class HiRes2Engine : public AdlEngine_v2 {
+public:
+ HiRes2Engine(OSystem *syst, const AdlGameDescription *gd) : AdlEngine_v2(syst, gd) { }
+
+private:
+ // AdlEngine
+ void runIntro() const;
+ void init();
+ void initGameState();
+};
+
+} // End of namespace Adl
+
+#endif
diff --git a/engines/adl/hires6.cpp b/engines/adl/hires6.cpp
new file mode 100644
index 0000000000..c42b4165a6
--- /dev/null
+++ b/engines/adl/hires6.cpp
@@ -0,0 +1,444 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "common/debug.h"
+#include "common/error.h"
+#include "common/file.h"
+#include "common/stream.h"
+#include "common/memstream.h"
+
+#include "adl/hires6.h"
+#include "adl/display.h"
+#include "adl/graphics.h"
+#include "adl/disk.h"
+
+namespace Adl {
+
+static const char *disks[] = { "DARK1A.DSK", "DARK1B.NIB", "DARK2A.NIB", "DARK2B.NIB" };
+
+#define SECTORS_PER_TRACK 16
+#define BYTES_PER_SECTOR 256
+
+static Common::MemoryReadStream *loadSectors(DiskImage *disk, byte track, byte sector = SECTORS_PER_TRACK - 1, byte count = SECTORS_PER_TRACK) {
+ const int bufSize = count * BYTES_PER_SECTOR;
+ byte *const buf = (byte *)malloc(bufSize);
+ byte *p = buf;
+
+ while (count-- > 0) {
+ StreamPtr stream(disk->createReadStream(track, sector, 0, 0));
+ stream->read(p, BYTES_PER_SECTOR);
+
+ if (stream->err() || stream->eos())
+ error("Error loading from disk image");
+
+ p += BYTES_PER_SECTOR;
+ if (sector > 0)
+ --sector;
+ else {
+ ++track;
+
+ // Skip VTOC track
+ if (track == 17)
+ ++track;
+
+ sector = SECTORS_PER_TRACK - 1;
+ }
+ }
+
+ return new Common::MemoryReadStream(buf, bufSize, DisposeAfterUse::YES);
+}
+
+void HiRes6Engine::runIntro() const {
+ DiskImage_DSK *boot(new DiskImage_DSK());
+
+ if (!boot->open(disks[0]))
+ error("Failed to open disk image '%s'", disks[0]);
+
+ StreamPtr stream(loadSectors(boot, 11, 1, 96));
+
+ _display->setMode(DISPLAY_MODE_HIRES);
+ _display->loadFrameBuffer(*stream);
+ _display->updateHiResScreen();
+ delay(256 * 8609 / 1000);
+
+ _display->loadFrameBuffer(*stream);
+ _display->updateHiResScreen();
+ delay(256 * 8609 / 1000);
+
+ _display->loadFrameBuffer(*stream);
+
+ delete boot;
+
+ // Load copyright string from boot file
+ Files_DOS33 *files(new Files_DOS33());
+
+ if (!files->open(disks[0]))
+ error("Failed to open disk image '%s'", disks[0]);
+
+ stream.reset(files->createReadStream("\010\010\010\010\010\010"));
+ Common::String copyright(readStringAt(*stream, 0x103, APPLECHAR('\r')));
+
+ delete files;
+
+ _display->updateHiResScreen();
+ _display->home();
+ _display->setMode(DISPLAY_MODE_MIXED);
+ _display->moveCursorTo(Common::Point(0, 21));
+ _display->printString(copyright);
+ delay(256 * 8609 / 1000);
+}
+
+void HiRes6Engine::init() {
+ _boot = new DiskImage_DSK();
+ _graphics = new Graphics_v2(*_display);
+
+ if (!_boot->open(disks[0]))
+ error("Failed to open disk image '%s'", disks[0]);
+
+ StreamPtr stream(loadSectors(_boot, 0x7));
+
+ // Read parser messages
+ _strings.verbError = readStringAt(*stream, 0x666);
+ _strings.nounError = readStringAt(*stream, 0x6bd);
+ _strings.enterCommand = readStringAt(*stream, 0x6e9);
+
+ // Read line feeds
+ _strings.lineFeeds = readStringAt(*stream, 0x408);
+
+ // Read opcode strings (TODO)
+ _strings_v2.saveInsert = readStringAt(*stream, 0xad8);
+ readStringAt(*stream, 0xb95); // Confirm save
+ // _strings_v2.saveReplace
+ _strings_v2.restoreInsert = readStringAt(*stream, 0xc07);
+ // _strings_v2.restoreReplace
+ _strings.playAgain = readStringAt(*stream, 0xcdf, 0xff);
+
+ _messageIds.cantGoThere = IDI_HR6_MSG_CANT_GO_THERE;
+ _messageIds.dontUnderstand = IDI_HR6_MSG_DONT_UNDERSTAND;
+ _messageIds.itemDoesntMove = IDI_HR6_MSG_ITEM_DOESNT_MOVE;
+ _messageIds.itemNotHere = IDI_HR6_MSG_ITEM_NOT_HERE;
+ _messageIds.thanksForPlaying = IDI_HR6_MSG_THANKS_FOR_PLAYING;
+
+ // Item descriptions
+ stream.reset(loadSectors(_boot, 0x6, 0xb, 2));
+ stream->seek(0x34);
+ for (uint i = 0; i < IDI_HR6_NUM_ITEM_DESCS; ++i)
+ _itemDesc.push_back(readString(*stream, 0xff));
+
+ // Load dropped item offsets
+ stream.reset(_boot->createReadStream(0x8, 0x9, 0x16));
+ for (uint i = 0; i < IDI_HR6_NUM_ITEM_OFFSETS; ++i) {
+ Common::Point p;
+ p.x = stream->readByte();
+ p.y = stream->readByte();
+ _itemOffsets.push_back(p);
+ }
+
+ // Location of game data for each disc
+ stream.reset(_boot->createReadStream(0x5, 0xa, 0x03));
+ for (uint i = 0; i < sizeof(disks); ++i) {
+ DiskDataDesc desc;
+ desc.track = stream->readByte();
+ desc.sector = stream->readByte();
+ desc.offset = stream->readByte();
+ desc.volume = stream->readByte();
+ _diskDataDesc.push_back(desc);
+ }
+
+ // DataBlockPtr offsets for each disk
+ stream.reset(_boot->createReadStream(0x3, 0xf, 0x03));
+ for (uint i = 0; i < sizeof(disks); ++i) {
+ DiskOffset offset;
+ offset.track = stream->readByte();
+ offset.sector = stream->readByte();
+ _diskOffsets.push_back(offset);
+ }
+}
+
+void HiRes6Engine::loadDisk(byte disk) {
+ delete _disk;
+ _disk = new DiskImage_NIB();
+
+ if (!_disk->open(disks[disk]))
+ error("Failed to open disk image '%s'", disks[disk]);
+
+ _curDisk = 0;
+
+ // Load item picture data (indexed on boot disk)
+ StreamPtr stream(_boot->createReadStream(0xb, 0xd, 0x08));
+ _itemPics.clear();
+ for (uint i = 0; i < IDI_HR6_NUM_ITEM_PICS; ++i) {
+ stream->readByte();
+ _itemPics.push_back(readDataBlockPtr(*stream));
+ }
+
+ _curDisk = disk;
+
+ byte track = _diskDataDesc[disk].track;
+ byte sector = _diskDataDesc[disk].sector;
+ uint offset = _diskDataDesc[disk].offset;
+
+ applyDiskOffset(track, sector);
+
+ for (uint block = 0; block < 7; ++block) {
+ stream.reset(_disk->createReadStream(track, sector, offset, 1));
+
+ uint16 addr = stream->readUint16LE();
+ uint16 size = stream->readUint16LE();
+
+ stream.reset(_disk->createReadStream(track, sector, offset, size / 256 + 1));
+ stream->skip(4);
+
+ switch (addr) {
+ case 0x9000: {
+ // Messages
+ _messages.clear();
+ uint count = size / 4;
+ for (uint i = 0; i < count; ++i)
+ _messages.push_back(readDataBlockPtr(*stream));
+ break;
+ }
+ case 0x4a80: {
+ // Global pics
+ _pictures.clear();
+ byte picNr;
+ while ((picNr = stream->readByte()) != 0xff) {
+ if (stream->eos() || stream->err())
+ error("Error reading global pic list");
+ _pictures[picNr] = readDataBlockPtr(*stream);
+ }
+ break;
+ }
+ case 0x4000:
+ // Verbs
+ loadWords(*stream, _verbs, _priVerbs);
+ break;
+ case 0x1800:
+ // Nouns
+ loadWords(*stream, _nouns, _priNouns);
+ break;
+ case 0x0e00: {
+ // Rooms
+ uint count = size / 14 - 1;
+ stream->skip(14); // Skip invalid room 0
+
+ _state.rooms.clear();
+ for (uint i = 0; i < count; ++i) {
+ Room room;
+ stream->readByte(); // number
+ for (uint j = 0; j < 6; ++j)
+ room.connections[j] = stream->readByte();
+ room.data = readDataBlockPtr(*stream);
+ room.picture = stream->readByte();
+ room.curPicture = stream->readByte();
+ room.isFirstTime = stream->readByte();
+ _state.rooms.push_back(room);
+ }
+ break;
+ }
+ case 0x7b00:
+ // Global commands
+ readCommands(*stream, _globalCommands);
+ break;
+ case 0x9500:
+ // Room commands
+ readCommands(*stream, _roomCommands);
+ break;
+ default:
+ error("Unknown data block found (addr %04x; size %04x)", addr, size);
+ }
+
+ offset += 4 + size;
+ while (offset >= 256) {
+ offset -= 256;
+ ++sector;
+ if (sector >= 16) {
+ sector = 0;
+ ++track;
+ }
+ }
+ }
+}
+
+void HiRes6Engine::initGameState() {
+ _state.vars.resize(IDI_HR6_NUM_VARS);
+
+ loadDisk(1);
+
+ StreamPtr stream(_boot->createReadStream(0x3, 0xe, 0x03));
+
+ byte id;
+ while ((id = stream->readByte()) != 0xff) {
+ Item item = Item();
+ item.id = id;
+ item.noun = stream->readByte();
+ item.room = stream->readByte();
+ item.picture = stream->readByte();
+ item.isLineArt = stream->readByte(); // Now seems to be disk number
+ item.position.x = stream->readByte();
+ item.position.y = stream->readByte();
+ item.state = stream->readByte();
+ item.description = stream->readByte();
+
+ stream->readByte(); // Struct size
+
+ byte picListSize = stream->readByte();
+
+ // Flag to keep track of what has been drawn on the screen
+ stream->readByte();
+
+ for (uint i = 0; i < picListSize; ++i)
+ item.roomPictures.push_back(stream->readByte());
+
+ _state.items.push_back(item);
+ }
+
+ _currVerb = _currNoun = 0;
+}
+
+void HiRes6Engine::showRoom() {
+ bool redrawPic = false;
+
+ if (getVar(26) == 0xfe)
+ setVar(26, 0);
+ else if (getVar(26) != 0xff)
+ setVar(26, _state.room);
+
+ if (_state.room != _roomOnScreen) {
+ loadRoom(_state.room);
+
+ if (getVar(26) < 0x80 && getCurRoom().isFirstTime)
+ setVar(26, 0);
+
+ clearScreen();
+
+ if (!_state.isDark)
+ redrawPic = true;
+ } else {
+ if (getCurRoom().curPicture != _picOnScreen || _itemRemoved)
+ redrawPic = true;
+ }
+
+ if (redrawPic) {
+ _roomOnScreen = _state.room;
+ _picOnScreen = getCurRoom().curPicture;
+
+ drawPic(getCurRoom().curPicture);
+ _itemRemoved = false;
+ _itemsOnScreen = 0;
+
+ Common::List<Item>::iterator item;
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ item->isOnScreen = false;
+ }
+
+ if (!_state.isDark)
+ drawItems();
+
+ _display->updateHiResScreen();
+ setVar(2, 0xff);
+ printString(_roomData.description);
+
+ // FIXME: move to main loop?
+ _linesPrinted = 0;
+}
+
+Common::String HiRes6Engine::formatVerbError(const Common::String &verb) const {
+ Common::String err = _strings.verbError;
+
+ for (uint i = 0; i < verb.size(); ++i)
+ err.setChar(verb[i], i + 24);
+
+ err.setChar(APPLECHAR(' '), 32);
+
+ uint i = 24;
+ while (err[i] != APPLECHAR(' '))
+ ++i;
+
+ err.setChar(APPLECHAR('.'), i);
+
+ return err;
+}
+
+Common::String HiRes6Engine::formatNounError(const Common::String &verb, const Common::String &noun) const {
+ Common::String err = _strings.nounError;
+
+ for (uint i = 0; i < noun.size(); ++i)
+ err.setChar(noun[i], i + 24);
+
+ for (uint i = 35; i > 31; --i)
+ err.setChar(APPLECHAR(' '), i);
+
+ uint i = 24;
+ while (err[i] != APPLECHAR(' '))
+ ++i;
+
+ err.setChar(APPLECHAR('I'), i + 1);
+ err.setChar(APPLECHAR('S'), i + 2);
+ err.setChar(APPLECHAR('.'), i + 3);
+
+ return err;
+}
+
+void HiRes6Engine::printString(const Common::String &str) {
+ Common::String s;
+ uint found = 0;
+
+ // This does not emulate the corner cases of the original, hence this check
+ if (getVar(27) > 1)
+ error("Invalid value %i encountered for variable 27", getVar(27));
+
+ for (uint i = 0; i < str.size(); ++i) {
+ if (str[i] == '%') {
+ ++found;
+ if (found == 3)
+ found = 0;
+ } else {
+ if (found == 0 || found - 1 == getVar(27))
+ s += str[i];
+ }
+ }
+
+ if (getVar(2) != 0xff) {
+ AdlEngine_v2::printString(s);
+ } else {
+ if (getVar(26) == 0) {
+ if (str.size() != 1 || APPLECHAR(str[0]) != APPLECHAR(' '))
+ return AdlEngine_v2::printString(s);
+ setVar(2, APPLECHAR(' '));
+ } else if (getVar(26) != 0xff) {
+ setVar(2, 'P');
+ } else {
+ setVar(26, _state.room);
+ setVar(2, 1);
+ }
+
+ doAllCommands(_globalCommands, _currVerb, _currNoun);
+ }
+}
+
+Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd) {
+ return new HiRes6Engine(syst, gd);
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/hires6.h b/engines/adl/hires6.h
new file mode 100644
index 0000000000..4bd2bcc7cc
--- /dev/null
+++ b/engines/adl/hires6.h
@@ -0,0 +1,92 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ADL_HIRES6_H
+#define ADL_HIRES6_H
+
+#include "common/str.h"
+
+#include "adl/adl_v3.h"
+#include "adl/disk.h"
+
+namespace Common {
+class ReadStream;
+struct Point;
+}
+
+namespace Adl {
+
+#define IDI_HR6_NUM_ROOMS 35
+#define IDI_HR6_NUM_MESSAGES 256
+#define IDI_HR6_NUM_VARS 40
+#define IDI_HR6_NUM_ITEM_DESCS 15
+#define IDI_HR6_NUM_ITEM_PICS 15
+#define IDI_HR6_NUM_ITEM_OFFSETS 16
+
+// Messages used outside of scripts
+#define IDI_HR6_MSG_CANT_GO_THERE 249
+#define IDI_HR6_MSG_DONT_UNDERSTAND 247
+#define IDI_HR6_MSG_ITEM_DOESNT_MOVE 253
+#define IDI_HR6_MSG_ITEM_NOT_HERE 254
+#define IDI_HR6_MSG_THANKS_FOR_PLAYING 252
+
+struct DiskDataDesc {
+ byte track;
+ byte sector;
+ byte offset;
+ byte volume;
+};
+
+class HiRes6Engine : public AdlEngine_v3 {
+public:
+ HiRes6Engine(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine_v3(syst, gd),
+ _boot(nullptr),
+ _currVerb(0),
+ _currNoun(0) {
+ }
+
+ ~HiRes6Engine() { delete _boot; }
+
+private:
+ // AdlEngine
+ void runIntro() const;
+ void init();
+ void initGameState();
+ void printRoomDescription();
+ void showRoom();
+ Common::String formatVerbError(const Common::String &verb) const;
+ Common::String formatNounError(const Common::String &verb, const Common::String &noun) const;
+
+ // AdlEngine_v2
+ void printString(const Common::String &str);
+
+ void loadDisk(byte disk);
+
+ DiskImage_DSK *_boot;
+ byte _currVerb, _currNoun;
+ Common::Array<DiskDataDesc> _diskDataDesc;
+};
+
+} // End of namespace Adl
+
+#endif
diff --git a/engines/adl/module.mk b/engines/adl/module.mk
index 6acd06f6de..7ab37efc67 100644
--- a/engines/adl/module.mk
+++ b/engines/adl/module.mk
@@ -2,9 +2,19 @@ MODULE := engines/adl
MODULE_OBJS := \
adl.o \
+ adl_v2.o \
+ adl_v3.o \
+ console.o \
detection.o \
+ disk.o \
display.o \
- hires1.o
+ graphics.o \
+ graphics_v1.o \
+ graphics_v2.o \
+ hires1.o \
+ hires2.o \
+ hires6.o \
+ speaker.o
MODULE_DIRS += \
engines/adl
diff --git a/engines/adl/speaker.cpp b/engines/adl/speaker.cpp
new file mode 100644
index 0000000000..532d361cd9
--- /dev/null
+++ b/engines/adl/speaker.cpp
@@ -0,0 +1,94 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "common/events.h"
+
+#include "engines/engine.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+#include "adl/speaker.h"
+#include "adl/adl.h"
+
+namespace Adl {
+
+// Number of times to duplicate each sample
+#define SAMPLE_DUP 4
+// Bell frequency in Hz
+#define BELL_FREQ 1000
+// Sample rate
+#define SAMPLE_RATE (BELL_FREQ * SAMPLE_DUP * 2)
+// Number of waves per 0.1 seconds (bell length)
+#define BELL_WAVE_COUNT (SAMPLE_RATE / 10 / SAMPLE_DUP / 2)
+// Length of bell in samples
+#define BELL_LEN (BELL_WAVE_COUNT * SAMPLE_DUP * 2)
+// Length of silence in samples
+#define SILENCE_LEN (SAMPLE_RATE / 80)
+
+Speaker::~Speaker() {
+ delete[] _bell;
+ delete[] _silence;
+}
+
+Speaker::Speaker() {
+ _bell = new byte[BELL_LEN];
+
+ byte *buf = _bell;
+ for (uint i = 0; i < BELL_WAVE_COUNT; ++i) {
+ for (uint j = 0; j < SAMPLE_DUP; ++j)
+ *buf++ = 0x00;
+ for (uint j = 0; j < SAMPLE_DUP; ++j)
+ *buf++ = 0xff;
+ }
+
+ _silence = new byte[SILENCE_LEN];
+
+ buf = _silence;
+ for (uint i = 0; i < SILENCE_LEN; ++i)
+ *buf++ = 0x80;
+}
+
+void Speaker::bell(uint count) {
+ Audio::QueuingAudioStream *stream = Audio::makeQueuingAudioStream(SAMPLE_RATE, false);
+ Audio::SoundHandle handle;
+
+ stream->queueBuffer(_bell, BELL_LEN, DisposeAfterUse::NO, Audio::FLAG_UNSIGNED);
+
+ for (uint i = 1; i < count; ++i) {
+ stream->queueBuffer(_silence, SILENCE_LEN, DisposeAfterUse::NO, Audio::FLAG_UNSIGNED);
+ stream->queueBuffer(_bell, BELL_LEN, DisposeAfterUse::NO, Audio::FLAG_UNSIGNED);
+ }
+
+ stream->finish();
+
+ g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &handle, stream);
+
+ while (!g_engine->shouldQuit() && g_system->getMixer()->isSoundHandleActive(handle)) {
+ Common::Event event;
+ static_cast<AdlEngine *>(g_engine)->pollEvent(event);
+ g_system->delayMillis(16);
+ }
+}
+
+} // End of namespace Adl
diff --git a/engines/sci/graphics/paint.h b/engines/adl/speaker.h
index b2277131d5..31aaac32d2 100644
--- a/engines/sci/graphics/paint.h
+++ b/engines/adl/speaker.h
@@ -20,20 +20,30 @@
*
*/
-#ifndef SCI_GRAPHICS_PAINT_H
-#define SCI_GRAPHICS_PAINT_H
+#ifndef ADL_SPEAKER_H
+#define ADL_SPEAKER_H
-namespace Sci {
+#include "common/types.h"
-class GfxPaint {
+#include "audio/mixer.h"
+
+namespace Audio {
+class AudioStream;
+}
+
+namespace Adl {
+
+class Speaker {
public:
- GfxPaint();
- virtual ~GfxPaint();
+ Speaker();
+ ~Speaker();
+
+ void bell(uint count);
- virtual void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo);
- virtual void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control);
+private:
+ byte *_bell, *_silence;
};
-} // End of namespace Sci
+} // End of namespace Adl
#endif
diff --git a/engines/agi/global.cpp b/engines/agi/global.cpp
index 23256f27fb..6f83f02a4e 100644
--- a/engines/agi/global.cpp
+++ b/engines/agi/global.cpp
@@ -21,6 +21,7 @@
*/
#include "common/config-manager.h"
+#include "audio/mixer.h"
#include "agi/agi.h"
#include "agi/graphics.h"
diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp
index 24cd4f43d3..3b1b99f458 100644
--- a/engines/agi/graphics.cpp
+++ b/engines/agi/graphics.cpp
@@ -61,6 +61,14 @@ GfxMgr::GfxMgr(AgiBase *vm, GfxFont *font) : _vm(vm), _font(font) {
_displayWidthMulAdjust = 0; // visualPos * (2+0) = displayPos
_displayHeightMulAdjust = 0; // visualPos * (1+0) = displayPos
+
+ _pixels = 0;
+ _displayPixels = 0;
+
+ _activeScreen = NULL;
+ _gameScreen = NULL;
+ _priorityScreen = NULL;
+ _displayScreen = NULL;
}
/**
diff --git a/engines/agi/lzw.cpp b/engines/agi/lzw.cpp
index bf41e1f3b4..ecb69543d7 100644
--- a/engines/agi/lzw.cpp
+++ b/engines/agi/lzw.cpp
@@ -72,6 +72,7 @@ LZWDecoder::LZWDecoder() {
appendCharacter = (uint8 *)malloc(TABLE_SIZE * sizeof(uint8));
inputBitCount = 0; // Number of bits in input bit buffer
inputBitBuffer = 0L;
+ BITS = MAX_VALUE = MAX_CODE = 0;
}
LZWDecoder::~LZWDecoder() {
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index a80e811f44..2b3bba89db 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -44,6 +44,8 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
_minCommand = 0xf0;
_flags = 0;
_currentStep = 0;
+
+ _width = _height = 0;
}
void PictureMgr::putVirtPixel(int x, int y) {
diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp
index 7e2e65a3eb..f8630db0b6 100644
--- a/engines/agi/preagi.cpp
+++ b/engines/agi/preagi.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "audio/mixer.h"
#include "audio/softsynth/pcspk.h"
#include "common/debug-channels.h"
@@ -50,6 +51,8 @@ PreAgiEngine::PreAgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) :
memset(&_game, 0, sizeof(struct AgiGame));
memset(&_debug, 0, sizeof(struct AgiDebug));
memset(&_mouse, 0, sizeof(struct Mouse));
+
+ _speakerHandle = new Audio::SoundHandle();
}
void PreAgiEngine::initialize() {
@@ -74,7 +77,7 @@ void PreAgiEngine::initialize() {
_gfx->initVideo();
_speakerStream = new Audio::PCSpeaker(_mixer->getOutputRate());
- _mixer->playStream(Audio::Mixer::kSFXSoundType, &_speakerHandle,
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, _speakerHandle,
_speakerStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
debugC(2, kDebugLevelMain, "Detect game");
@@ -89,8 +92,9 @@ void PreAgiEngine::initialize() {
}
PreAgiEngine::~PreAgiEngine() {
- _mixer->stopHandle(_speakerHandle);
+ _mixer->stopHandle(*_speakerHandle);
delete _speakerStream;
+ delete _speakerHandle;
delete _picture;
delete _gfx;
diff --git a/engines/agi/preagi.h b/engines/agi/preagi.h
index d6026a5d4d..6950aa30cd 100644
--- a/engines/agi/preagi.h
+++ b/engines/agi/preagi.h
@@ -26,6 +26,7 @@
#include "agi/agi.h"
namespace Audio {
+class SoundHandle;
class PCSpeaker;
}
@@ -110,7 +111,7 @@ private:
int _defaultColor;
Audio::PCSpeaker *_speakerStream;
- Audio::SoundHandle _speakerHandle;
+ Audio::SoundHandle *_speakerHandle;
};
} // End of namespace Agi
diff --git a/engines/agi/preagi_mickey.cpp b/engines/agi/preagi_mickey.cpp
index e1545cdb68..0584aab683 100644
--- a/engines/agi/preagi_mickey.cpp
+++ b/engines/agi/preagi_mickey.cpp
@@ -255,7 +255,7 @@ bool MickeyEngine::checkMenu() {
return parse(menu.cmd[iSel0].data[iSel1], menu.arg[iSel0].data[iSel1]);
}
-void MickeyEngine::drawMenu(MSA_MENU menu, int sel0, int sel1) {
+void MickeyEngine::drawMenu(MSA_MENU &menu, int sel0, int sel1) {
int iWord;
int iRow;
int sel;
@@ -286,7 +286,7 @@ void MickeyEngine::drawMenu(MSA_MENU menu, int sel0, int sel1) {
_gfx->updateScreen();
}
-void MickeyEngine::getMouseMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow, int x, int y) {
+void MickeyEngine::getMouseMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int iRow, int x, int y) {
int iWord;
int *sel = 0;
@@ -313,7 +313,7 @@ void MickeyEngine::getMouseMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int i
}
}
-bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow) {
+bool MickeyEngine::getMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int iRow) {
Common::Event event;
int *sel = 0;
int nWords;
diff --git a/engines/agi/preagi_mickey.h b/engines/agi/preagi_mickey.h
index 81565d3982..066880d324 100644
--- a/engines/agi/preagi_mickey.h
+++ b/engines/agi/preagi_mickey.h
@@ -710,9 +710,9 @@ protected:
void printExeMsg(int);
void printDesc(int);
bool checkMenu();
- void drawMenu(MSA_MENU, int, int);
- void getMouseMenuSelRow(MSA_MENU, int *, int *, int, int, int);
- bool getMenuSelRow(MSA_MENU, int *, int *, int);
+ void drawMenu(MSA_MENU &, int, int);
+ void getMouseMenuSelRow(MSA_MENU &, int *, int *, int, int, int);
+ bool getMenuSelRow(MSA_MENU &, int *, int *, int);
void getMenuSel(char *, int *, int *);
void centerMenu(MSA_MENU *);
void patchMenu(MSA_MENU *);
diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp
index edf17960ad..2c1eb021f9 100644
--- a/engines/agi/sound.cpp
+++ b/engines/agi/sound.cpp
@@ -29,9 +29,19 @@
#include "agi/sound_pcjr.h"
#include "common/textconsole.h"
+#include "audio/mixer.h"
namespace Agi {
+SoundGen::SoundGen(AgiBase *vm, Audio::Mixer *pMixer) : _vm(vm), _mixer(pMixer) {
+ _sampleRate = pMixer->getOutputRate();
+ _soundHandle = new Audio::SoundHandle();
+}
+
+SoundGen::~SoundGen() {
+ delete _soundHandle;
+}
+
//
// TODO: add support for variable sampling rate in the output device
//
@@ -183,6 +193,7 @@ SoundMgr::SoundMgr(AgiBase *agi, Audio::Mixer *pMixer) {
_playingSound = -1;
switch (_vm->_soundemu) {
+ default:
case SOUND_EMU_NONE:
case SOUND_EMU_AMIGA:
case SOUND_EMU_MAC:
diff --git a/engines/agi/sound.h b/engines/agi/sound.h
index 4b668e8cf2..8aa7a5d1df 100644
--- a/engines/agi/sound.h
+++ b/engines/agi/sound.h
@@ -23,7 +23,10 @@
#ifndef AGI_SOUND_H
#define AGI_SOUND_H
-#include "audio/mixer.h"
+namespace Audio {
+class Mixer;
+class SoundHandle;
+}
namespace Agi {
@@ -71,11 +74,8 @@ class SoundMgr;
class SoundGen {
public:
- SoundGen(AgiBase *vm, Audio::Mixer *pMixer) : _vm(vm), _mixer(pMixer) {
- _sampleRate = pMixer->getOutputRate();
- }
-
- virtual ~SoundGen() {}
+ SoundGen(AgiBase *vm, Audio::Mixer *pMixer);
+ virtual ~SoundGen();
virtual void play(int resnum) = 0;
virtual void stop(void) = 0;
@@ -83,7 +83,7 @@ public:
AgiBase *_vm;
Audio::Mixer *_mixer;
- Audio::SoundHandle _soundHandle;
+ Audio::SoundHandle *_soundHandle;
uint32 _sampleRate;
};
diff --git a/engines/agi/sound_2gs.cpp b/engines/agi/sound_2gs.cpp
index b1bcee3920..4992b6516c 100644
--- a/engines/agi/sound_2gs.cpp
+++ b/engines/agi/sound_2gs.cpp
@@ -27,6 +27,7 @@
#include "common/memstream.h"
#include "common/str-array.h"
#include "common/textconsole.h"
+#include "audio/mixer.h"
#include "agi/agi.h"
#include "agi/sound_2gs.h"
@@ -55,11 +56,11 @@ SoundGen2GS::SoundGen2GS(AgiBase *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixe
// Load instruments
_disableMidi = !loadInstruments();
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, _soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
SoundGen2GS::~SoundGen2GS() {
- _mixer->stopHandle(_soundHandle);
+ _mixer->stopHandle(*_soundHandle);
delete[] _wavetable;
delete[] _out;
}
diff --git a/engines/agi/sound_2gs.h b/engines/agi/sound_2gs.h
index a7a23f5b06..49a375cdbc 100644
--- a/engines/agi/sound_2gs.h
+++ b/engines/agi/sound_2gs.h
@@ -126,6 +126,7 @@ public:
memset(&osc, 0, sizeof(osc));
seg = 0;
a = 0;
+ velocity = 0;
}
const IIgsInstrumentHeader *curInstrument; ///< Currently used instrument
diff --git a/engines/agi/sound_pcjr.cpp b/engines/agi/sound_pcjr.cpp
index 9d556e048a..0a0c456e14 100644
--- a/engines/agi/sound_pcjr.cpp
+++ b/engines/agi/sound_pcjr.cpp
@@ -54,6 +54,7 @@
*
*/
+#include "audio/mixer.h"
#include "agi/agi.h"
#include "agi/sound.h"
#include "agi/sound_pcjr.h"
@@ -123,7 +124,7 @@ SoundGenPCJr::SoundGenPCJr(AgiBase *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMi
memset(_channel, 0, sizeof(_channel));
memset(_tchannel, 0, sizeof(_tchannel));
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, _soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
_v1data = NULL;
_v1size = 0;
@@ -132,7 +133,7 @@ SoundGenPCJr::SoundGenPCJr(AgiBase *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMi
SoundGenPCJr::~SoundGenPCJr() {
free(_chanData);
- _mixer->stopHandle(_soundHandle);
+ _mixer->stopHandle(*_soundHandle);
}
void SoundGenPCJr::play(int resnum) {
diff --git a/engines/agi/sound_sarien.cpp b/engines/agi/sound_sarien.cpp
index 939c3d2c77..3e44546688 100644
--- a/engines/agi/sound_sarien.cpp
+++ b/engines/agi/sound_sarien.cpp
@@ -21,6 +21,7 @@
*/
#include "common/random.h"
+#include "audio/mixer.h"
#include "agi/agi.h"
@@ -73,6 +74,7 @@ SoundGenSarien::SoundGenSarien(AgiBase *vm, Audio::Mixer *pMixer) : SoundGen(vm,
_useChorus = true; // FIXME: Currently always true?
switch (_vm->_soundemu) {
+ default:
case SOUND_EMU_NONE:
_waveform = waveformRamp;
_env = true;
@@ -92,11 +94,11 @@ SoundGenSarien::SoundGenSarien(AgiBase *vm, Audio::Mixer *pMixer) : SoundGen(vm,
debug(0, "Initializing sound: envelopes disabled");
}
- _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, _soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
SoundGenSarien::~SoundGenSarien() {
- _mixer->stopHandle(_soundHandle);
+ _mixer->stopHandle(*_soundHandle);
free(_sndBuffer);
}
diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp
index 274a654547..4aa42ffec3 100644
--- a/engines/agi/text.cpp
+++ b/engines/agi/text.cpp
@@ -39,6 +39,8 @@ TextMgr::TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx) {
_words = words;
_gfx = gfx;
+ _systemUI = NULL;
+
memset(&_messageState, 0, sizeof(_messageState));
_textPos.row = 0;
_textPos.column = 0;
diff --git a/engines/agi/view.h b/engines/agi/view.h
index e59916da78..adcf7dd1b3 100644
--- a/engines/agi/view.h
+++ b/engines/agi/view.h
@@ -132,6 +132,8 @@ struct ScreenObjEntry {
uint8 wander_count;
// end of motion related variables
uint8 loop_flag;
+
+ ScreenObjEntry() { memset(this, 0, sizeof(ScreenObjEntry)); }
}; // struct vt_entry
} // End of namespace Agi
diff --git a/engines/bbvs/spritemodule.cpp b/engines/bbvs/spritemodule.cpp
index f8b0d9afd5..74c71e5aeb 100644
--- a/engines/bbvs/spritemodule.cpp
+++ b/engines/bbvs/spritemodule.cpp
@@ -32,7 +32,8 @@ byte *Sprite::getRow(int y) {
}
SpriteModule::SpriteModule()
- : _spritesCount(0), _paletteStart(0), _paletteCount(0), _spriteData(0) {
+ : _spritesCount(0), _paletteStart(0), _paletteCount(0), _spriteData(0), _spriteDataSize(0),
+ _spriteTblOffs(0), _paletteOffs(0) {
}
SpriteModule::~SpriteModule() {
diff --git a/engines/cge/detection.cpp b/engines/cge/detection.cpp
index 52168dc934..82d27f8d54 100644
--- a/engines/cge/detection.cpp
+++ b/engines/cge/detection.cpp
@@ -123,7 +123,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "Soltys (c) 1994-1996 L.K. Avalon";
+ return "Soltys (C) 1994-1996 L.K. Avalon";
}
virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const;
diff --git a/engines/cge2/detection.cpp b/engines/cge2/detection.cpp
index 01119bb1fd..2b84d167c7 100644
--- a/engines/cge2/detection.cpp
+++ b/engines/cge2/detection.cpp
@@ -119,7 +119,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "Sfinx (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon";
+ return "Sfinx (C) 1994-1997 Janus B. Wisniewski and L.K. Avalon";
}
virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const;
diff --git a/engines/engine.cpp b/engines/engine.cpp
index d3b9b113cf..8fff99f3fc 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -269,8 +269,8 @@ void splashScreen() {
// Load logo
Graphics::Surface *logo = bitmap.getSurface()->convertTo(g_system->getOverlayFormat(), bitmap.getPalette());
- int lx = (g_system->getOverlayWidth() - logo->w) / 2;
- int ly = (g_system->getOverlayHeight() - logo->h) / 2;
+ int lx = MAX((g_system->getOverlayWidth() - logo->w) / 2, 0);
+ int ly = MAX((g_system->getOverlayHeight() - logo->h) / 2, 0);
// Print version information
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
@@ -283,7 +283,10 @@ void splashScreen() {
screen.free();
// Draw logo
- g_system->copyRectToOverlay(logo->getPixels(), logo->pitch, lx, ly, logo->w, logo->h);
+ int lw = MIN<uint16>(logo->w, g_system->getOverlayWidth() - lx);
+ int lh = MIN<uint16>(logo->h, g_system->getOverlayHeight() - ly);
+
+ g_system->copyRectToOverlay(logo->getPixels(), logo->pitch, lx, ly, lw, lh);
logo->free();
delete logo;
diff --git a/engines/gnap/character.cpp b/engines/gnap/character.cpp
index 36d849acbf..56e520068d 100644
--- a/engines/gnap/character.cpp
+++ b/engines/gnap/character.cpp
@@ -402,22 +402,12 @@ int PlayerGnap::getSequenceId(int kind, Common::Point gridPos) {
case kGSMoan1:
if (gridPos.x > 0 && gridPos.y > 0) {
- if (_pos.y > gridPos.y) {
- if (_pos.x > gridPos.x) {
- sequenceId = 0x832;
- _idleFacing = kDirBottomLeft;
- } else {
- sequenceId = 0x7AA;
- _idleFacing = kDirBottomRight;
- }
+ if (_pos.x > gridPos.x) {
+ sequenceId = 0x832;
+ _idleFacing = kDirBottomLeft;
} else {
- if (_pos.x > gridPos.x) {
- sequenceId = 0x832;
- _idleFacing = kDirBottomLeft;
- } else {
- sequenceId = 0x7AA;
- _idleFacing = kDirBottomRight;
- }
+ sequenceId = 0x7AA;
+ _idleFacing = kDirBottomRight;
}
} else if (_idleFacing != kDirBottomRight && _idleFacing != kDirUpRight) {
sequenceId = 0x832;
diff --git a/engines/gnap/configure.engine b/engines/gnap/configure.engine
index 7aa538fd2c..f3742ef0d2 100644
--- a/engines/gnap/configure.engine
+++ b/engines/gnap/configure.engine
@@ -1,3 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine gnap "UFOs" no
+add_engine gnap "UFOs" no "" "" "highres"
diff --git a/engines/gnap/datarchive.cpp b/engines/gnap/datarchive.cpp
index c74766bd03..3a586c58ba 100644
--- a/engines/gnap/datarchive.cpp
+++ b/engines/gnap/datarchive.cpp
@@ -83,7 +83,6 @@ DatManager::~DatManager() {
void DatManager::open(int index, const char *filename) {
close(index);
_datArchives[index] = new DatArchive(filename);
- warning("Loading %s - %d", filename, index);
}
void DatManager::close(int index) {
diff --git a/engines/gnap/detection.cpp b/engines/gnap/detection.cpp
index a7e9eece4a..7e4ab56d1f 100644
--- a/engines/gnap/detection.cpp
+++ b/engines/gnap/detection.cpp
@@ -43,7 +43,7 @@ static const ADGameDescription gameDescriptions[] = {
{"stock_n.dat", 0, "46819043d019a2f36b727cc2bdd6980f", 12515823},
AD_LISTEND
},
- Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO0()
+ Common::EN_ANY, Common::kPlatformWindows, ADGF_TESTING, GUIO0()
},
{
"gnap", "",
@@ -51,7 +51,7 @@ static const ADGameDescription gameDescriptions[] = {
{"stock_n.dat", 0, "46819043d019a2f36b727cc2bdd6980f", 12995485},
AD_LISTEND
},
- Common::RU_RUS, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO0()
+ Common::RU_RUS, Common::kPlatformWindows, ADGF_TESTING, GUIO0()
},
AD_TABLE_END_MARKER
@@ -154,11 +154,10 @@ SaveStateDescriptor GnapMetaEngine::querySaveMetaInfos(const char *target, int s
return SaveStateDescriptor();
}
- char saveName[256];
+ Common::String saveName;
char ch;
- int i = 0;
while ((ch = (char)file->readByte()) != '\0')
- saveName[i++] = ch;
+ saveName += ch;
SaveStateDescriptor desc(slot, saveName);
diff --git a/engines/gnap/fontdata.h b/engines/gnap/fontdata.h
index ef39df960e..61128211fc 100644
--- a/engines/gnap/fontdata.h
+++ b/engines/gnap/fontdata.h
@@ -28,6 +28,8 @@ namespace Gnap {
struct FONT_CHAR_INFO {
const byte _width; // width, in bits (or pixels), of the character
const uint16 _offset; // offset of the character's bitmap, in bytes, into the the FONT_INFO's data array
+
+ FONT_CHAR_INFO(byte width, uint16 offset) : _width(width), _offset(offset) {}
};
/*
@@ -747,101 +749,101 @@ const byte _dejaVuSans9ptCharBitmaps[] = {
/* Character descriptors for DejaVu Sans 9pt */
/* { [Char width in bits], [Offset into dejaVuSans9ptCharBitmaps in bytes] } */
const FONT_CHAR_INFO _dejaVuSans9ptCharDescriptors[] = {
- {5, 0}, /* */
- {1, 10}, /* ! */
- {3, 12}, /* " */
- {8, 18}, /* # */
- {5, 34}, /* $ */
- {10, 44}, /* % */
- {8, 64}, /* & */
- {1, 80}, /* ' */
- {3, 82}, /* ( */
- {3, 88}, /* ) */
- {5, 94}, /* * */
- {7, 104}, /* + */
- {1, 118}, /* , */
- {3, 120}, /* - */
- {1, 126}, /* . */
- {4, 128}, /* / */
- {6, 136}, /* 0 */
- {5, 148}, /* 1 */
- {6, 158}, /* 2 */
- {6, 170}, /* 3 */
- {6, 182}, /* 4 */
- {6, 194}, /* 5 */
- {6, 206}, /* 6 */
- {6, 218}, /* 7 */
- {6, 230}, /* 8 */
- {6, 242}, /* 9 */
- {1, 254}, /* : */
- {1, 256}, /* ; */
- {8, 258}, /* < */
- {8, 274}, /* = */
- {8, 290}, /* > */
- {5, 306}, /* ? */
- {11, 316}, /* @ */
- {8, 338}, /* A */
- {6, 354}, /* B */
- {6, 366}, /* C */
- {7, 378}, /* D */
- {6, 392}, /* E */
- {5, 404}, /* F */
- {7, 414}, /* G */
- {7, 428}, /* H */
- {1, 442}, /* I */
- {3, 444}, /* J */
- {6, 450}, /* K */
- {5, 462}, /* L */
- {8, 472}, /* M */
- {7, 488}, /* N */
- {7, 502}, /* O */
- {6, 516}, /* P */
- {7, 528}, /* Q */
- {7, 542}, /* R */
- {6, 556}, /* S */
- {7, 568}, /* T */
- {7, 582}, /* U */
- {8, 596}, /* V */
- {11, 612}, /* W */
- {7, 634}, /* X */
- {7, 648}, /* Y */
- {7, 662}, /* Z */
- {2, 676}, /* [ */
- {4, 680}, /* \ */
- {2, 688}, /* ] */
- {6, 692}, /* ^ */
- {6, 704}, /* _ */
- {2, 716}, /* ` */
- {6, 720}, /* a */
- {6, 732}, /* b */
- {5, 744}, /* c */
- {6, 754}, /* d */
- {6, 766}, /* e */
- {4, 778}, /* f */
- {6, 786}, /* g */
- {6, 798}, /* h */
- {1, 810}, /* i */
- {2, 812}, /* j */
- {5, 816}, /* k */
- {1, 826}, /* l */
- {9, 828}, /* m */
- {6, 846}, /* n */
- {6, 858}, /* o */
- {6, 870}, /* p */
- {6, 882}, /* q */
- {4, 894}, /* r */
- {5, 902}, /* s */
- {4, 912}, /* t */
- {6, 920}, /* u */
- {6, 932}, /* v */
- {9, 944}, /* w */
- {6, 962}, /* x */
- {6, 974}, /* y */
- {5, 986}, /* z */
- {5, 996}, /* { */
- {1, 1006}, /* | */
- {5, 1008}, /* } */
- {8, 1018}, /* ~ */
+ FONT_CHAR_INFO(5, 0), /* */
+ FONT_CHAR_INFO(1, 10), /* ! */
+ FONT_CHAR_INFO(3, 12), /* " */
+ FONT_CHAR_INFO(8, 18), /* # */
+ FONT_CHAR_INFO(5, 34), /* $ */
+ FONT_CHAR_INFO(10, 44), /* % */
+ FONT_CHAR_INFO(8, 64), /* & */
+ FONT_CHAR_INFO(1, 80), /* ' */
+ FONT_CHAR_INFO(3, 82), /* ( */
+ FONT_CHAR_INFO(3, 88), /* ) */
+ FONT_CHAR_INFO(5, 94), /* * */
+ FONT_CHAR_INFO(7, 104), /* + */
+ FONT_CHAR_INFO(1, 118), /* , */
+ FONT_CHAR_INFO(3, 120), /* - */
+ FONT_CHAR_INFO(1, 126), /* . */
+ FONT_CHAR_INFO(4, 128), /* / */
+ FONT_CHAR_INFO(6, 136), /* 0 */
+ FONT_CHAR_INFO(5, 148), /* 1 */
+ FONT_CHAR_INFO(6, 158), /* 2 */
+ FONT_CHAR_INFO(6, 170), /* 3 */
+ FONT_CHAR_INFO(6, 182), /* 4 */
+ FONT_CHAR_INFO(6, 194), /* 5 */
+ FONT_CHAR_INFO(6, 206), /* 6 */
+ FONT_CHAR_INFO(6, 218), /* 7 */
+ FONT_CHAR_INFO(6, 230), /* 8 */
+ FONT_CHAR_INFO(6, 242), /* 9 */
+ FONT_CHAR_INFO(1, 254), /* : */
+ FONT_CHAR_INFO(1, 256), /* ; */
+ FONT_CHAR_INFO(8, 258), /* < */
+ FONT_CHAR_INFO(8, 274), /* = */
+ FONT_CHAR_INFO(8, 290), /* > */
+ FONT_CHAR_INFO(5, 306), /* ? */
+ FONT_CHAR_INFO(11, 316), /* @ */
+ FONT_CHAR_INFO(8, 338), /* A */
+ FONT_CHAR_INFO(6, 354), /* B */
+ FONT_CHAR_INFO(6, 366), /* C */
+ FONT_CHAR_INFO(7, 378), /* D */
+ FONT_CHAR_INFO(6, 392), /* E */
+ FONT_CHAR_INFO(5, 404), /* F */
+ FONT_CHAR_INFO(7, 414), /* G */
+ FONT_CHAR_INFO(7, 428), /* H */
+ FONT_CHAR_INFO(1, 442), /* I */
+ FONT_CHAR_INFO(3, 444), /* J */
+ FONT_CHAR_INFO(6, 450), /* K */
+ FONT_CHAR_INFO(5, 462), /* L */
+ FONT_CHAR_INFO(8, 472), /* M */
+ FONT_CHAR_INFO(7, 488), /* N */
+ FONT_CHAR_INFO(7, 502), /* O */
+ FONT_CHAR_INFO(6, 516), /* P */
+ FONT_CHAR_INFO(7, 528), /* Q */
+ FONT_CHAR_INFO(7, 542), /* R */
+ FONT_CHAR_INFO(6, 556), /* S */
+ FONT_CHAR_INFO(7, 568), /* T */
+ FONT_CHAR_INFO(7, 582), /* U */
+ FONT_CHAR_INFO(8, 596), /* V */
+ FONT_CHAR_INFO(11, 612), /* W */
+ FONT_CHAR_INFO(7, 634), /* X */
+ FONT_CHAR_INFO(7, 648), /* Y */
+ FONT_CHAR_INFO(7, 662), /* Z */
+ FONT_CHAR_INFO(2, 676), /* [ */
+ FONT_CHAR_INFO(4, 680), /* \ */
+ FONT_CHAR_INFO(2, 688), /* ] */
+ FONT_CHAR_INFO(6, 692), /* ^ */
+ FONT_CHAR_INFO(6, 704), /* _ */
+ FONT_CHAR_INFO(2, 716), /* ` */
+ FONT_CHAR_INFO(6, 720), /* a */
+ FONT_CHAR_INFO(6, 732), /* b */
+ FONT_CHAR_INFO(5, 744), /* c */
+ FONT_CHAR_INFO(6, 754), /* d */
+ FONT_CHAR_INFO(6, 766), /* e */
+ FONT_CHAR_INFO(4, 778), /* f */
+ FONT_CHAR_INFO(6, 786), /* g */
+ FONT_CHAR_INFO(6, 798), /* h */
+ FONT_CHAR_INFO(1, 810), /* i */
+ FONT_CHAR_INFO(2, 812), /* j */
+ FONT_CHAR_INFO(5, 816), /* k */
+ FONT_CHAR_INFO(1, 826), /* l */
+ FONT_CHAR_INFO(9, 828), /* m */
+ FONT_CHAR_INFO(6, 846), /* n */
+ FONT_CHAR_INFO(6, 858), /* o */
+ FONT_CHAR_INFO(6, 870), /* p */
+ FONT_CHAR_INFO(6, 882), /* q */
+ FONT_CHAR_INFO(4, 894), /* r */
+ FONT_CHAR_INFO(5, 902), /* s */
+ FONT_CHAR_INFO(4, 912), /* t */
+ FONT_CHAR_INFO(6, 920), /* u */
+ FONT_CHAR_INFO(6, 932), /* v */
+ FONT_CHAR_INFO(9, 944), /* w */
+ FONT_CHAR_INFO(6, 962), /* x */
+ FONT_CHAR_INFO(6, 974), /* y */
+ FONT_CHAR_INFO(5, 986), /* z */
+ FONT_CHAR_INFO(5, 996), /* { */
+ FONT_CHAR_INFO(1, 1006), /* | */
+ FONT_CHAR_INFO(5, 1008), /* ) */
+ FONT_CHAR_INFO(8, 1018) /* ~ */
};
} // End of namespace Gnap
diff --git a/engines/gnap/gamesys.cpp b/engines/gnap/gamesys.cpp
index e59662f08a..08a4af39ee 100644
--- a/engines/gnap/gamesys.cpp
+++ b/engines/gnap/gamesys.cpp
@@ -48,6 +48,7 @@ GameSys::GameSys(GnapEngine *vm) : _vm(vm) {
_removeSequenceItemValue = 0;
_gfxItemsCount = 0;
_animationsCount = 0;
+ _animationsDone = false;
_backgroundImageValue3 = 0;
_backgroundImageValue1 = 0;
_backgroundImageValue4 = 1000;
@@ -167,6 +168,9 @@ void GameSys::requestRemoveSequence(int sequenceId, int id) {
void GameSys::waitForUpdate() {
//ResetEvent(updateEvent);
//WaitForSingleObject(updateEvent, INFINITE);
+ while ( !_animationsDone) {
+ _vm->gameUpdateTick();
+ }
}
int GameSys::isSequenceActive(int sequenceId, int id) {
@@ -638,9 +642,9 @@ void GameSys::blitSurface32(Graphics::Surface *destSurface, int x, int y, Graphi
byte *rsrc = src;
byte *rdst = dst;
for (int xc = 0; xc < width; ++xc) {
- uint32 pixel = READ_LE_UINT32(rsrc);
+ uint32 pixel = READ_UINT32(rsrc);
if (!transparent || pixel != 0xFFFFFF00)
- WRITE_LE_UINT32(rdst, pixel);
+ WRITE_UINT32(rdst, pixel);
rsrc += 4;
rdst += 4;
}
@@ -1006,6 +1010,8 @@ void GameSys::fatUpdateFrame() {
if (clockDelta <= 0)
return;
+ _animationsDone = true;
+
int duration, currFrameNum;
for (int i = 0; i < _gfxItemsCount; ++i) {
@@ -1091,10 +1097,12 @@ void GameSys::fatUpdateFrame() {
} else {
gfxItem->_prevFrame._duration -= duration;
gfxItem->_updFlag = false;
+ _animationsDone = false;
}
} else {
gfxItem->_delayTicks -= clockDelta;
gfxItem->_updFlag = false;
+ _animationsDone = false;
}
}
} else {
@@ -1108,7 +1116,10 @@ void GameSys::fatUpdateFrame() {
if (_newSpriteDrawItemsCount > 0) {
debugC(kDebugBasic, "_newSpriteDrawItemsCount: %d", _newSpriteDrawItemsCount);
for (int k = 0; k < _newSpriteDrawItemsCount; ++k) {
- if (_gfxItemsCount < 50) {
+ // The original was allowing a buffer overflow.
+ // In order to fit in memory, insertIndex + 1 + (_gfxItemsCount - InsertIndex) must be
+ // smaller than the size _gfxItems array (50).
+ if (_gfxItemsCount + 1 < 50) {
int insertIndex;
seqLocateGfx(-1, _newSpriteDrawItems[k]._id, &insertIndex);
if (_gfxItemsCount != insertIndex)
@@ -1130,8 +1141,9 @@ void GameSys::fatUpdateFrame() {
gfxItem->_currFrame._duration = 0;
gfxItem->_currFrame._isScaled = false;
gfxItem->_currFrame._rect = _newSpriteDrawItems[k]._rect;
- gfxItem->_currFrame._spriteId = _newSpriteDrawItems[k]._surface ? 0xCAFEBABE : -1;// TODO
+ gfxItem->_currFrame._spriteId = _newSpriteDrawItems[k]._surface ? (int32)0xCAFEBABE : -1;// TODO
gfxItem->_currFrame._soundId = -1;
+ _animationsDone = false;
}
}
_newSpriteDrawItemsCount = 0;
@@ -1149,6 +1161,7 @@ void GameSys::fatUpdateFrame() {
gfxItem->_currFrame._soundId = -1;
gfxItem->_updFlag = true;
gfxItem->_surface = _grabSpriteSurface2;
+ _animationsDone = false;
break;
}
}
@@ -1175,6 +1188,7 @@ void GameSys::fatUpdateFrame() {
found = true;
}
if (found) {
+ _animationsDone = false;
seqRemoveGfx(seqItem->_sequenceId2, seqItem->_id2);
seqRemoveGfx(seqItem->_sequenceId, seqItem->_id);
_fatSequenceItems.remove_at(i);
@@ -1197,6 +1211,7 @@ void GameSys::fatUpdateFrame() {
found = true;
}
if (found) {
+ _animationsDone = false;
seqRemoveGfx(seqItem->_sequenceId, seqItem->_id);
_fatSequenceItems.remove_at(i);
--i;
@@ -1220,6 +1235,7 @@ void GameSys::fatUpdateFrame() {
seqRemoveGfx(seqItem->_sequenceId, seqItem->_id);
seqInsertGfx(i, gfxDuration);
}
+ _animationsDone = false;
}
} else {
_seqItems.remove_at(i);
diff --git a/engines/gnap/gamesys.h b/engines/gnap/gamesys.h
index 98014f1bac..b9752bde06 100644
--- a/engines/gnap/gamesys.h
+++ b/engines/gnap/gamesys.h
@@ -167,6 +167,8 @@ public:
int _backgroundImageValue4, _backgroundImageValue2;
int32 _gameSysClock, _lastUpdateClock;
+ bool _animationsDone;
+
Graphics::Surface *_backgroundSurface;
Graphics::Surface *_frontSurface;
diff --git a/engines/gnap/gnap.cpp b/engines/gnap/gnap.cpp
index ed2d25f3de..6a03bf8eb0 100644
--- a/engines/gnap/gnap.cpp
+++ b/engines/gnap/gnap.cpp
@@ -95,16 +95,45 @@ GnapEngine::GnapEngine(OSystem *syst, const ADGameDescription *gd) :
DebugMan.addDebugChannel(kDebugBasic, "basic", "Basic debug level");
Engine::syncSoundSettings();
+
+ _exe = nullptr;
+ _dat = nullptr;
+ _spriteCache = nullptr;
+ _soundCache = nullptr;
+ _sequenceCache = nullptr;
+ _gameSys = nullptr;
+ _soundMan = nullptr;
+ _debugger = nullptr;
+ _gnap = nullptr;
+ _plat = nullptr;
+ _font = nullptr;
_scene = nullptr;
_music = nullptr;
_tempThumbnail = nullptr;
+ _menuBackgroundSurface = nullptr;
+ _menuQuitQuerySprite = nullptr;
+ _largeSprite = nullptr;
+ _menuSaveLoadSprite = nullptr;
+ _menuSprite2 = nullptr;
+ _menuSprite1 = nullptr;
+ _spriteHandle = nullptr;
+ _cursorSprite = nullptr;
+ _pauseSprite = nullptr;
+ _backgroundSurface = nullptr;
_wasSavegameLoaded = false;
- for (int i = 0; i < kMaxTimers; ++i)
- _savedTimers[i] = _timers[i] = 0;
-
_isWaiting = false;
_sceneWaiting = false;
+ _menuDone = false;
+ _sceneDone = false;
+ _isLeavingScene = false;
+ _isStockDatLoaded = false;
+ _gameDone = false;
+ _isPaused = false;
+ _sceneSavegameLoaded = false;
+
+ for (int i = 0; i < kMaxTimers; ++i)
+ _savedTimers[i] = _timers[i] = 0;
_mousePos = Common::Point(0, 0);
_currGrabCursorX = _currGrabCursorY = 0;
@@ -112,15 +141,6 @@ GnapEngine::GnapEngine(OSystem *syst, const ADGameDescription *gd) :
_idleTimerIndex = -1;
_menuStatus = 0;
_menuSpritesIndex = -1;
- _menuDone = false;
- _menuBackgroundSurface = nullptr;
- _menuQuitQuerySprite = nullptr;
- _largeSprite = nullptr;
- _menuSaveLoadSprite = nullptr;
- _menuSprite2 = nullptr;
- _menuSprite1 = nullptr;
- _spriteHandle = nullptr;
- _cursorSprite = nullptr;
_savegameIndex = -1;
_gridMinX = 0;
_gridMinY = 0;
@@ -138,6 +158,32 @@ GnapEngine::GnapEngine(OSystem *syst, const ADGameDescription *gd) :
_savegameSprites[i] = nullptr;
for (int i = 0; i < 30; i++)
_menuInventorySprites[i] = nullptr;
+
+ _newSceneNum = 0;
+ _inventory = 0;
+ _gameFlags = 0;
+ _hotspotsCount = 0;
+ _sceneClickedHotspot = -1;
+ _newCursorValue = 0;
+ _cursorValue = 0;
+ _verbCursor = 0;
+ _cursorIndex = -1;
+ _leftClickMouseX = 0;
+ _leftClickMouseY = 0;
+ _grabCursorSprite = nullptr;
+ _grabCursorSpriteIndex = 0;
+ _newGrabCursorSpriteIndex = 0;
+ _fullScreenSprite = nullptr;
+ _fullScreenSpriteId = 0;
+ _deviceX1 = 0;
+ _deviceY1 = 0;
+ _soundTimerIndexA = 0;
+ _soundTimerIndexB = 0;
+ _soundTimerIndexC = 0;
+ _loadGameSlot = -1;
+ _lastUpdateClock = 0;
+ _prevSceneNum = -1;
+ _currentSceneNum = -1;
}
GnapEngine::~GnapEngine() {
@@ -148,7 +194,11 @@ GnapEngine::~GnapEngine() {
Common::Error GnapEngine::run() {
// Initialize the graphics mode to RGBA8888
+#if defined(SCUMM_BIG_ENDIAN)
+ Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24);
+#else
Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
+#endif
initGraphics(800, 600, true, &format);
// We do not support color conversion yet
@@ -161,7 +211,6 @@ Common::Error GnapEngine::run() {
_cursorIndex = -1;
_verbCursor = 1;
- _loadGameSlot = -1;
if (ConfMan.hasKey("save_slot"))
_loadGameSlot = ConfMan.getInt("save_slot");
@@ -235,11 +284,11 @@ void GnapEngine::updateEvents() {
_debugger->onFrame();
}
- _keyPressState[event.kbd.keycode] = 1;
- _keyDownState[event.kbd.keycode] = 1;
+ _keyPressState[event.kbd.keycode] = true;
+ _keyDownState[event.kbd.keycode] = true;
break;
case Common::EVENT_KEYUP:
- _keyDownState[event.kbd.keycode] = 0;
+ _keyDownState[event.kbd.keycode] = false;
break;
case Common::EVENT_MOUSEMOVE:
_mousePos = event.mouse;
@@ -476,9 +525,11 @@ void GnapEngine::updateMouseCursor() {
}
if (_isWaiting && ((_gnap->_actionStatus < 0 && _plat->_actionStatus < 0) || _sceneWaiting)) {
setCursor(kDisabledCursors[_verbCursor]);
+ showCursor();
_isWaiting = false;
} else if (!_isWaiting && (_gnap->_actionStatus >= 0 || _plat->_actionStatus >= 0) && !_sceneWaiting) {
setCursor(WAIT_CURSOR);
+ hideCursor();
_isWaiting = true;
}
}
@@ -627,19 +678,19 @@ void GnapEngine::removeDeviceIconActive() {
void GnapEngine::setDeviceHotspot(int hotspotIndex, int x1, int y1, int x2, int y2) {
_deviceX1 = x1;
- _deviceX2 = x2;
_deviceY1 = y1;
- _deviceY2 = y2;
+ int deviceX2 = x2;
+ int deviceY2 = y2;
if (x1 == -1)
_deviceX1 = 730;
if (x2 == -1)
- _deviceX2 = 780;
+ deviceX2 = 780;
if (y1 == -1)
_deviceY1 = 14;
if (y2 == -1)
- _deviceY2 = 79;
+ deviceY2 = 79;
- _hotspots[hotspotIndex]._rect = Common::Rect(_deviceX1, _deviceY1, _deviceX2, _deviceY2);
+ _hotspots[hotspotIndex]._rect = Common::Rect(_deviceX1, _deviceY1, deviceX2, deviceY2);
_hotspots[hotspotIndex]._flags = SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR;
}
@@ -959,21 +1010,19 @@ void GnapEngine::screenEffect(int dir, byte r, byte g, byte b) {
}
bool GnapEngine::isKeyStatus1(int key) {
- return _keyPressState[key] != 0;
+ return _keyPressState[key];
}
bool GnapEngine::isKeyStatus2(int key) {
- return _keyDownState[key] != 0;
+ return _keyDownState[key];
}
void GnapEngine::clearKeyStatus1(int key) {
- _keyPressState[key] = 0;
- _keyDownState[key] = 0;
+ _keyPressState[key] = false;
+ _keyDownState[key] = false;
}
void GnapEngine::clearAllKeyStatus1() {
- _keyStatus1[0] = 0;
- _keyStatus1[1] = 0;
memset(_keyPressState, 0, sizeof(_keyPressState));
memset(_keyDownState, 0, sizeof(_keyDownState));
}
diff --git a/engines/gnap/gnap.h b/engines/gnap/gnap.h
index 84c40e2969..dbefa31795 100644
--- a/engines/gnap/gnap.h
+++ b/engines/gnap/gnap.h
@@ -253,8 +253,8 @@ public:
int _lastUpdateClock;
bool _gameDone;
- byte _keyPressState[512];
- byte _keyDownState[512];
+ bool _keyPressState[512];
+ bool _keyDownState[512];
bool _isPaused;
Graphics::Surface *_pauseSprite;
@@ -263,8 +263,6 @@ public:
MouseButtonState _mouseButtonState;
MouseButtonState _mouseClickState;
- uint32 _keyStatus1[2];
-
bool _sceneSavegameLoaded, _wasSavegameLoaded;
Graphics::Surface *_backgroundSurface;
@@ -296,7 +294,7 @@ public:
Graphics::Surface *_fullScreenSprite;
int _fullScreenSpriteId;
- int _deviceX1, _deviceY1, _deviceX2, _deviceY2;
+ int _deviceX1, _deviceY1;
int _soundTimerIndexA;
int _soundTimerIndexB;
diff --git a/engines/gnap/resource.cpp b/engines/gnap/resource.cpp
index 8244213a7f..c6390082b1 100644
--- a/engines/gnap/resource.cpp
+++ b/engines/gnap/resource.cpp
@@ -101,6 +101,10 @@ SpriteResource::SpriteResource(byte *data, uint32 size) {
_colorsCount = READ_LE_UINT16(_data + 10);
_palette = (uint32 *)(_data + 12);
_pixels = _data + 12 + _colorsCount * 4;
+#if defined(SCUMM_BIG_ENDIAN)
+ for (uint16 c = 0; c < _colorsCount; ++c)
+ _palette[c] = SWAP_BYTES_32(_palette[c]);
+#endif
debugC(kDebugBasic, "SpriteResource() width: %d; height: %d; colorsCount: %d", _width, _height, _colorsCount);
}
diff --git a/engines/gnap/scenes/arcade.cpp b/engines/gnap/scenes/arcade.cpp
index 028a9006d0..db4999cb43 100644
--- a/engines/gnap/scenes/arcade.cpp
+++ b/engines/gnap/scenes/arcade.cpp
@@ -918,7 +918,6 @@ void Scene50::run() {
_timesPlayedModifier = _timesPlayed / 4;
_leftTongueRoundsWon = 0;
_rightTongueRoundsWon = 0;
- // initFont();
_leftTongueSequenceId = 186;
_rightTongueSequenceId = 194;
_rightTongueNextSequenceId = -1;
@@ -1884,6 +1883,7 @@ Scene52::Scene52(GnapEngine *vm) : Scene(vm) {
_aliensCount = 0;
_nextUfoSequenceId = -1;
_ufoSequenceId = -1;
+ _liveAlienRows = 0;
}
int Scene52::init() {
@@ -2639,8 +2639,8 @@ void Scene52::run() {
_shipMidY = _vm->_gameSys->getSpriteHeightById(15);
_shipPosX = (800 - _shipMidX) / 2;
_arcadeScreenBottom = 496;
- _arcadeScreenRight = 595 - _shipMidX;
- _arcadeScreenLeft = 210;
+ int arcadeScreenRight = 595 - _shipMidX;
+ int arcadeScreenLeft = 210;
_shipsLeft = 3;
_alienCounter = 0;
@@ -2682,10 +2682,10 @@ void Scene52::run() {
while (_vm->isKeyStatus2(Common::KEYCODE_RIGHT)) {
update();
if (_vm->_gameSys->getAnimationStatus(7) == 2) {
- if (_shipPosX < _arcadeScreenRight) {
+ if (_shipPosX < arcadeScreenRight) {
_shipPosX += 15;
- if (_shipPosX > _arcadeScreenRight)
- _shipPosX = _arcadeScreenRight;
+ if (_shipPosX > arcadeScreenRight)
+ _shipPosX = arcadeScreenRight;
_vm->_gameSys->setAnimation(_nextUfoSequenceId, 256, 7);
_vm->_gameSys->insertSequence(_nextUfoSequenceId, 256, _ufoSequenceId, 256, kSeqSyncWait, 0, _shipPosX, _arcadeScreenBottom);
_ufoSequenceId = _nextUfoSequenceId;
@@ -2699,10 +2699,10 @@ void Scene52::run() {
while (_vm->isKeyStatus2(Common::KEYCODE_LEFT)) {
update();
if (_vm->_gameSys->getAnimationStatus(7) == 2) {
- if (_shipPosX > _arcadeScreenLeft) {
+ if (_shipPosX > arcadeScreenLeft) {
_shipPosX -= 15;
- if (_shipPosX < _arcadeScreenLeft)
- _shipPosX = _arcadeScreenLeft;
+ if (_shipPosX < arcadeScreenLeft)
+ _shipPosX = arcadeScreenLeft;
_vm->_gameSys->setAnimation(_nextUfoSequenceId, 256, 7);
_vm->_gameSys->insertSequence(_nextUfoSequenceId, 256, _ufoSequenceId, 256, kSeqSyncWait, 0, _shipPosX, _arcadeScreenBottom);
_ufoSequenceId = _nextUfoSequenceId;
diff --git a/engines/gnap/scenes/arcade.h b/engines/gnap/scenes/arcade.h
index e472e00508..ab519ee00b 100644
--- a/engines/gnap/scenes/arcade.h
+++ b/engines/gnap/scenes/arcade.h
@@ -216,8 +216,6 @@ private:
int _liveAlienRows;
int _gameScore;
bool _soundToggle;
- int _arcadeScreenLeft;
- int _arcadeScreenRight;
int _arcadeScreenBottom;
int _shipsLeft;
int _shieldSpriteIds[3];
diff --git a/engines/gnap/scenes/group0.cpp b/engines/gnap/scenes/group0.cpp
index b2351b08ad..e76fd9bbd2 100644
--- a/engines/gnap/scenes/group0.cpp
+++ b/engines/gnap/scenes/group0.cpp
@@ -2908,7 +2908,7 @@ void Scene08::updateHotspots() {
_vm->setHotspot(kHS08Meat, 405, 450, 480, 485, SF_PLAT_CURSOR | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR);
_vm->setHotspot(kHS08Bone, 200, 405, 270, 465, SF_PLAT_CURSOR | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR);
_vm->setHotspot(kHS08Toy, 540, 430, 615, 465, SF_PLAT_CURSOR | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR);
- _vm->setHotspot(kHS08WalkArea1, 290, 340, -1, -1);
+ _vm->setHotspot(kHS08WalkArea1, 0, 0, 290, 340);
_vm->setHotspot(kHS08WalkArea2, 0, 0, 799, 420);
_vm->setDeviceHotspot(kHS08Device, -1, -1, -1, -1);
if (_vm->isFlag(kGFBarnPadlockOpen))
@@ -3379,7 +3379,7 @@ int Scene09::init() {
}
void Scene09::updateHotspots() {
- _vm->setHotspot(kHS09Platypus, 0, 200, 0, 0, SF_WALKABLE | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR);
+ _vm->setHotspot(kHS09Platypus, 0, 0, 0, 0, SF_WALKABLE | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR);
_vm->setHotspot(kHS09ExitKitchen, 280, 200, 380, 400, SF_EXIT_U_CURSOR);
_vm->setHotspot(kHS09ExitHouse, 790, 200, 799, 450, SF_EXIT_R_CURSOR | SF_WALKABLE);
_vm->setHotspot(kHS09Trash, 440, 310, 680, 420, SF_PLAT_CURSOR | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR);
diff --git a/engines/gnap/scenes/group1.cpp b/engines/gnap/scenes/group1.cpp
index bd152c7f39..e50e8cc959 100644
--- a/engines/gnap/scenes/group1.cpp
+++ b/engines/gnap/scenes/group1.cpp
@@ -2324,7 +2324,8 @@ int Scene17::init() {
}
void Scene17::updateHotspots() {
- _vm->setHotspot(kHS17Platypus, 1, 0, 0, 0, SF_WALKABLE | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR);
+ // The original is using (1, 0, 0, 0)
+ _vm->setHotspot(kHS17Platypus, 0, 0, 0, 0, SF_WALKABLE | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR);
_vm->setHotspot(kHS17Phone1, 61, 280, 97, 322, SF_PLAT_CURSOR | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR, 1, 7);
_vm->setHotspot(kHS17Phone2, 80, 204, 178, 468, SF_PLAT_CURSOR | SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR, 1, 7);
_vm->setHotspot(kHS17ExitGrubCity, 196, 207, 280, 304, SF_EXIT_U_CURSOR, 3, 5);
diff --git a/engines/gnap/scenes/group2.cpp b/engines/gnap/scenes/group2.cpp
index 522a3f4337..1db3144c2d 100644
--- a/engines/gnap/scenes/group2.cpp
+++ b/engines/gnap/scenes/group2.cpp
@@ -1021,6 +1021,7 @@ Scene22::Scene22(GnapEngine *vm) : Scene(vm) {
_caughtBefore = false;
_cashierCtr = 3;
_nextCashierSequenceId = -1;
+ _currCashierSequenceId = -1;
}
int Scene22::init() {
diff --git a/engines/gnap/scenes/intro.cpp b/engines/gnap/scenes/intro.cpp
index b4ba2f5201..5bb2239bab 100644
--- a/engines/gnap/scenes/intro.cpp
+++ b/engines/gnap/scenes/intro.cpp
@@ -88,12 +88,15 @@ void SceneIntro::run() {
frame1->free();
delete frame1;
} else {
+ Graphics::Surface *frame1 = frame->convertTo(_vm->_system->getScreenFormat());
+
// The intro AVI is played upside down, it's the only video played in the English version
- for (uint16 y = 0; y < frame->h / 2; y++) {
- uint32 *ptrFrom = (uint32 *)frame->getBasePtr(0, y);
- uint32 *ptrTo = (uint32 *)frame->getBasePtr(0, frame->h - y - 1);
- for (uint16 x = 0; x < frame->w; x++) {
- uint32 t = *ptrFrom;
+ for (uint16 y = 0; y < frame1->h / 2; y++) {
+ uint32 *ptrFrom = (uint32 *)frame1->getBasePtr(0, y);
+ uint32 *ptrTo = (uint32 *)frame1->getBasePtr(0, frame1->h - y - 1);
+ // in this else branch, bytesPerPixel equals 4
+ for (uint16 x = 0; x < frame1->pitch / 4; x++) {
+ uint32 t = *ptrFrom;
*ptrFrom = *ptrTo;
*ptrTo = t;
ptrFrom++;
@@ -101,7 +104,6 @@ void SceneIntro::run() {
}
}
- Graphics::Surface *frame1 = frame->convertTo(_vm->_system->getScreenFormat());
_vm->_system->copyRectToScreen(frame1->getPixels(), frame1->pitch, vidPosX, vidPosY, frame1->w, frame1->h);
frame1->free();
delete frame1;
diff --git a/engines/gnap/sound.cpp b/engines/gnap/sound.cpp
index 75cfb5555c..b09cc7cafe 100644
--- a/engines/gnap/sound.cpp
+++ b/engines/gnap/sound.cpp
@@ -26,8 +26,7 @@
namespace Gnap {
-SoundMan::SoundMan(GnapEngine *vm)
- : _vm(vm) {
+SoundMan::SoundMan(GnapEngine *vm) : _vm(vm) {
}
SoundMan::~SoundMan() {
@@ -49,11 +48,12 @@ void SoundMan::playSound(int resourceId, bool looping) {
void SoundMan::stopSound(int resourceId) {
const int index = find(resourceId);
- if (index >= 0) {
- _vm->_soundCache->release(_items[index]._resourceId);
- _vm->_mixer->stopHandle(_items[index]._handle);
- _items.remove_at(index);
- }
+ if (index < 0)
+ return;
+
+ _vm->_soundCache->release(_items[index]._resourceId);
+ _vm->_mixer->stopHandle(_items[index]._handle);
+ _items.remove_at(index);
}
void SoundMan::setSoundVolume(int resourceId, int volume) {
@@ -61,13 +61,19 @@ void SoundMan::setSoundVolume(int resourceId, int volume) {
return;
const int index = find(resourceId);
+ if (index < 0)
+ return;
+
int realVol = volume * 2.55;
_vm->_mixer->setChannelVolume(_items[index]._handle, realVol);
}
bool SoundMan::isSoundPlaying(int resourceId) {
const int index = find(resourceId);
- return index >= 0 && _vm->_mixer->isSoundHandleActive(_items[index]._handle);
+ if (index < 0)
+ return false;
+
+ return _vm->_mixer->isSoundHandleActive(_items[index]._handle);
}
void SoundMan::stopAll() {
diff --git a/engines/hopkins/detection.cpp b/engines/hopkins/detection.cpp
index cc1e84f5f8..cfdbf8030c 100644
--- a/engines/hopkins/detection.cpp
+++ b/engines/hopkins/detection.cpp
@@ -111,7 +111,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "Hopkins FBI (c)1997-2003 MP Entertainment";
+ return "Hopkins FBI (C)1997-2003 MP Entertainment";
}
virtual bool hasFeature(MetaEngineFeature f) const;
diff --git a/engines/lab/detection.cpp b/engines/lab/detection.cpp
index 1fd3ca8944..30890b5acf 100644
--- a/engines/lab/detection.cpp
+++ b/engines/lab/detection.cpp
@@ -127,7 +127,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "Labyrinth of Time (c) 2004 The Wyrmkeep Entertainment Co. and Terra Nova Development";
+ return "Labyrinth of Time (C) 2004 The Wyrmkeep Entertainment Co. and Terra Nova Development";
}
virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
diff --git a/engines/mads/detection.cpp b/engines/mads/detection.cpp
index b3ba60b6d0..4736503a38 100644
--- a/engines/mads/detection.cpp
+++ b/engines/mads/detection.cpp
@@ -149,7 +149,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "MADS (c)";
+ return "MADS (C)";
}
virtual bool hasFeature(MetaEngineFeature f) const;
diff --git a/engines/mohawk/bitmap.cpp b/engines/mohawk/bitmap.cpp
index 6435daf46f..d8c6d6aacd 100644
--- a/engines/mohawk/bitmap.cpp
+++ b/engines/mohawk/bitmap.cpp
@@ -53,6 +53,16 @@ MohawkBitmap::MohawkBitmap() {
_drawTable = drawTable;
_drawTableSize = ARRAYSIZE(drawTable);
+
+ _header.width = 0;
+ _header.height = 0;
+ _header.bytesPerRow = 0;
+ _header.format = 0;
+ _header.colorTable.colorCount = 0;
+ _header.colorTable.palette = nullptr;
+ _header.colorTable.rgbBits = 0;
+ _header.colorTable.tableSize = 0;
+ _data = nullptr;
}
MohawkBitmap::~MohawkBitmap() {
diff --git a/engines/mohawk/cstime.h b/engines/mohawk/cstime.h
index f95222d3a1..bfb7daf945 100644
--- a/engines/mohawk/cstime.h
+++ b/engines/mohawk/cstime.h
@@ -111,7 +111,7 @@ enum {
};
struct CSTimeEvent {
- CSTimeEvent() { }
+ CSTimeEvent() : type(0), param1(0), param2(0) { }
CSTimeEvent(uint16 t, uint16 p1, uint16 p2) : type(t), param1(p1), param2(p2) { }
uint16 type;
diff --git a/engines/mohawk/cstime_game.cpp b/engines/mohawk/cstime_game.cpp
index 8eced701c3..c939d8bc24 100644
--- a/engines/mohawk/cstime_game.cpp
+++ b/engines/mohawk/cstime_game.cpp
@@ -94,6 +94,11 @@ CSTimeChar::CSTimeChar(MohawkEngine_CSTime *vm, CSTimeScene *scene, uint id) : _
_lastTime2 = 0;
_lastTime3 = 0;
+ _unknown1 = _unknown2 = _unknown3 = 0;
+ _enabled = false;
+ _nextCue = 0;
+ _waveStatus = 0;
+
_playingWaveId = 0;
}
diff --git a/engines/mohawk/cstime_ui.cpp b/engines/mohawk/cstime_ui.cpp
index f3fe27a966..59be95adf6 100644
--- a/engines/mohawk/cstime_ui.cpp
+++ b/engines/mohawk/cstime_ui.cpp
@@ -79,6 +79,8 @@ CSTimeInterface::CSTimeInterface(MohawkEngine_CSTime *vm) : _vm(vm) {
_rolloverTextFeature = NULL;
_bubbleTextFeature = NULL;
+ _draggedItem = 0;
+
_mouseWasInScene = false;
_state = kCSTimeInterfaceStateNormal;
@@ -1034,6 +1036,8 @@ CSTimeInventoryDisplay::CSTimeInventoryDisplay(MohawkEngine_CSTime *vm, Common::
_cuffsState = false;
_cuffsShape = 10;
+ _draggedItem = 0;
+
_invRect = baseRect;
for (uint i = 0; i < MAX_DISPLAYED_ITEMS; i++) {
diff --git a/engines/mohawk/cstime_view.cpp b/engines/mohawk/cstime_view.cpp
index 7879175bb0..8727560094 100644
--- a/engines/mohawk/cstime_view.cpp
+++ b/engines/mohawk/cstime_view.cpp
@@ -243,7 +243,7 @@ void CSTimeModule::defaultMoveProc(Feature *feature) {
if ((feature->_flags & kFeatureNewDisable) || (feature->_flags & kFeatureNewDisableOnReset)) {
feature->_data.enabled = 0;
}
- feature->_dirty = 1;
+ feature->_dirty = true;
if (feature->_flags & kFeatureInternalRegion) {
// TODO: create region [+140] (if not already done)
}
@@ -257,7 +257,7 @@ void CSTimeModule::defaultMoveProc(Feature *feature) {
// TODO: or clip with bounds
}
}
- feature->_dirty = 1;
+ feature->_dirty = true;
if (feature->_flags & kFeatureNewInternalTiming) {
feature->_nextTime += feature->_delayTime;
} else {
@@ -277,7 +277,7 @@ void CSTimeModule::defaultMoveProc(Feature *feature) {
}
feature->_data.currOffset = 26;
- feature->_done = 0;
+ feature->_done = false;
}
if (feature->_flags & kFeatureNewDisable)
feature->_data.enabled = 0;
@@ -307,7 +307,7 @@ void CSTimeModule::defaultMoveProc(Feature *feature) {
}
case 0:
// TODO: set ptr +176 to 1
- feature->_done = 1;
+ feature->_done = true;
if (feature->_doneProc) {
(this->*(feature->_doneProc))(feature); // TODO: with -1
}
diff --git a/engines/mohawk/cursors.cpp b/engines/mohawk/cursors.cpp
index 4b66829e6a..72eebca917 100644
--- a/engines/mohawk/cursors.cpp
+++ b/engines/mohawk/cursors.cpp
@@ -34,8 +34,8 @@
#include "graphics/wincursor.h"
#ifdef ENABLE_MYST
-#include "mohawk/bitmap.h"
#include "mohawk/myst.h"
+#include "mohawk/myst_graphics.h"
#endif
namespace Mohawk {
@@ -86,11 +86,9 @@ void DefaultCursorManager::setCursor(uint16 id) {
#ifdef ENABLE_MYST
MystCursorManager::MystCursorManager(MohawkEngine_Myst *vm) : _vm(vm) {
- _bmpDecoder = new MystBitmap();
}
MystCursorManager::~MystCursorManager() {
- delete _bmpDecoder;
}
void MystCursorManager::showCursor() {
@@ -111,17 +109,18 @@ void MystCursorManager::setCursor(uint16 id) {
return;
}
- // Both Myst and Myst ME use the "MystBitmap" format for cursor images.
- MohawkSurface *mhkSurface = _bmpDecoder->decodeImage(_vm->getResource(ID_WDIB, id));
- Graphics::Surface *surface = mhkSurface->getSurface();
Common::SeekableReadStream *clrcStream = _vm->getResource(ID_CLRC, id);
uint16 hotspotX = clrcStream->readUint16LE();
uint16 hotspotY = clrcStream->readUint16LE();
delete clrcStream;
+ // Both Myst and Myst ME use the "MystBitmap" format for cursor images.
+ MohawkSurface *mhkSurface = _vm->_gfx->findImage(id);
+ Graphics::Surface *surface = mhkSurface->getSurface();
+
// Myst ME stores some cursors as 24bpp images instead of 8bpp
if (surface->format.bytesPerPixel == 1) {
- CursorMan.replaceCursor(surface->getPixels(), surface->w, surface->h, hotspotX, hotspotY, 0);
+ CursorMan.replaceCursor(surface->getPixels(), surface->w, surface->h, hotspotX, hotspotY, 255);
// We're using the screen palette for the original game, but we need
// to use this for any 8bpp cursor in ME.
@@ -133,7 +132,6 @@ void MystCursorManager::setCursor(uint16 id) {
}
_vm->_needsUpdate = true;
- delete mhkSurface;
}
void MystCursorManager::setDefaultCursor() {
diff --git a/engines/mohawk/cursors.h b/engines/mohawk/cursors.h
index c41b5c273e..742ae30107 100644
--- a/engines/mohawk/cursors.h
+++ b/engines/mohawk/cursors.h
@@ -102,7 +102,6 @@ enum {
};
class MohawkEngine_Myst;
-class MystBitmap;
// The cursor manager for Myst
// Uses WDIB + CLRC resources
@@ -119,7 +118,6 @@ public:
private:
MohawkEngine_Myst *_vm;
- MystBitmap *_bmpDecoder;
};
#endif // ENABLE_MYST
diff --git a/engines/mohawk/graphics.h b/engines/mohawk/graphics.h
index 5f9b523e9a..f9fdeea15f 100644
--- a/engines/mohawk/graphics.h
+++ b/engines/mohawk/graphics.h
@@ -74,6 +74,10 @@ public:
// Free all surfaces in the cache
void clearCache();
+ // findImage will search the cache to find the image.
+ // If not found, it will call decodeImage to get a new one.
+ MohawkSurface *findImage(uint16 id);
+
void preloadImage(uint16 image);
virtual void setPalette(uint16 id);
void copyAnimImageToScreen(uint16 image, int left = 0, int top = 0);
@@ -85,10 +89,6 @@ public:
protected:
void copyAnimImageSectionToScreen(MohawkSurface *image, Common::Rect src, Common::Rect dest);
- // findImage will search the cache to find the image.
- // If not found, it will call decodeImage to get a new one.
- MohawkSurface *findImage(uint16 id);
-
// decodeImage will always return a new image.
virtual MohawkSurface *decodeImage(uint16 id) = 0;
virtual Common::Array<MohawkSurface *> decodeImages(uint16 id);
diff --git a/engines/mohawk/myst_graphics.cpp b/engines/mohawk/myst_graphics.cpp
index 5db9697a78..427fba4d22 100644
--- a/engines/mohawk/myst_graphics.cpp
+++ b/engines/mohawk/myst_graphics.cpp
@@ -47,8 +47,7 @@ MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm) {
} else {
// Paletted
initGraphics(_viewport.width(), _viewport.height(), true);
- setBasePalette();
- setPaletteToScreen();
+ clearScreenPalette();
}
_pixelFormat = _vm->_system->getScreenFormat();
@@ -86,7 +85,7 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) {
bool isPict = false;
- if (_vm->getFeatures() & GF_ME) {
+ if ((_vm->getFeatures() & GF_ME) && dataStream->size() > 512 + 10 + 4) {
// Here we detect whether it's really a PICT or a WDIB. Since a MystBitmap
// would be compressed, there's no way to detect for the BM without a hack.
// So, we search for the PICT version opcode for detection.
@@ -109,8 +108,11 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) {
} else {
mhkSurface = _bmpDecoder->decodeImage(dataStream);
- if (_vm->getFeatures() & GF_ME)
+ if (_vm->getFeatures() & GF_ME) {
mhkSurface->convertToTrueColor();
+ } else {
+ remapSurfaceToSystemPalette(mhkSurface);
+ }
}
assert(mhkSurface);
@@ -204,7 +206,7 @@ void MystGraphics::copyImageSectionToBackBuffer(uint16 image, Common::Rect src,
if (!(_vm->getFeatures() & GF_ME)) {
// Make sure the palette is set
assert(mhkSurface->getPalette());
- memcpy(_palette + 10 * 3, mhkSurface->getPalette() + 10 * 3, (256 - 10 * 2) * 3);
+ memcpy(_palette, mhkSurface->getPalette(), 256 * 3);
setPaletteToScreen();
}
}
@@ -703,10 +705,10 @@ void MystGraphics::clearScreenPalette() {
_vm->_system->getPaletteManager()->setPalette(palette, 0, 256);
}
-void MystGraphics::setBasePalette() {
+void MystGraphics::remapSurfaceToSystemPalette(MohawkSurface *mhkSurface) {
// Entries [0, 9] of the palette
static const byte lowPalette[] = {
- 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00,
0x80, 0x00, 0x00,
0x00, 0x80, 0x00,
0x80, 0x80, 0x00,
@@ -729,15 +731,68 @@ void MystGraphics::setBasePalette() {
0x00, 0x00, 0xFF,
0xFF, 0x00, 0xFF,
0x00, 0xFF, 0xFF,
- 0x00, 0x00, 0x00
+ 0xFF, 0xFF, 0xFF
};
- // Note that 0 and 255 are different from normal Windows.
- // Myst seems to hack that to white, resp. black (probably for Mac compat).
+ byte *originalPalette = mhkSurface->getPalette();
+
+ // The target palette is made of the Windows reserved palette, and colors 10 to 245
+ // of the bitmap palette. Entries 0 to 9 and 246 to 255 of the bitmap palette are
+ // discarded.
+ byte targetPalette[256 * 3];
+ memcpy(targetPalette, lowPalette, sizeof(lowPalette));
+ memcpy(targetPalette + sizeof(lowPalette), originalPalette + sizeof(lowPalette), sizeof(_palette) - sizeof(lowPalette) - sizeof(highPalette));
+ memcpy(targetPalette + sizeof(_palette) - sizeof(highPalette), highPalette, sizeof(highPalette));
+
+ // Remap the discarded entries from the bitmap palette using the target palette.
+ byte lowColorMap[ARRAYSIZE(lowPalette) / 3];
+ byte highColorMap[ARRAYSIZE(highPalette) / 3];
+
+ for (uint i = 0; i < ARRAYSIZE(lowColorMap); i++) {
+ uint colorIndex = 3 * i;
+ byte red = originalPalette[colorIndex + 0];
+ byte green = originalPalette[colorIndex + 1];
+ byte blue = originalPalette[colorIndex + 2];
+
+ lowColorMap[i] = getColorIndex(targetPalette, red, green, blue);
+ }
+
+ for (uint i = 0; i < ARRAYSIZE(highColorMap); i++) {
+ uint colorIndex = 3 * (i + 246);
+ byte red = originalPalette[colorIndex + 0];
+ byte green = originalPalette[colorIndex + 1];
+ byte blue = originalPalette[colorIndex + 2];
+
+ highColorMap[i] = getColorIndex(targetPalette, red, green, blue);
+ }
+
+ // Replace the original palette with the target palette
+ memcpy(originalPalette, targetPalette, sizeof(targetPalette));
+
+ // Remap the pixel data to the target palette
+ Graphics::Surface *surface = mhkSurface->getSurface();
+ byte *pixels = (byte *) surface->getPixels();
+
+ for (int i = 0; i < surface->w * surface->h; i++) {
+ if (pixels[i] < ARRAYSIZE(lowColorMap)) {
+ pixels[i] = lowColorMap[pixels[i]];
+ } else if (pixels[i] >= 246) {
+ pixels[i] = highColorMap[pixels[i] - 246];
+ }
+ }
+}
+
+byte MystGraphics::getColorIndex(const byte *palette, byte red, byte green, byte blue) {
+ for (uint i = 0; i < 256; i++) {
+ if (palette[(3 * i) + 0] == red && palette[(3 * i) + 1] == green && palette[(3 * i) + 2] == blue) {
+ return i;
+ }
+ }
- memcpy(_palette, lowPalette, sizeof(lowPalette));
- memset(_palette + sizeof(lowPalette), 0, sizeof(_palette) - sizeof(lowPalette) - sizeof(highPalette));
- memcpy(_palette + sizeof(_palette) - sizeof(highPalette), highPalette, sizeof(highPalette));
+ // GDI actually chooses the nearest color if no exact match is found,
+ // but this should not happen in Myst
+ debug(1, "Color (%d, %d, %d) not in target palette", red, green, blue);
+ return 0;
}
void MystGraphics::setPaletteToScreen() {
diff --git a/engines/mohawk/myst_graphics.h b/engines/mohawk/myst_graphics.h
index 93e388cb83..cd09a53a3a 100644
--- a/engines/mohawk/myst_graphics.h
+++ b/engines/mohawk/myst_graphics.h
@@ -56,7 +56,6 @@ public:
void fadeFromBlack();
void clearScreenPalette();
- void setBasePalette();
void setPaletteToScreen();
const byte *getPalette() const { return _palette; }
@@ -86,6 +85,9 @@ private:
void transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay);
void transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps);
void transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps);
+
+ void remapSurfaceToSystemPalette(MohawkSurface *mhkSurface);
+ byte getColorIndex(const byte *palette, byte red, byte green, byte blue);
};
} // End of namespace Mohawk
diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp
index 178139ab76..0f764aeded 100644
--- a/engines/mohawk/riven.cpp
+++ b/engines/mohawk/riven.cpp
@@ -55,9 +55,19 @@ MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescriptio
_gameOver = false;
_activatedSLST = false;
_ignoreNextMouseUp = false;
- _extrasFile = 0;
+ _extrasFile = nullptr;
_curStack = kStackUnknown;
- _hotspots = 0;
+ _hotspots = nullptr;
+ _gfx = nullptr;
+ _externalScriptHandler = nullptr;
+ _rnd = nullptr;
+ _scriptMan = nullptr;
+ _console = nullptr;
+ _saveLoad = nullptr;
+ _optionsDialog = nullptr;
+ _curCard = 0;
+ _hotspotCount = 0;
+ _curHotspot = -1;
removeTimer();
// NOTE: We can never really support CD swapping. All of the music files
diff --git a/engines/mohawk/riven_graphics.cpp b/engines/mohawk/riven_graphics.cpp
index b44fbb828e..db22dde22d 100644
--- a/engines/mohawk/riven_graphics.cpp
+++ b/engines/mohawk/riven_graphics.cpp
@@ -51,6 +51,8 @@ RivenGraphics::RivenGraphics(MohawkEngine_Riven* vm) : GraphicsManager(), _vm(vm
_creditsImage = 302;
_creditsPos = 0;
+
+ _transitionSpeed = 0;
}
RivenGraphics::~RivenGraphics() {
diff --git a/engines/mohawk/view.cpp b/engines/mohawk/view.cpp
index 1aaf32ea78..70d20270a5 100644
--- a/engines/mohawk/view.cpp
+++ b/engines/mohawk/view.cpp
@@ -37,6 +37,23 @@ Module::~Module() {
}
Feature::Feature(View *view) : _view(view) {
+ _next = _prev = nullptr;
+ _drawProc = nullptr;
+ _moveProc = nullptr;
+ _doneProc = nullptr;
+ _frameProc = nullptr;
+ _timeProc = nullptr;
+ _region = 0;
+ _id = 0;
+ _scrbId = 0;
+ _storedScrbId = 0;
+ _flags = 0;
+ _nextTime = 0;
+ _delayTime = 0;
+ _dirty = false;
+ _needsReset = false;
+ _justReset = false;
+ _done = false;
}
Feature::~Feature() {
@@ -75,11 +92,10 @@ void Feature::setNodeDefaults(Feature *prev, Feature *next) {
_flags = 0;
- _dirty = 1;
- _needsReset = 1;
- _justReset = 0; // old
- _notifyDone = 0;
- _done = 0; // new
+ _dirty = true;
+ _needsReset = true;
+ _justReset = false; // old
+ _done = false; // new
_nextTime = 0;
_delayTime = 0;
@@ -107,11 +123,11 @@ void Feature::resetFeatureScript(uint16 enabled, uint16 scrbId) {
resetFrame();
_nextTime = 0; // New feature code uses _view->_lastIdleTime, but should be equivalent.
_data.enabled = enabled;
- _dirty = 1;
+ _dirty = true;
finishResetFeatureScript();
- _needsReset = 0;
+ _needsReset = false;
if (_region) {
// TODO: mark _region as dirty
@@ -123,7 +139,6 @@ void Feature::resetFeatureScript(uint16 enabled, uint16 scrbId) {
void Feature::resetFeature(bool notifyDone, Module::FeatureProc doneProc, uint16 scrbId) {
resetFeatureScript(1, scrbId);
_doneProc = doneProc;
- _notifyDone = notifyDone;
}
void Feature::hide(bool clip) {
@@ -159,7 +174,7 @@ void Feature::moveAndUpdate(Common::Point newPos) {
return;
_nextTime = 0;
- _dirty = 1;
+ _dirty = true;
// TODO: mark _data.bounds as dirty
if (_data.bitmapIds[0])
@@ -228,7 +243,7 @@ void OldFeature::resetScript() {
}
void OldFeature::finishResetFeatureScript() {
- _justReset = 1;
+ _justReset = true;
if (_flags & kFeatureOldAdjustByPos) {
Common::SeekableReadStream *ourSCRB = _view->getSCRB(_data.scrbIndex, _scrbId);
@@ -240,6 +255,13 @@ void OldFeature::finishResetFeatureScript() {
}
NewFeature::NewFeature(View *view) : Feature(view) {
+ _unknown168 = 0;
+ _pickupProc = nullptr;
+ _dropProc = nullptr;
+ _dragMoveProc = nullptr;
+ _oldMoveProc = nullptr;
+ _dragFlags = 0;
+ _oldFlags = 0;
}
NewFeature::~NewFeature() {
@@ -307,7 +329,7 @@ void NewFeature::resetScript() {
}
void NewFeature::finishResetFeatureScript() {
- _done = 0;
+ _done = false;
}
View::View(MohawkEngine *vm) : _vm(vm) {
@@ -319,6 +341,12 @@ View::View(MohawkEngine *vm) : _vm(vm) {
_compoundSHAPGroups[i] = 0;
}
_numSCRBGroups = 0;
+
+ _lastIdleTime = 0;
+ _needsUpdate = false;
+ _gfx = nullptr;
+ _rootNode = nullptr;
+ _cursorNode = nullptr;
}
View::~View() {
@@ -347,7 +375,7 @@ void View::idleView() {
}
if (node->_drawProc)
(_currentModule->*(node->_drawProc))(node);
- node->_dirty = 0;
+ node->_dirty = false;
}
if (_needsUpdate) {
diff --git a/engines/mohawk/view.h b/engines/mohawk/view.h
index 47853f056f..463715b765 100644
--- a/engines/mohawk/view.h
+++ b/engines/mohawk/view.h
@@ -138,11 +138,10 @@ public:
uint32 _flags;
uint32 _nextTime;
uint32 _delayTime;
- uint16 _dirty; // byte in old
- byte _needsReset;
- byte _justReset; // old
- byte _notifyDone; // old
- byte _done; // new
+ bool _dirty; // byte in old
+ bool _needsReset;
+ bool _justReset; // old
+ bool _done; // new
FeatureData _data;
@@ -192,13 +191,6 @@ protected:
void finishResetFeatureScript();
};
-#define NUM_SYNC_CHANNELS 17
-struct SyncChannel {
- uint16 masterId;
- byte state;
- bool alternate;
-};
-
class View {
public:
View(MohawkEngine *vm);
@@ -234,7 +226,6 @@ public:
void sortView();
uint32 _lastIdleTime;
- SyncChannel _syncChannels[NUM_SYNC_CHANNELS];
virtual uint32 getTime() = 0;
diff --git a/engines/mortevielle/saveload.cpp b/engines/mortevielle/saveload.cpp
index 9f46940e1f..3065d6c551 100644
--- a/engines/mortevielle/saveload.cpp
+++ b/engines/mortevielle/saveload.cpp
@@ -237,7 +237,7 @@ bool SavegameManager::readSavegameHeader(Common::InSaveFile *in, SavegameHeader
SaveStateList SavegameManager::listSaves(const Common::String &target) {
Common::String pattern = target;
- pattern += ".???";
+ pattern += ".###";
Common::StringArray files = g_system->getSavefileManager()->listSavefiles(pattern);
sort(files.begin(), files.end()); // Sort (hopefully ensuring we are sorted numerically..)
diff --git a/engines/neverhood/detection.cpp b/engines/neverhood/detection.cpp
index cfddc2d6b4..0f409a6435 100644
--- a/engines/neverhood/detection.cpp
+++ b/engines/neverhood/detection.cpp
@@ -114,6 +114,23 @@ static const NeverhoodGameDescription gameDescriptions[] = {
},
{
+ // Neverhood earlier English demo version
+ {
+ "neverhood",
+ "Demo",
+ AD_ENTRY1s("nevdemo.blb", "9cbc33bc8ebacacfc8071f3e26a9c85f", 22357020),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DEMO,
+ GUIO1(GUIO_NONE)
+ },
+ 0,
+ 0,
+ 0,
+ 0,
+ },
+
+ {
// Neverhood Russian version. Dyadyushka Risech
{
"neverhood",
diff --git a/engines/parallaction/dialogue.cpp b/engines/parallaction/dialogue.cpp
index 771715b95e..4dbedc8dbe 100644
--- a/engines/parallaction/dialogue.cpp
+++ b/engines/parallaction/dialogue.cpp
@@ -153,6 +153,8 @@ DialogueManager::DialogueManager(Parallaction *vm, ZonePtr z) : _vm(vm), _z(z) {
_downKey = 0;
_mouseButtons = 0;
+
+ _state = DIALOGUE_START;
}
void DialogueManager::start() {
diff --git a/engines/parallaction/font.cpp b/engines/parallaction/font.cpp
index f1c3b89ae8..0476b15971 100644
--- a/engines/parallaction/font.cpp
+++ b/engines/parallaction/font.cpp
@@ -70,6 +70,8 @@ public:
_data = (byte *)malloc(size);
stream.read(_data, size);
+ _cp = 0;
+ _bufPitch = 0;
}
~BraFont() {
diff --git a/engines/parallaction/input.cpp b/engines/parallaction/input.cpp
index 2cd85d7f1c..c62e7479d3 100644
--- a/engines/parallaction/input.cpp
+++ b/engines/parallaction/input.cpp
@@ -77,6 +77,8 @@ Input::Input(Parallaction *vm) : _vm(vm) {
_dougCursor = 0;
_donnaCursor = 0;
_comboArrow = 0;
+ _mouseArrow = 0;
+
initCursors();
}
diff --git a/engines/parallaction/parallaction_ns.cpp b/engines/parallaction/parallaction_ns.cpp
index 5fd6d87985..ccf7130eb8 100644
--- a/engines/parallaction/parallaction_ns.cpp
+++ b/engines/parallaction/parallaction_ns.cpp
@@ -352,8 +352,8 @@ void Parallaction_ns::changeLocation() {
}
char location[200];
- strcpy(location, _newLocationName.c_str());
- strcpy(_location._name, _newLocationName.c_str());
+ Common::strlcpy(location, _newLocationName.c_str(), 200);
+ Common::strlcpy(_location._name, _newLocationName.c_str(), 100);
debugC(1, kDebugExec, "changeLocation(%s)", location);
diff --git a/engines/pegasus/input.h b/engines/pegasus/input.h
index ba6f11dba0..ac5b149413 100644
--- a/engines/pegasus/input.h
+++ b/engines/pegasus/input.h
@@ -451,7 +451,7 @@ protected:
class Tracker : public InputHandler {
public:
- Tracker() : InputHandler(0) {}
+ Tracker() : InputHandler(0), _savedHandler(nullptr) {}
virtual ~Tracker() {}
virtual void handleInput(const Input &, const Hotspot *);
diff --git a/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp
index 688fb7860d..9a2cf8c4bb 100644
--- a/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp
+++ b/engines/pegasus/neighborhood/caldoria/caldoria4dsystem.cpp
@@ -104,6 +104,14 @@ static const ExtraID s_shutDownExtras[3][3] = {
Caldoria4DSystem::Caldoria4DSystem(Neighborhood *owner) : GameInteraction(kCaldoria4DInteractionID, owner),
_4DSpritesMovie(kCaldoria4DSpritesID) {
+ _4DSpritesScale = 0;
+ _whichMenu = k4DVideoMenu;
+ _videoChoice = k4DIslandChoice;
+ _audioChoice = k4DRockChoice;
+ _neighborhoodNotification = nullptr;
+ _loopStart = 0;
+ _clickedHotspotID = kNoHotSpotID;
+
g_AIArea->lockAIOut();
}
diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp b/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp
index 2ae990d775..54c8b514d1 100644
--- a/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp
+++ b/engines/pegasus/neighborhood/caldoria/caldoriamessages.cpp
@@ -35,6 +35,8 @@ static const NotificationFlags kMessageDoneFlag = 1;
CaldoriaMessages::CaldoriaMessages(Neighborhood *owner, const NotificationID id, NotificationManager *manager) :
GameInteraction(kCaldoriaMessagesInteractionID, owner), Notification(id, manager), _messageMovie(kCaldoriaMessagesID) {
+ _neighborhoodNotification = nullptr;
+ _messageNumber = 0;
}
void CaldoriaMessages::openInteraction() {
diff --git a/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp b/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp
index ff4d1811d0..e0e9e2f22d 100644
--- a/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp
+++ b/engines/pegasus/neighborhood/caldoria/caldoriamirror.cpp
@@ -32,6 +32,7 @@
namespace Pegasus {
CaldoriaMirror::CaldoriaMirror(Neighborhood *owner) : GameInteraction(kCaldoriaMirrorInteractionID, owner) {
+ _neighborhoodNotification = nullptr;
}
void CaldoriaMirror::openInteraction() {
diff --git a/engines/pegasus/neighborhood/mars/mars.cpp b/engines/pegasus/neighborhood/mars/mars.cpp
index 7c4a8a98ba..0d5edd85ba 100644
--- a/engines/pegasus/neighborhood/mars/mars.cpp
+++ b/engines/pegasus/neighborhood/mars/mars.cpp
@@ -100,6 +100,14 @@ Mars::Mars(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextH
_planetMovie(kNoDisplayElement), _junk(kNoDisplayElement), _energyChoiceSpot(kShuttleEnergySpotID),
_gravitonChoiceSpot(kShuttleGravitonSpotID), _tractorChoiceSpot(kShuttleTractorSpotID),
_shuttleViewSpot(kShuttleViewSpotID), _shuttleTransportSpot(kShuttleTransportSpotID) {
+
+ _reactorStage = 0;
+ _nextGuess = 0;
+ _attackingItem = nullptr;
+ _marsEvent.mars = nullptr;
+ _marsEvent.event = kMarsLaunchTubeReached;
+ _weaponSelection = kNoWeapon;
+
_noAirFuse.setFunctor(new Common::Functor0Mem<void, Mars>(this, &Mars::airStageExpired));
setIsItemTaken(kMarsCard);
setIsItemTaken(kAirMask);
diff --git a/engines/pegasus/neighborhood/norad/alpha/noradalpha.cpp b/engines/pegasus/neighborhood/norad/alpha/noradalpha.cpp
index 6a24113465..5c2af3eec2 100644
--- a/engines/pegasus/neighborhood/norad/alpha/noradalpha.cpp
+++ b/engines/pegasus/neighborhood/norad/alpha/noradalpha.cpp
@@ -93,6 +93,7 @@ NoradAlpha::NoradAlpha(InputHandler *nextHandler, PegasusEngine *owner) : Norad(
_subControlRoom = kNorad22West;
_subPrepFailed = false;
+ _fillingStationItem = nullptr;
setIsItemTaken(kGasCanister);
}
diff --git a/engines/pegasus/neighborhood/norad/delta/globegame.cpp b/engines/pegasus/neighborhood/norad/delta/globegame.cpp
index 9ea3036024..434b95c978 100644
--- a/engines/pegasus/neighborhood/norad/delta/globegame.cpp
+++ b/engines/pegasus/neighborhood/norad/delta/globegame.cpp
@@ -42,6 +42,9 @@ GlobeTracker::GlobeTracker(Movie *globeMovie, Picture *leftHighlight, Picture *r
_rightHighlight = rightHighlight;
_upHighlight = upHighlight;
_downHighlight = downHighlight;
+ _trackSpot = nullptr;
+ _trackTime = -1;
+ _trackDirection = kTrackDown;
}
void GlobeTracker::setTrackParameters(const Hotspot *trackSpot, GlobeTrackDirection direction) {
diff --git a/engines/pegasus/neighborhood/norad/pressuretracker.cpp b/engines/pegasus/neighborhood/norad/pressuretracker.cpp
index 5aac19dcbe..390e3e33b6 100644
--- a/engines/pegasus/neighborhood/norad/pressuretracker.cpp
+++ b/engines/pegasus/neighborhood/norad/pressuretracker.cpp
@@ -34,6 +34,7 @@ PressureTracker::PressureTracker(PressureDoor *pressureDoor) {
_pressureDoor = pressureDoor;
_trackSpot = 0;
_trackTime = 0;
+ _trackButton = nullptr;
}
void PressureTracker::setTrackParameters(const Hotspot *trackSpot, Sprite *trackButton) {
diff --git a/engines/pegasus/neighborhood/wsc/wsc.cpp b/engines/pegasus/neighborhood/wsc/wsc.cpp
index 5e35d8ccc1..c907bee289 100644
--- a/engines/pegasus/neighborhood/wsc/wsc.cpp
+++ b/engines/pegasus/neighborhood/wsc/wsc.cpp
@@ -486,6 +486,12 @@ static const CoordType kMoleculesMovieTop = kNavAreaTop + 40;
WSC::WSC(InputHandler *nextHandler, PegasusEngine *owner) : Neighborhood(nextHandler, owner, "WSC", kWSCID),
_moleculesMovie(kNoDisplayElement) {
+
+ _argonSprite = nullptr;
+ _cachedZoomSpot = nullptr;
+ _moleculeGameLevel = 0;
+ _numCorrect = 0;
+
setIsItemTaken(kArgonCanister);
setIsItemTaken(kSinclairKey);
setIsItemTaken(kNitrogenCanister);
diff --git a/engines/prince/debugger.cpp b/engines/prince/debugger.cpp
index fc216e0cfb..661b563944 100644
--- a/engines/prince/debugger.cpp
+++ b/engines/prince/debugger.cpp
@@ -37,6 +37,8 @@ Debugger::Debugger(PrinceEngine *vm, InterpreterFlags *flags) : GUI::Debugger(),
registerCmd("initroom", WRAP_METHOD(Debugger, Cmd_InitRoom));
registerCmd("changecursor", WRAP_METHOD(Debugger, Cmd_ChangeCursor));
registerCmd("additem", WRAP_METHOD(Debugger, Cmd_AddItem));
+
+ _cursorNr = 0;
}
static int strToInt(const char *s) {
diff --git a/engines/prince/graphics.cpp b/engines/prince/graphics.cpp
index d5178efc17..ea8c52a45b 100644
--- a/engines/prince/graphics.cpp
+++ b/engines/prince/graphics.cpp
@@ -44,6 +44,8 @@ GraphicsMan::GraphicsMan(PrinceEngine *vm) : _vm(vm), _changed(false) {
_shadowTable70 = (byte *)malloc(256);
_shadowTable50 = (byte *)malloc(256);
+
+ _roomBackground = 0;
}
GraphicsMan::~GraphicsMan() {
diff --git a/engines/prince/mob.h b/engines/prince/mob.h
index 0ea610dd8f..863fd3a319 100644
--- a/engines/prince/mob.h
+++ b/engines/prince/mob.h
@@ -35,7 +35,7 @@ namespace Prince {
class Mob {
public:
- Mob() : _name(""), _examText("") {}
+ Mob() : _name(""), _examText(""), _visible(false), _type(0), _mask(0), _examDirection(kDirL), _useDirection(kDirL) {}
bool loadFromStream(Common::SeekableReadStream &stream);
diff --git a/engines/prince/prince.cpp b/engines/prince/prince.cpp
index f1fd5a25d3..a1386c6b16 100644
--- a/engines/prince/prince.cpp
+++ b/engines/prince/prince.cpp
@@ -623,6 +623,8 @@ void PrinceEngine::changeCursor(uint16 curId) {
const Graphics::Surface *curSurface = nullptr;
switch (curId) {
+ default:
+ error("Unknown cursor Id: %d", curId);
case 0:
CursorMan.showMouse(false);
_optionsFlag = 0;
diff --git a/engines/prince/saveload.cpp b/engines/prince/saveload.cpp
index 46e598be70..d3360badd1 100644
--- a/engines/prince/saveload.cpp
+++ b/engines/prince/saveload.cpp
@@ -63,7 +63,7 @@ SaveStateList PrinceMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames;
Common::String pattern = target;
- pattern += ".???";
+ pattern += ".###";
filenames = saveFileMan->listSavefiles(pattern);
sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)
diff --git a/engines/prince/script.cpp b/engines/prince/script.cpp
index 4ed3cee6f3..89e22b586e 100644
--- a/engines/prince/script.cpp
+++ b/engines/prince/script.cpp
@@ -400,9 +400,12 @@ bool Script::loadAllMasks(Common::Array<Mask> &maskList, int offset) {
return false;
}
delete msStream;
+
+ tempMask._width = tempMask.getWidth();
+ tempMask._height = tempMask.getHeight();
+ } else {
+ return false;
}
- tempMask._width = tempMask.getWidth();
- tempMask._height = tempMask.getHeight();
}
maskList.push_back(tempMask);
diff --git a/engines/prince/sound.cpp b/engines/prince/sound.cpp
index c9746842ef..22db9c4998 100644
--- a/engines/prince/sound.cpp
+++ b/engines/prince/sound.cpp
@@ -118,6 +118,7 @@ const uint8 MusicPlayer::_musRoomTable[] = {
MusicPlayer::MusicPlayer(PrinceEngine *vm) : _vm(vm) {
_data = nullptr;
+ _dataSize = 0;
_isGM = false;
MidiPlayer::createDriver();
diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp
index b08534c7fa..c663313f01 100644
--- a/engines/saga/interface.cpp
+++ b/engines/saga/interface.cpp
@@ -1170,7 +1170,7 @@ void Interface::processStatusTextInput(Common::KeyState keystate) {
_statusTextInputPos--;
_statusTextInputString[_statusTextInputPos] = 0;
default:
- if (_statusTextInputPos > STATUS_TEXT_INPUT_MAX) {
+ if (_statusTextInputPos >= STATUS_TEXT_INPUT_MAX - 1) { // -1 because of the null termination
break;
}
if (Common::isAlnum(keystate.ascii) || (keystate.ascii == ' ')) {
diff --git a/engines/saga/saveload.cpp b/engines/saga/saveload.cpp
index 2d798bb0d6..1a131bf5cc 100644
--- a/engines/saga/saveload.cpp
+++ b/engines/saga/saveload.cpp
@@ -185,7 +185,7 @@ void SagaEngine::save(const char *fileName, const char *saveName) {
// Original game title
memset(title, 0, TITLESIZE);
- strncpy(title, _gameTitle.c_str(), TITLESIZE);
+ Common::strlcpy(title, _gameTitle.c_str(), TITLESIZE);
out->write(title, TITLESIZE);
// Thumbnail
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 51fb52bb21..27ac4fac49 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -41,9 +41,7 @@
#include "sci/graphics/cache.h"
#include "sci/graphics/cursor.h"
#include "sci/graphics/screen.h"
-#include "sci/graphics/paint.h"
#include "sci/graphics/paint16.h"
-#include "sci/graphics/paint32.h"
#include "sci/graphics/palette.h"
#include "sci/graphics/ports.h"
#include "sci/graphics/view.h"
@@ -54,6 +52,7 @@
#include "sci/video/seq_decoder.h"
#ifdef ENABLE_SCI32
#include "sci/graphics/frameout.h"
+#include "sci/graphics/paint32.h"
#include "video/coktel_decoder.h"
#include "sci/video/robot_decoder.h"
#endif
@@ -1648,7 +1647,7 @@ bool Console::cmdDrawPic(int argc, const char **argv) {
#endif
uint16 resourceId = atoi(argv[1]);
- _engine->_gfxPaint->kernelDrawPicture(resourceId, 100, false, false, false, 0);
+ _engine->_gfxPaint16->kernelDrawPicture(resourceId, 100, false, false, false, 0);
_engine->_gfxScreen->copyToScreen();
_engine->sleep(2000);
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index c01613268a..0b69aa9221 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -3591,7 +3591,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.map", 0, "aba367f2102e81782d961b14fbe3d630", 10246},
{"resource.000", 0, "263dce4aa34c49d3ad29bec889007b1c", 11571394},
AD_LISTEND},
- Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
// RAMA - English DOS/Windows Demo
// Executable scanning reports "2.100.002", VERSION file reports "000.000.008"
diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp
index 70c8c52bf0..b229490570 100644
--- a/engines/sci/engine/gc.cpp
+++ b/engines/sci/engine/gc.cpp
@@ -24,6 +24,10 @@
#include "common/array.h"
#include "sci/graphics/ports.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/controls32.h"
+#endif
+
namespace Sci {
//#define GC_DEBUG_CODE
@@ -150,6 +154,12 @@ AddrSet *findAllActiveReferences(EngineState *s) {
}
}
+#ifdef ENABLE_SCI32
+ // Init: ScrollWindows
+ if (g_sci->_gfxControls32)
+ wm.pushArray(g_sci->_gfxControls32->listObjectReferences());
+#endif
+
debugC(kDebugLevelGC, "[GC] -- Finished explicitly loaded scripts, done with root set");
processWorkList(s->_segMan, wm, heap);
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 62566a74b2..1202982986 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -412,7 +412,7 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv);
reg_t kTextColors(EngineState *s, int argc, reg_t *argv);
reg_t kTextFonts(EngineState *s, int argc, reg_t *argv);
reg_t kShow(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColors(EngineState *s, int argc, reg_t *argv);
reg_t kDummy(EngineState *s, int argc, reg_t *argv);
reg_t kEmpty(EngineState *s, int argc, reg_t *argv);
reg_t kStub(EngineState *s, int argc, reg_t *argv);
@@ -420,6 +420,26 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv);
#ifdef ENABLE_SCI32
// SCI2 Kernel Functions
+reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioDistort(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv);
+
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
reg_t kArray(EngineState *s, int argc, reg_t *argv);
reg_t kListAt(EngineState *s, int argc, reg_t *argv);
@@ -447,19 +467,28 @@ reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv);
+reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv);
reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv);
reg_t kMulDiv(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapColors(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapOff(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv);
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv);
reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv);
@@ -537,6 +566,7 @@ reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv);
reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv);
reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv);
reg_t kWinHelp(EngineState *s, int argc, reg_t *argv);
+reg_t kMessageBox(EngineState *s, int argc, reg_t *argv);
reg_t kGetConfig(EngineState *s, int argc, reg_t *argv);
reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv);
reg_t kCelInfo(EngineState *s, int argc, reg_t *argv);
@@ -558,7 +588,6 @@ reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv);
-reg_t kDoSoundRestore(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv);
@@ -573,7 +602,6 @@ reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv);
-reg_t kDoSoundDummy(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv);
reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 0ede307e6b..dacaafe757 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -102,7 +102,7 @@ struct SciKernelMapSubEntry {
static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI0, 0, MAP_CALL(DoSoundInit), "o", NULL },
{ SIG_SOUNDSCI0, 1, MAP_CALL(DoSoundPlay), "o", NULL },
- { SIG_SOUNDSCI0, 2, MAP_CALL(DoSoundRestore), "(o)", NULL },
+ { SIG_SOUNDSCI0, 2, MAP_EMPTY(DoSoundRestore), "(o)", NULL },
{ SIG_SOUNDSCI0, 3, MAP_CALL(DoSoundDispose), "o", NULL },
{ SIG_SOUNDSCI0, 4, MAP_CALL(DoSoundMute), "(i)", NULL },
{ SIG_SOUNDSCI0, 5, MAP_CALL(DoSoundStop), "o", NULL },
@@ -115,7 +115,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI0, 12, MAP_CALL(DoSoundStopAll), "", NULL },
{ SIG_SOUNDSCI1EARLY, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 1, MAP_CALL(DoSoundMute), NULL, NULL },
- { SIG_SOUNDSCI1EARLY, 2, MAP_CALL(DoSoundRestore), NULL, NULL },
+ { SIG_SOUNDSCI1EARLY, 2, MAP_EMPTY(DoSoundRestore), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 4, MAP_CALL(DoSoundUpdate), NULL, NULL },
{ SIG_SOUNDSCI1EARLY, 5, MAP_CALL(DoSoundInit), NULL, NULL },
@@ -128,11 +128,11 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI1EARLY, 12, MAP_CALL(DoSoundSendMidi), "oiii", NULL },
{ SIG_SOUNDSCI1EARLY, 13, MAP_CALL(DoSoundGlobalReverb), "(i)", NULL },
{ SIG_SOUNDSCI1EARLY, 14, MAP_CALL(DoSoundSetHold), "oi", NULL },
- { SIG_SOUNDSCI1EARLY, 15, MAP_CALL(DoSoundDummy), "", NULL },
+ { SIG_SOUNDSCI1EARLY, 15, MAP_EMPTY(DoSoundDummy), "", NULL },
// ^^ Longbow demo
{ SIG_SOUNDSCI1LATE, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 1, MAP_CALL(DoSoundMute), NULL, NULL },
- { SIG_SOUNDSCI1LATE, 2, MAP_CALL(DoSoundRestore), "", NULL },
+ { SIG_SOUNDSCI1LATE, 2, MAP_EMPTY(DoSoundRestore), "", NULL },
{ SIG_SOUNDSCI1LATE, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL },
{ SIG_SOUNDSCI1LATE, 5, MAP_CALL(DoSoundSuspend), "i", NULL },
@@ -143,7 +143,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI1LATE, 10, MAP_CALL(DoSoundPause), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 11, MAP_CALL(DoSoundFade), "oiiii(i)", kDoSoundFade_workarounds },
{ SIG_SOUNDSCI1LATE, 12, MAP_CALL(DoSoundSetHold), NULL, NULL },
- { SIG_SOUNDSCI1LATE, 13, MAP_CALL(DoSoundDummy), NULL, NULL },
+ { SIG_SOUNDSCI1LATE, 13, MAP_EMPTY(DoSoundDummy), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL },
{ SIG_SOUNDSCI1LATE, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL },
{ SIG_SOUNDSCI1LATE, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL },
@@ -152,36 +152,97 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
{ SIG_SOUNDSCI1LATE, 19, MAP_CALL(DoSoundGlobalReverb), NULL, NULL },
{ SIG_SOUNDSCI1LATE, 20, MAP_CALL(DoSoundUpdate), NULL, NULL },
#ifdef ENABLE_SCI32
- { SIG_SOUNDSCI21, 0, MAP_CALL(DoSoundMasterVolume), NULL, NULL },
- { SIG_SOUNDSCI21, 1, MAP_CALL(DoSoundMute), NULL, NULL },
- { SIG_SOUNDSCI21, 2, MAP_CALL(DoSoundRestore), NULL, NULL },
- { SIG_SOUNDSCI21, 3, MAP_CALL(DoSoundGetPolyphony), NULL, NULL },
- { SIG_SOUNDSCI21, 4, MAP_CALL(DoSoundGetAudioCapability), NULL, NULL },
- { SIG_SOUNDSCI21, 5, MAP_CALL(DoSoundSuspend), NULL, NULL },
- { SIG_SOUNDSCI21, 6, MAP_CALL(DoSoundInit), NULL, NULL },
- { SIG_SOUNDSCI21, 7, MAP_CALL(DoSoundDispose), NULL, NULL },
- { SIG_SOUNDSCI21, 8, MAP_CALL(DoSoundPlay), "o(i)", NULL },
+ { SIG_SOUNDSCI21, 0, MAP_CALL(DoSoundMasterVolume), "(i)", NULL },
+ { SIG_SOUNDSCI21, 1, MAP_CALL(DoSoundMute), "(i)", NULL },
+ { SIG_SOUNDSCI21, 2, MAP_EMPTY(DoSoundRestore), NULL, NULL },
+ { SIG_SOUNDSCI21, 3, MAP_CALL(DoSoundGetPolyphony), "", NULL },
+ { SIG_SOUNDSCI21, 4, MAP_CALL(DoSoundGetAudioCapability), "", NULL },
+ { SIG_SOUNDSCI21, 5, MAP_CALL(DoSoundSuspend), "i", NULL },
+ { SIG_SOUNDSCI21, 6, MAP_CALL(DoSoundInit), "o", NULL },
+ { SIG_SOUNDSCI21, 7, MAP_CALL(DoSoundDispose), "o", NULL },
+ { SIG_SOUNDSCI21, 8, MAP_CALL(DoSoundPlay), "o", kDoSoundPlay_workarounds },
// ^^ TODO: if this is really the only change between SCI1LATE AND SCI21, we could rename the
// SIG_SOUNDSCI1LATE #define to SIG_SINCE_SOUNDSCI1LATE and make it being SCI1LATE+. Although
// I guess there are many more changes somewhere
// TODO: Quest for Glory 4 (SCI2.1) uses the old scheme, we need to detect it accordingly
// signature for SCI21 should be "o"
- { SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), NULL, NULL },
- { SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), NULL, NULL },
- { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), NULL, kDoSoundFade_workarounds },
- { SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), NULL, NULL },
- { SIG_SOUNDSCI21, 13, MAP_CALL(DoSoundDummy), NULL, NULL },
- { SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), NULL, NULL },
- { SIG_SOUNDSCI21, 15, MAP_CALL(DoSoundSetPriority), NULL, NULL },
- { SIG_SOUNDSCI21, 16, MAP_CALL(DoSoundSetLoop), NULL, NULL },
- { SIG_SOUNDSCI21, 17, MAP_CALL(DoSoundUpdateCues), NULL, NULL },
- { SIG_SOUNDSCI21, 18, MAP_CALL(DoSoundSendMidi), NULL, NULL },
- { SIG_SOUNDSCI21, 19, MAP_CALL(DoSoundGlobalReverb), NULL, NULL },
- { SIG_SOUNDSCI21, 20, MAP_CALL(DoSoundUpdate), NULL, NULL },
+ { SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), "o", NULL },
+ { SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), "[o0]i", NULL },
+ { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), "oiiii", kDoSoundFade_workarounds },
+ { SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), "oi", NULL },
+ { SIG_SOUNDSCI21, 13, MAP_EMPTY(DoSoundDummy), NULL, NULL },
+ { SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), "oi", NULL },
+ { SIG_SOUNDSCI21, 15, MAP_CALL(DoSoundSetPriority), "oi", NULL },
+ { SIG_SOUNDSCI21, 16, MAP_CALL(DoSoundSetLoop), "oi", NULL },
+ { SIG_SOUNDSCI21, 17, MAP_CALL(DoSoundUpdateCues), "o", NULL },
+ { SIG_SOUNDSCI21, 18, MAP_CALL(DoSoundSendMidi), "oiiii", NULL },
+ { SIG_SOUNDSCI21, 19, MAP_CALL(DoSoundGlobalReverb), "(i)", NULL },
+ { SIG_SOUNDSCI21, 20, MAP_CALL(DoSoundUpdate), "o", NULL },
#endif
SCI_SUBOPENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+// NOTE: In SSCI, some 'unused' kDoAudio subops are actually
+// called indirectly by kDoSound:
+//
+// kDoSoundGetAudioCapability -> kDoAudioGetCapability
+// kDoSoundPlay -> kDoAudioPlay, kDoAudioStop
+// kDoSoundPause -> kDoAudioPause, kDoAudioResume
+// kDoSoundFade -> kDoAudioFade
+// kDoSoundSetVolume -> kDoAudioVolume
+// kDoSoundSetLoop -> kDoAudioSetLoop
+// kDoSoundUpdateCues -> kDoAudioPosition
+//
+// In ScummVM, logic inside these kernel functions has been
+// moved to methods of Audio32, and direct calls to Audio32
+// are made from kDoSound instead.
+//
+// Some kDoAudio methods are esoteric and appear to be used
+// only by one or two games:
+//
+// kDoAudioMixing: Phantasmagoria (other games call this
+// function, but only to disable the feature)
+// kDoAudioHasSignal: SQ6 TalkRandCycle
+// kDoAudioPan: Rama RegionSFX::pan method
+//
+// Finally, there is a split in SCI2.1mid audio code.
+// QFG4CD & SQ6 do not have opcodes 18 and 19, but they
+// exist in GK2, KQ7 2.00b, Phantasmagoria 1, PQ:SWAT, and
+// Torin. (It is unknown if they exist in MUMG Deluxe or
+// Shivers 1; they are not used in either of these games.)
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kDoAudio_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(DoAudioInit), "", NULL },
+ // SCI2 includes a Sync script that would call
+ // kDoAudioWaitForPlay, but SSCI has no opcode 1 until
+ // SCI2.1early
+ { SIG_SINCE_SCI21, 1, MAP_CALL(DoAudioWaitForPlay), "(i)(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(DoAudioPlay), "(i)(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(DoAudioStop), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 4, MAP_CALL(DoAudioPause), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 5, MAP_CALL(DoAudioResume), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 6, MAP_CALL(DoAudioPosition), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 7, MAP_CALL(DoAudioRate), "(i)", NULL },
+ { SIG_SCI32, 8, MAP_CALL(DoAudioVolume), "(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 9, MAP_CALL(DoAudioGetCapability), "", NULL },
+ { SIG_SCI32, 10, MAP_CALL(DoAudioBitDepth), "(i)", NULL },
+ { SIG_SCI32, 11, MAP_DUMMY(DoAudioDistort), "(i)", NULL },
+ { SIG_SCI32, 12, MAP_CALL(DoAudioMixing), "(i)", NULL },
+ { SIG_SCI32, 13, MAP_CALL(DoAudioChannels), "(i)", NULL },
+ { SIG_SCI32, 14, MAP_CALL(DoAudioPreload), "(i)", NULL },
+ { SIG_SINCE_SCI21MID, 15, MAP_CALL(DoAudioFade), "(iiii)(i)(i)", NULL },
+ { SIG_SINCE_SCI21MID, 16, MAP_DUMMY(DoAudioFade36), "iiiii(iii)(i)", NULL },
+ { SIG_SINCE_SCI21MID, 17, MAP_CALL(DoAudioHasSignal), "", NULL },
+ { SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical), "", NULL },
+ { SIG_SINCE_SCI21MID, 19, MAP_CALL(DoAudioSetLoop), "iii(o)", NULL },
+ { SIG_SCI3, 20, MAP_DUMMY(DoAudioPan), "", NULL },
+ { SIG_SCI3, 21, MAP_DUMMY(DoAudioPanOff), "", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+#endif
+
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kGraph_subops[] = {
{ SIG_SCI32, 1, MAP_CALL(StubNull), "", NULL }, // called by gk1 sci32 right at the start
@@ -353,12 +414,12 @@ static const SciKernelMapSubEntry kList_subops[] = {
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kRemapColors_subops[] = {
- { SIG_SCI32, 0, MAP_CALL(RemapOff), "(i)", NULL },
- { SIG_SCI32, 1, MAP_CALL(RemapByRange), "iiii(i)", NULL },
- { SIG_SCI32, 2, MAP_CALL(RemapByPercent), "ii(i)", NULL },
- { SIG_SCI32, 3, MAP_CALL(RemapToGray), "ii(i)", NULL },
- { SIG_SCI32, 4, MAP_CALL(RemapToPercentGray), "iii(i)", NULL },
- { SIG_SCI32, 5, MAP_CALL(RemapSetNoMatchRange), "ii", NULL },
+ { SIG_SCI32, 0, MAP_CALL(RemapColorsOff), "(i)", NULL },
+ { SIG_SCI32, 1, MAP_CALL(RemapColorsByRange), "iiii(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(RemapColorsByPercent), "ii(i)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(RemapColorsToGray), "ii(i)", NULL },
+ { SIG_SCI32, 4, MAP_CALL(RemapColorsToPercentGray), "iii(i)", NULL },
+ { SIG_SCI32, 5, MAP_CALL(RemapColorsBlockRange), "ii", NULL },
SCI_SUBOPENTRY_TERMINATOR
};
@@ -407,25 +468,30 @@ static const SciKernelMapSubEntry kString_subops[] = {
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kScrollWindow_subops[] = {
{ SIG_SCI32, 0, MAP_CALL(ScrollWindowCreate), "oi", NULL },
- { SIG_SCI32, 1, MAP_CALL(ScrollWindowAdd), "o.ii.(.)", NULL },
- { SIG_SCI32, 2, MAP_DUMMY(ScrollWindowClear), "o", NULL },
- { SIG_SCI32, 3, MAP_DUMMY(ScrollWindowPageUp), "o", NULL },
- { SIG_SCI32, 4, MAP_DUMMY(ScrollWindowPageDown), "o", NULL },
- { SIG_SCI32, 5, MAP_DUMMY(ScrollWindowUpArrow), "o", NULL },
- { SIG_SCI32, 6, MAP_DUMMY(ScrollWindowDownArrow), "o", NULL },
- { SIG_SCI32, 7, MAP_DUMMY(ScrollWindowHome), "o", NULL },
- { SIG_SCI32, 8, MAP_DUMMY(ScrollWindowEnd), "o", NULL },
- { SIG_SCI32, 9, MAP_DUMMY(ScrollWindowResize), "o.", NULL },
- { SIG_SCI32, 10, MAP_CALL(ScrollWindowWhere), "oi", NULL },
- { SIG_SCI32, 11, MAP_DUMMY(ScrollWindowGo), "o..", NULL },
- { SIG_SCI32, 12, MAP_DUMMY(ScrollWindowInsert), "o.....", NULL },
- { SIG_SCI32, 13, MAP_DUMMY(ScrollWindowDelete), "o.", NULL },
- { SIG_SCI32, 14, MAP_DUMMY(ScrollWindowModify), "o.....(.)", NULL },
- { SIG_SCI32, 15, MAP_DUMMY(ScrollWindowHide), "o", NULL },
- { SIG_SCI32, 16, MAP_CALL(ScrollWindowShow), "o", NULL },
- { SIG_SCI32, 17, MAP_CALL(ScrollWindowDestroy), "o", NULL },
- { SIG_SCI32, 18, MAP_DUMMY(ScrollWindowText), "o", NULL },
- { SIG_SCI32, 19, MAP_DUMMY(ScrollWindowReconstruct), "o.", NULL },
+ { SIG_SCI32, 1, MAP_CALL(ScrollWindowAdd), "iriii(i)", kScrollWindowAdd_workarounds },
+ { SIG_SCI32, 2, MAP_DUMMY(ScrollWindowClear), "i", NULL },
+ { SIG_SCI32, 3, MAP_CALL(ScrollWindowPageUp), "i", NULL },
+ { SIG_SCI32, 4, MAP_CALL(ScrollWindowPageDown), "i", NULL },
+ { SIG_SCI32, 5, MAP_CALL(ScrollWindowUpArrow), "i", NULL },
+ { SIG_SCI32, 6, MAP_CALL(ScrollWindowDownArrow), "i", NULL },
+ { SIG_SCI32, 7, MAP_CALL(ScrollWindowHome), "i", NULL },
+ { SIG_SCI32, 8, MAP_CALL(ScrollWindowEnd), "i", NULL },
+ { SIG_SCI32, 9, MAP_DUMMY(ScrollWindowResize), "i.", NULL },
+ { SIG_SCI32, 10, MAP_CALL(ScrollWindowWhere), "ii", NULL },
+ { SIG_SCI32, 11, MAP_CALL(ScrollWindowGo), "i..", NULL },
+ { SIG_SCI32, 12, MAP_DUMMY(ScrollWindowInsert), "i.....", NULL },
+ { SIG_SCI32, 13, MAP_DUMMY(ScrollWindowDelete), "i.", NULL },
+ { SIG_SCI32, 14, MAP_CALL(ScrollWindowModify), "iiriii(i)", NULL },
+ { SIG_SCI32, 15, MAP_CALL(ScrollWindowHide), "i", NULL },
+ { SIG_SCI32, 16, MAP_CALL(ScrollWindowShow), "i", NULL },
+ { SIG_SCI32, 17, MAP_CALL(ScrollWindowDestroy), "i", NULL },
+ // LSL6hires uses kScrollWindowText and kScrollWindowReconstruct to try to save
+ // and restore the content of the game's subtitle window, but this feature did not
+ // use the normal save/load functionality of the engine and was actually broken
+ // (all text formatting was missing on restore). Since there is no real reason to
+ // save the subtitle scrollback anyway, we just ignore calls to these two functions.
+ { SIG_SCI32, 18, MAP_EMPTY(ScrollWindowText), "i", NULL },
+ { SIG_SCI32, 19, MAP_EMPTY(ScrollWindowReconstruct), "i.", NULL },
SCI_SUBOPENTRY_TERMINATOR
};
@@ -484,7 +550,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(DisposeList), SIG_EVERYWHERE, "l", NULL, NULL },
{ MAP_CALL(DisposeScript), SIG_EVERYWHERE, "i(i*)", NULL, kDisposeScript_workarounds },
{ MAP_CALL(DisposeWindow), SIG_EVERYWHERE, "i(i)", NULL, NULL },
- { MAP_CALL(DoAudio), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop
+ { MAP_CALL(DoAudio), SCI_VERSION_NONE, SCI_VERSION_2, SIGFOR_ALL, "i(.*)", NULL, NULL }, // subop
+#ifdef ENABLE_SCI32
+ { "DoAudio", kDoAudio32, SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kDoAudio_subops, NULL },
+#endif
{ MAP_CALL(DoAvoider), SIG_EVERYWHERE, "o(i)", NULL, NULL },
{ MAP_CALL(DoBresen), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(DoSound), SIG_EVERYWHERE, "i(.*)", kDoSound_subops, NULL },
@@ -565,9 +634,9 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL },
{ MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, kReadNumber_workarounds },
- { "RemapColors", kRemapColors16, SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL },
+ { MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL },
#ifdef ENABLE_SCI32
- { MAP_CALL(RemapColors), SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", kRemapColors_subops, NULL },
+ { "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", kRemapColors_subops, NULL },
#endif
{ MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL },
{ MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL },
@@ -659,6 +728,8 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(ListEachElementDo), SIG_EVERYWHERE, "li(.*)", NULL, NULL },
{ MAP_CALL(ListFirstTrue), SIG_EVERYWHERE, "li(.*)", NULL, NULL },
{ MAP_CALL(ListIndexOf), SIG_EVERYWHERE, "l[o0]", NULL, NULL },
+ // kMessageBox is used only by KQ7 1.51
+ { MAP_CALL(MessageBox), SIG_SCI32, SIGFOR_ALL, "rri", NULL, NULL },
{ "OnMe", kIsOnMe, SIG_EVERYWHERE, "iioi", NULL, NULL },
// Purge is used by the memory manager in SSCI to ensure that X number of bytes (the so called "unmovable
// memory") are available when the current room changes. This is similar to the SCI0-SCI1.1 FlushResources
@@ -736,9 +807,12 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(SetFontRes), SIG_SCI21EARLY, SIGFOR_ALL, "ii", NULL, NULL },
{ MAP_CALL(Font), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kFont_subops, NULL },
{ MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", kBitmap_subops, NULL },
- { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiiiiiiii", NULL, NULL },
- { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "[r0]oiiiiiiiii", NULL, NULL },
- { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "[r0]o", NULL, NULL },
+ { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiii(iiiii)", NULL, NULL },
+ // The first argument is a ScreenItem instance ID that is created by the
+ // engine, not the VM; as a result, in ScummVM, this argument looks like
+ // an integer and not an object, although it is an object reference.
+ { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "ioiiii(iiiii)", NULL, NULL },
+ { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "io", NULL, NULL },
// SCI2.1 Empty Functions
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 335763a35f..d604bb85d0 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -806,25 +806,47 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
} else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) {
// virtualId is low, we assume that scripts expect us to create new slot
- if (g_sci->getGameId() == GID_JONES) {
+ switch (g_sci->getGameId()) {
+ case GID_JONES:
// Jones has one save slot only
savegameId = 0;
- } else if (virtualId == s->_lastSaveVirtualId) {
- // if last virtual id is the same as this one, we assume that caller wants to overwrite last save
- savegameId = s->_lastSaveNewId;
- } else {
- uint savegameNr;
- // savegameId is in lower range, scripts expect us to create a new slot
- for (savegameId = SAVEGAMESLOT_FIRST; savegameId <= SAVEGAMESLOT_LAST; savegameId++) {
- for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) {
- if (savegameId == saves[savegameNr].id)
+ break;
+ case GID_FANMADE: {
+ // Fanmade game, try to identify the game
+ const char *gameName = g_sci->getGameObjectName();
+
+ if (strcmp(gameName, "CascadeQuest") == 0) {
+ // Cascade Quest calls us directly to auto-save and uses slot 99,
+ // put that save into slot 0 (ScummVM auto-save slot) see bug #7007
+ if (virtualId == (SAVEGAMEID_OFFICIALRANGE_START - 1)) {
+ savegameId = 0;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (savegameId < 0) {
+ // savegameId not set yet
+ if (virtualId == s->_lastSaveVirtualId) {
+ // if last virtual id is the same as this one, we assume that caller wants to overwrite last save
+ savegameId = s->_lastSaveNewId;
+ } else {
+ uint savegameNr;
+ // savegameId is in lower range, scripts expect us to create a new slot
+ for (savegameId = SAVEGAMESLOT_FIRST; savegameId <= SAVEGAMESLOT_LAST; savegameId++) {
+ for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) {
+ if (savegameId == saves[savegameNr].id)
+ break;
+ }
+ if (savegameNr == saves.size()) // Slot not found, seems to be good to go
break;
}
- if (savegameNr == saves.size()) // Slot not found, seems to be good to go
- break;
+ if (savegameId > SAVEGAMESLOT_LAST)
+ error("kSavegame: no more savegame slots available");
}
- if (savegameId > SAVEGAMESLOT_LAST)
- error("kSavegame: no more savegame slots available");
}
} else {
error("kSaveGame: invalid savegameId used");
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index 73236b98ed..cae5a09789 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -1258,7 +1258,7 @@ reg_t kShow(EngineState *s, int argc, reg_t *argv) {
}
// Early variant of the SCI32 kRemapColors kernel function, used in the demo of QFG4
-reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv) {
+reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
uint16 operation = argv[0].toUint16();
switch (operation) {
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 6c51ec4284..019a06930c 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -52,7 +52,9 @@
#include "sci/graphics/controls32.h"
#include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class
#include "sci/graphics/frameout.h"
+#include "sci/graphics/paint32.h"
#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/text32.h"
#endif
@@ -133,8 +135,6 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) {
bool showBits = argc > 0 ? argv[0].toUint16() : true;
g_sci->_gfxFrameout->kernelFrameOut(showBits);
- s->speedThrottler(16);
- s->_throttleTrigger = true;
return s->r_acc;
}
@@ -255,6 +255,10 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
+reg_t kMessageBox(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_gfxControls32->kernelMessageBox(s->_segMan->getString(argv[0]), s->_segMan->getString(argv[1]), argv[2].toUint16());
+}
+
/**
* Causes an immediate plane transition with an optional transition
* effect
@@ -361,140 +365,138 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
}
reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowCreate");
- kStub(s, argc, argv);
- return argv[0];
+ const reg_t object = argv[0];
+ const uint16 maxNumEntries = argv[1].toUint16();
+
+ SegManager *segMan = s->_segMan;
+ const int16 borderColor = readSelectorValue(segMan, object, SELECTOR(borderColor));
+ const TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode));
+ const GuiResourceId fontId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(font));
+ const int16 backColor = readSelectorValue(segMan, object, SELECTOR(back));
+ const int16 foreColor = readSelectorValue(segMan, object, SELECTOR(fore));
+ const reg_t plane = readSelector(segMan, object, SELECTOR(plane));
+
+ Common::Rect rect;
+ rect.left = readSelectorValue(segMan, object, SELECTOR(nsLeft));
+ rect.top = readSelectorValue(segMan, object, SELECTOR(nsTop));
+ rect.right = readSelectorValue(segMan, object, SELECTOR(nsRight)) + 1;
+ rect.bottom = readSelectorValue(segMan, object, SELECTOR(nsBottom)) + 1;
+ const Common::Point position(rect.left, rect.top);
+
+ return g_sci->_gfxControls32->makeScrollWindow(rect, position, plane, foreColor, backColor, fontId, alignment, borderColor, maxNumEntries);
}
reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowAdd");
- return kStubNull(s, argc, argv);
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ const Common::String text = s->_segMan->getString(argv[1]);
+ const GuiResourceId fontId = argv[2].toSint16();
+ const int16 color = argv[3].toSint16();
+ const TextAlign alignment = (TextAlign)argv[4].toSint16();
+ const bool scrollTo = argc > 5 ? (bool)argv[5].toUint16() : true;
+
+ return scrollWindow->add(text, fontId, color, alignment, scrollTo);
}
reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowWhere");
- return kStubNull(s, argc, argv);
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ const uint16 where = (argv[1].toUint16() * scrollWindow->where()).toInt();
+
+ return make_reg(0, where);
+}
+
+reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ const Ratio scrollTop(argv[1].toSint16(), argv[2].toSint16());
+ scrollWindow->go(scrollTop);
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ const reg_t entryId = argv[1];
+ const Common::String newText = s->_segMan->getString(argv[2]);
+ const GuiResourceId fontId = argv[3].toSint16();
+ const int16 color = argv[4].toSint16();
+ const TextAlign alignment = (TextAlign)argv[5].toSint16();
+ const bool scrollTo = argc > 6 ? (bool)argv[6].toUint16() : true;
+
+ return scrollWindow->modify(entryId, newText, fontId, color, alignment, scrollTo);
+}
+
+reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->hide();
+
+ return s->r_acc;
}
reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowShow");
- return kStubNull(s, argc, argv);
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->show();
+
+ return s->r_acc;
}
-reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) {
- debug("kScrollWindowDestroy");
- return kStubNull(s, argc, argv);
+reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->pageUp();
+
+ return s->r_acc;
}
-#if 0
-reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
- // Used by SQ6 and LSL6 hires for the text area in the bottom of the
- // screen. The relevant scripts also exist in Phantasmagoria 1, but they're
- // unused. This is always called by scripts 64906 (ScrollerWindow) and
- // 64907 (ScrollableWindow).
-
- reg_t kWindow = argv[1];
- uint16 op = argv[0].toUint16();
- switch (op) {
- case 0: // Init
- // TODO: Init reads the nsLeft, nsTop, nsRight, nsBottom,
- // borderColor, fore, back, mode, font, plane selectors
- // from the window in argv[1].
- g_sci->_gfxFrameout->initScrollText(argv[2].toUint16()); // maxItems
- g_sci->_gfxFrameout->clearScrollTexts();
- return argv[1]; // kWindow
- case 1: // Show message, called by ScrollableWindow::addString
- case 14: // Modify message, called by ScrollableWindow::modifyString
- // TODO: The parameters in Modify are shifted by one: the first
- // argument is the handle of the text to modify. The others
- // are as Add.
- {
- Common::String text = s->_segMan->getString(argv[2]);
- uint16 x = 0;
- uint16 y = 0;
- // TODO: argv[3] is font
- // TODO: argv[4] is color
- // TODO: argv[5] is alignment (0 = left, 1 = center, 2 = right)
- // font,color,alignment may also be -1. (Maybe same as previous?)
- // TODO: argv[6] is an optional bool, defaulting to true if not present.
- // If true, the old contents are scrolled out of view.
- // TODO: Return a handle of the inserted text. (Used for modify/insert)
- // This handle looks like it should also be usable by kString.
- g_sci->_gfxFrameout->addScrollTextEntry(text, kWindow, x, y, (op == 14));
- }
- break;
- case 2: // Clear, called by ScrollableWindow::erase
- g_sci->_gfxFrameout->clearScrollTexts();
- break;
- case 3: // Page up, called by ScrollableWindow::scrollTo
- // TODO
- kStub(s, argc, argv);
- break;
- case 4: // Page down, called by ScrollableWindow::scrollTo
- // TODO
- kStub(s, argc, argv);
- break;
- case 5: // Up arrow, called by ScrollableWindow::scrollTo
- g_sci->_gfxFrameout->prevScrollText();
- break;
- case 6: // Down arrow, called by ScrollableWindow::scrollTo
- g_sci->_gfxFrameout->nextScrollText();
- break;
- case 7: // Home, called by ScrollableWindow::scrollTo
- g_sci->_gfxFrameout->firstScrollText();
- break;
- case 8: // End, called by ScrollableWindow::scrollTo
- g_sci->_gfxFrameout->lastScrollText();
- break;
- case 9: // Resize, called by ScrollableWindow::resize and ScrollerWindow::resize
- // TODO: This reads the nsLeft, nsTop, nsRight, nsBottom
- // selectors from the SCI object passed in argv[2].
- kStub(s, argc, argv);
- break;
- case 10: // Where, called by ScrollableWindow::where
- // TODO:
- // Gives the current relative scroll location as a fraction
- // with argv[2] as the denominator. (Return value is the numerator.)
- // Silenced the warnings because of the high amount of console spam
- //kStub(s, argc, argv);
- break;
- case 11: // Go, called by ScrollableWindow::scrollTo
- // TODO:
- // Two arguments provide a fraction: argv[2] is num., argv[3] is denom.
- // Scrolls to the relative location given by the fraction.
- kStub(s, argc, argv);
- break;
- case 12: // Insert, called by ScrollableWindow::insertString
- // 5 extra parameters here:
- // handle of insert location (new string takes that position).
- // text, font, color, alignment
- // TODO
- kStub(s, argc, argv);
- break;
- // case 13 (Delete) is handled below
- // case 14 (Modify) is handled above
- case 15: // Hide, called by ScrollableWindow::hide
- g_sci->_gfxFrameout->toggleScrollText(false);
- break;
- case 16: // Show, called by ScrollableWindow::show
- g_sci->_gfxFrameout->toggleScrollText(true);
- break;
- case 17: // Destroy, called by ScrollableWindow::dispose
- g_sci->_gfxFrameout->clearScrollTexts();
- break;
- case 13: // Delete, unused
- case 18: // Text, unused
- case 19: // Reconstruct, unused
- error("kScrollWindow: Unused subop %d invoked", op);
- break;
- default:
- error("kScrollWindow: unknown subop %d", op);
- break;
- }
+reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->pageDown();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->upArrow();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->downArrow();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->home();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv) {
+ ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]);
+
+ scrollWindow->end();
+
+ return s->r_acc;
+}
+
+reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxControls32->destroyScrollWindow(argv[0]);
return s->r_acc;
}
-#endif
reg_t kFont(EngineState *s, int argc, reg_t *argv) {
if (!s)
@@ -548,46 +550,31 @@ reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv) {
}
reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv) {
- // viewId, loopNo, celNo, displace x, displace y, unused, view x, view y
+ BitmapResource bitmap(argv[0]);
+ CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16());
- // called e.g. from TiledBitmap::resize() in Torin's Passage, script 64869
- // The tiled view seems to always have 2 loops.
- // These loops need to have 1 cel in loop 0 and 8 cels in loop 1.
+ const int16 x = argc > 4 ? argv[4].toSint16() : 0;
+ const int16 y = argc > 5 ? argv[5].toSint16() : 0;
+ const int16 alignX = argc > 7 ? argv[7].toSint16() : -1;
+ const int16 alignY = argc > 8 ? argv[8].toSint16() : -1;
- return kStubNull(s, argc + 1, argv - 1);
+ Common::Point position(
+ x == -1 ? bitmap.getDisplace().x : x,
+ y == -1 ? bitmap.getDisplace().y : y
+ );
-#if 0
- // tiled surface
- // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage,
- // script 64869
- reg_t hunkId = argv[1]; // obtained from kBitmap(0)
- // The tiled view seems to always have 2 loops.
- // These loops need to have 1 cel in loop 0 and 8 cels in loop 1.
- uint16 viewNum = argv[2].toUint16(); // vTiles selector
- uint16 loop = argv[3].toUint16();
- uint16 cel = argv[4].toUint16();
- uint16 x = argv[5].toUint16();
- uint16 y = argv[6].toUint16();
-
- byte *memoryPtr = s->_segMan->getHunkPointer(hunkId);
- // Get totalWidth, totalHeight
- uint16 totalWidth = READ_LE_UINT16(memoryPtr);
- uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2);
- byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE;
-
- GfxView *view = g_sci->_gfxCache->getView(viewNum);
- uint16 tileWidth = view->getWidth(loop, cel);
- uint16 tileHeight = view->getHeight(loop, cel);
- const byte *tileBitmap = view->getBitmap(loop, cel);
- uint16 width = MIN<uint16>(totalWidth - x, tileWidth);
- uint16 height = MIN<uint16>(totalHeight - y, tileHeight);
-
- for (uint16 curY = 0; curY < height; curY++) {
- for (uint16 curX = 0; curX < width; curX++) {
- bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX];
- }
- }
-#endif
+ position.x -= alignX == -1 ? view._displace.x : alignX;
+ position.y -= alignY == -1 ? view._displace.y : alignY;
+
+ Common::Rect drawRect(
+ position.x,
+ position.y,
+ position.x + view._width,
+ position.y + view._height
+ );
+ drawRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight()));
+ view.draw(bitmap.getBuffer(), drawRect, position, view._mirrorX);
+ return s->r_acc;
}
reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) {
@@ -618,9 +605,8 @@ reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) {
textRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight()));
reg_t textBitmapObject = g_sci->_gfxText32->createFontBitmap(textRect.width(), textRect.height(), Common::Rect(textRect.width(), textRect.height()), text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, false);
- Buffer bitmapBuffer(bitmap.getWidth(), bitmap.getHeight(), bitmap.getPixels());
CelObjMem textCel(textBitmapObject);
- textCel.draw(bitmapBuffer, textRect, Common::Point(textRect.left, textRect.top), false);
+ textCel.draw(bitmap.getBuffer(), textRect, Common::Point(textRect.left, textRect.top), false);
s->_segMan->freeHunkEntry(textBitmapObject);
return s->r_acc;
@@ -637,8 +623,7 @@ reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv) {
argv[4].toSint16() + 1
);
- Buffer buffer(bitmap.getWidth(), bitmap.getHeight(), bitmap.getPixels());
- buffer.fillRect(fillRect, argv[5].toSint16());
+ bitmap.getBuffer().fillRect(fillRect, argv[5].toSint16());
return s->r_acc;
}
@@ -702,46 +687,77 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv) {
}
reg_t kAddLine(EngineState *s, int argc, reg_t *argv) {
- return kStubNull(s, argc, argv); // return 0:0 for now, so that follow up calls won't create signature mismatches
-#if 0
- reg_t plane = argv[0];
- Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16());
- Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16());
- byte priority = (byte)argv[5].toUint16();
- byte color = (byte)argv[6].toUint16();
- byte style = (byte)argv[7].toUint16(); // 0: solid, 1: dashed, 2: pattern
- byte pattern = (byte)argv[8].toUint16();
- byte thickness = (byte)argv[9].toUint16();
-// return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, 0);
- return s->r_acc;
-#endif
+ const reg_t plane = argv[0];
+ const Common::Point startPoint(argv[1].toSint16(), argv[2].toSint16());
+ const Common::Point endPoint(argv[3].toSint16(), argv[4].toSint16());
+
+ int16 priority;
+ uint8 color;
+ LineStyle style;
+ uint16 pattern;
+ uint8 thickness;
+
+ if (argc == 10) {
+ priority = argv[5].toSint16();
+ color = (uint8)argv[6].toUint16();
+ style = (LineStyle)argv[7].toSint16();
+ pattern = argv[8].toUint16();
+ thickness = (uint8)argv[9].toUint16();
+ } else {
+ priority = 1000;
+ color = 255;
+ style = kLineStyleSolid;
+ pattern = 0;
+ thickness = 1;
+ }
+
+ return g_sci->_gfxPaint32->kernelAddLine(plane, startPoint, endPoint, priority, color, style, pattern, thickness);
}
reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
- return kStub(s, argc, argv);
+ const reg_t screenItemObject = argv[0];
+ const reg_t planeObject = argv[1];
+ const Common::Point startPoint(argv[2].toSint16(), argv[3].toSint16());
+ const Common::Point endPoint(argv[4].toSint16(), argv[5].toSint16());
+
+ int16 priority;
+ uint8 color;
+ LineStyle style;
+ uint16 pattern;
+ uint8 thickness;
+
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kUpdateLine: Plane %04x:%04x not found", PRINT_REG(planeObject));
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
+ if (screenItem == nullptr) {
+ error("kUpdateLine: Screen item %04x:%04x not found", PRINT_REG(screenItemObject));
+ }
+
+ if (argc == 11) {
+ priority = argv[6].toSint16();
+ color = (uint8)argv[7].toUint16();
+ style = (LineStyle)argv[8].toSint16();
+ pattern = argv[9].toUint16();
+ thickness = (uint8)argv[10].toUint16();
+ } else {
+ priority = screenItem->_priority;
+ color = screenItem->_celInfo.color;
+ style = kLineStyleSolid;
+ pattern = 0;
+ thickness = 1;
+ }
+
+ g_sci->_gfxPaint32->kernelUpdateLine(screenItem, plane, startPoint, endPoint, priority, color, style, pattern, thickness);
-#if 0
- reg_t hunkId = argv[0];
- reg_t plane = argv[1];
- Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16());
- Common::Point endPoint(argv[4].toUint16(), argv[5].toUint16());
- // argv[6] is unknown (a number, usually 200)
- byte color = (byte)argv[7].toUint16();
- byte priority = (byte)argv[8].toUint16();
- byte control = (byte)argv[9].toUint16();
- // argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
-// g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
return s->r_acc;
-#endif
}
+
reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) {
- return kStub(s, argc, argv);
-#if 0
- reg_t hunkId = argv[0];
- reg_t plane = argv[1];
-// g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
+ g_sci->_gfxPaint32->kernelDeleteLine(argv[0], argv[1]);
return s->r_acc;
-#endif
}
reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) {
@@ -910,57 +926,69 @@ reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
-reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
+reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) {
if (!s)
return make_reg(0, getSciVersion());
error("not supposed to call this");
}
-reg_t kRemapOff(EngineState *s, int argc, reg_t *argv) {
- byte color = (argc >= 1) ? argv[0].toUint16() : 0;
- g_sci->_gfxRemap32->remapOff(color);
+reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv) {
+ if (argc == 0) {
+ g_sci->_gfxRemap32->remapAllOff();
+ } else {
+ const uint8 color = argv[0].toUint16();
+ g_sci->_gfxRemap32->remapOff(color);
+ }
return s->r_acc;
}
-reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv) {
- byte color = argv[0].toUint16();
- byte from = argv[1].toUint16();
- byte to = argv[2].toUint16();
- byte base = argv[3].toUint16();
- // The last parameter, depth, is unused
- g_sci->_gfxRemap32->setRemappingRange(color, from, to, base);
+reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv) {
+ const uint8 color = argv[0].toUint16();
+ const int16 from = argv[1].toSint16();
+ const int16 to = argv[2].toSint16();
+ const int16 base = argv[3].toSint16();
+ // NOTE: There is an optional last parameter after `base`
+ // which was only used by the priority map debugger, which
+ // does not exist in release versions of SSCI
+ g_sci->_gfxRemap32->remapByRange(color, from, to, base);
return s->r_acc;
}
-reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv) {
- byte color = argv[0].toUint16();
- byte percent = argv[1].toUint16();
- // The last parameter, depth, is unused
- g_sci->_gfxRemap32->setRemappingPercent(color, percent);
+reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv) {
+ const uint8 color = argv[0].toUint16();
+ const int16 percent = argv[1].toSint16();
+ // NOTE: There is an optional last parameter after `percent`
+ // which was only used by the priority map debugger, which
+ // does not exist in release versions of SSCI
+ g_sci->_gfxRemap32->remapByPercent(color, percent);
return s->r_acc;
}
-reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv) {
- byte color = argv[0].toUint16();
- byte gray = argv[1].toUint16();
- // The last parameter, depth, is unused
- g_sci->_gfxRemap32->setRemappingToGray(color, gray);
+reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv) {
+ const uint8 color = argv[0].toUint16();
+ const int16 gray = argv[1].toSint16();
+ // NOTE: There is an optional last parameter after `gray`
+ // which was only used by the priority map debugger, which
+ // does not exist in release versions of SSCI
+ g_sci->_gfxRemap32->remapToGray(color, gray);
return s->r_acc;
}
-reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv) {
- byte color = argv[0].toUint16();
- byte gray = argv[1].toUint16();
- byte percent = argv[2].toUint16();
- // The last parameter, depth, is unused
- g_sci->_gfxRemap32->setRemappingToPercentGray(color, gray, percent);
+reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv) {
+ const uint8 color = argv[0].toUint16();
+ const int16 gray = argv[1].toSint16();
+ const int16 percent = argv[2].toSint16();
+ // NOTE: There is an optional last parameter after `percent`
+ // which was only used by the priority map debugger, which
+ // does not exist in release versions of SSCI
+ g_sci->_gfxRemap32->remapToPercentGray(color, gray, percent);
return s->r_acc;
}
-reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv) {
- byte from = argv[0].toUint16();
- byte count = argv[1].toUint16();
- g_sci->_gfxRemap32->setNoMatchRange(from, count);
+reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv) {
+ const uint8 from = argv[0].toUint16();
+ const uint8 count = argv[1].toUint16();
+ g_sci->_gfxRemap32->blockRange(from, count);
return s->r_acc;
}
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index f4bb4ff85b..0a5f26476f 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -243,10 +243,18 @@ reg_t kGetTime(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelTime, "GetTime(24h) returns %d", retval);
break;
case KGETTIME_DATE :
- // Year since 1980 (0 = 1980, 1 = 1981, etc.)
- retval = loc_time.tm_mday | ((loc_time.tm_mon + 1) << 5) | (((loc_time.tm_year - 80) & 0x7f) << 9);
+ {
+ // SCI0 late: Year since 1920 (0 = 1920, 1 = 1921, etc)
+ // SCI01 and newer: Year since 1980 (0 = 1980, 1 = 1981, etc)
+ // Atari ST SCI0 late versions use the newer base year.
+ int baseYear = 80;
+ if (getSciVersion() == SCI_VERSION_0_LATE && g_sci->getPlatform() == Common::kPlatformDOS) {
+ baseYear = 20;
+ }
+ retval = loc_time.tm_mday | ((loc_time.tm_mon + 1) << 5) | (((loc_time.tm_year - baseYear) & 0x7f) << 9);
debugC(kDebugLevelTime, "GetTime(date) returns %d", retval);
break;
+ }
default:
error("Attempt to use unknown GetTime mode %d", mode);
break;
diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp
index 7ac744f584..06f16299aa 100644
--- a/engines/sci/engine/kpathing.cpp
+++ b/engines/sci/engine/kpathing.cpp
@@ -326,7 +326,7 @@ static void draw_line(EngineState *s, Common::Point p1, Common::Point p2, int ty
p2.y = CLIP<int16>(p2.y, 0, height - 1);
assert(type >= 0 && type <= 3);
- g_sci->_gfxPaint->kernelGraphDrawLine(p1, p2, poly_colors[type], 255, 255);
+ g_sci->_gfxPaint16->kernelGraphDrawLine(p1, p2, poly_colors[type], 255, 255);
}
static void draw_point(EngineState *s, Common::Point p, int start, int width, int height) {
diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp
index 398a623286..ed53b8d52f 100644
--- a/engines/sci/engine/ksound.cpp
+++ b/engines/sci/engine/ksound.cpp
@@ -26,7 +26,11 @@
#include "sci/engine/kernel.h"
#include "sci/engine/vm.h" // for Object
#include "sci/sound/audio.h"
+#ifdef ENABLE_SCI32
+#include "sci/sound/audio32.h"
+#endif
#include "sci/sound/soundcmd.h"
+#include "sci/sound/sync.h"
#include "audio/mixer.h"
#include "common/system.h"
@@ -46,7 +50,6 @@ reg_t kDoSound(EngineState *s, int argc, reg_t *argv) {
CREATE_DOSOUND_FORWARD(DoSoundInit)
CREATE_DOSOUND_FORWARD(DoSoundPlay)
-CREATE_DOSOUND_FORWARD(DoSoundRestore)
CREATE_DOSOUND_FORWARD(DoSoundDispose)
CREATE_DOSOUND_FORWARD(DoSoundMute)
CREATE_DOSOUND_FORWARD(DoSoundStop)
@@ -61,13 +64,41 @@ CREATE_DOSOUND_FORWARD(DoSoundUpdateCues)
CREATE_DOSOUND_FORWARD(DoSoundSendMidi)
CREATE_DOSOUND_FORWARD(DoSoundGlobalReverb)
CREATE_DOSOUND_FORWARD(DoSoundSetHold)
-CREATE_DOSOUND_FORWARD(DoSoundDummy)
CREATE_DOSOUND_FORWARD(DoSoundGetAudioCapability)
CREATE_DOSOUND_FORWARD(DoSoundSuspend)
CREATE_DOSOUND_FORWARD(DoSoundSetVolume)
CREATE_DOSOUND_FORWARD(DoSoundSetPriority)
CREATE_DOSOUND_FORWARD(DoSoundSetLoop)
+#ifdef ENABLE_SCI32
+reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) {
+ // Phantasmagoria Mac (and seemingly no other game (!)) uses this
+ // cutdown version of kDoSound.
+
+ switch (argv[0].toUint16()) {
+ case 0:
+ return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc);
+ case 2:
+ return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc);
+ case 3:
+ return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc);
+ case 4:
+ return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc);
+ case 5:
+ return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc);
+ case 8:
+ return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc);
+ case 9:
+ return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc);
+ case 10:
+ return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc);
+ }
+
+ error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16());
+ return s->r_acc;
+}
+#endif
+
reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) {
switch (argv[0].toUint16()) {
case kSciAudioPlay: {
@@ -113,7 +144,8 @@ reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) {
}
/**
- * Used for speech playback and digital soundtracks in CD games
+ * Used for speech playback and digital soundtracks in CD games.
+ * This is the SCI16 version; SCI32 is handled separately.
*/
reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
// JonesCD uses different functions based on the cdaudio.map file
@@ -184,14 +216,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
int16 volume = argv[1].toUint16();
volume = CLIP<int16>(volume, 0, AUDIO_VOLUME_MAX);
debugC(kDebugLevelSound, "kDoAudio: set volume to %d", volume);
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
- int16 volumePrev = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2;
- volumePrev = CLIP<int16>(volumePrev, 0, AUDIO_VOLUME_MAX);
- mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);
- return make_reg(0, volumePrev);
- } else
-#endif
mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);
break;
}
@@ -232,12 +256,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
if (getSciVersion() <= SCI_VERSION_1_1) {
debugC(kDebugLevelSound, "kDoAudio: CD audio subop");
return kDoCdAudio(s, argc - 1, argv + 1);
-#ifdef ENABLE_SCI32
- } else {
- // TODO: This isn't CD Audio in SCI32 anymore
- warning("kDoAudio: Unhandled case 10, %d extra arguments passed", argc - 1);
- break;
-#endif
}
// 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C
@@ -286,14 +304,12 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
}
reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {
- SegManager *segMan = s->_segMan;
switch (argv[0].toUint16()) {
case kSciAudioSyncStart: {
ResourceId id;
- g_sci->_audio->stopSoundSync();
+ g_sci->_sync->stop();
- // Load sound sync resource and lock it
if (argc == 3) {
id = ResourceId(kResourceTypeSync, argv[2].toUint16());
} else if (argc == 7) {
@@ -304,14 +320,14 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
- g_sci->_audio->setSoundSync(id, argv[1], segMan);
+ g_sci->_sync->start(id, argv[1]);
break;
}
case kSciAudioSyncNext:
- g_sci->_audio->doSoundSync(argv[1], segMan);
+ g_sci->_sync->next(argv[1]);
break;
case kSciAudioSyncStop:
- g_sci->_audio->stopSoundSync();
+ g_sci->_sync->stop();
break;
default:
error("DoSync: Unhandled subfunction %d", argv[0].toUint16());
@@ -321,6 +337,155 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {
}
#ifdef ENABLE_SCI32
+reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, 0);
+}
+
+reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_audio32->kernelPlay(false, argc, argv);
+}
+
+reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_audio32->kernelPlay(true, argc, argv);
+}
+
+reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->stop(channelIndex));
+}
+
+reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->pause(channelIndex));
+}
+
+reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->resume(channelIndex));
+}
+
+reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->getPosition(channelIndex));
+}
+
+reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP sampling rate; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const uint16 sampleRate = argv[0].toUint16();
+ if (sampleRate != 0) {
+ g_sci->_audio32->setSampleRate(sampleRate);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getSampleRate());
+}
+
+reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv) {
+ const int16 volume = argc > 0 ? argv[0].toSint16() : -1;
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 1, argc > 2 ? argv[2] : NULL_REG);
+
+ if (volume != -1) {
+ g_sci->_audio32->setVolume(channelIndex, volume);
+ }
+
+ return make_reg(0, g_sci->_audio32->getVolume(channelIndex));
+}
+
+reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, 1);
+}
+
+reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP bit depth; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const uint16 bitDepth = argv[0].toUint16();
+ if (bitDepth != 0) {
+ g_sci->_audio32->setBitDepth(bitDepth);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getBitDepth());
+}
+
+reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv) {
+ if (argc > 0) {
+ g_sci->_audio32->setAttenuatedMixing(argv[0].toUint16());
+ }
+
+ return make_reg(0, g_sci->_audio32->getAttenuatedMixing());
+}
+
+reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP stereo output; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const int16 numChannels = argv[0].toSint16();
+ if (numChannels != 0) {
+ g_sci->_audio32->setNumOutputChannels(numChannels);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getNumOutputChannels());
+}
+
+reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would cause audio
+ // data for new channels to be preloaded to memory when
+ // the channel was initialized; we do not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ g_sci->_audio32->setPreload(argv[0].toUint16());
+ }
+
+ return make_reg(0, g_sci->_audio32->getPreload());
+}
+
+reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv) {
+ if (argc < 4) {
+ return make_reg(0, 0);
+ }
+
+ // NOTE: Sierra did a nightmarish hack here, temporarily replacing
+ // the argc of the kernel arguments with 2 and then restoring it
+ // after findChannelByArgs was called.
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(2, argv, 0, argc > 5 ? argv[5] : NULL_REG);
+
+ const int16 volume = argv[1].toSint16();
+ const int16 speed = argv[2].toSint16();
+ const int16 steps = argv[3].toSint16();
+ const bool stopAfterFade = argc > 4 ? (bool)argv[4].toUint16() : false;
+
+ return make_reg(0, g_sci->_audio32->fadeChannel(channelIndex, volume, speed, steps, stopAfterFade));
+}
+
+reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_audio32->hasSignal());
+}
+
+reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc == 3 ? argv[2] : NULL_REG);
+
+ const bool loop = argv[0].toSint16() != 0 && argv[0].toSint16() != 1;
+
+ g_sci->_audio32->setLoop(channelIndex, loop);
+ return s->r_acc;
+}
reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) {
// This is used by script 90 of MUMG Deluxe from the main menu to toggle
@@ -335,33 +500,6 @@ reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
-reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) {
- // Phantasmagoria Mac (and seemingly no other game (!)) uses this
- // cutdown version of kDoSound.
-
- switch (argv[0].toUint16()) {
- case 0:
- return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc);
- case 2:
- return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc);
- case 3:
- return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc);
- case 4:
- return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc);
- case 5:
- return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc);
- case 8:
- return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc);
- case 9:
- return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc);
- case 10:
- return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc);
- }
-
- error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16());
- return s->r_acc;
-}
-
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 0cc1e752e1..0972aec4a4 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -48,8 +48,9 @@
#include "sci/sound/music.h"
#ifdef ENABLE_SCI32
-#include "sci/graphics/palette32.h"
#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
#endif
namespace Sci {
@@ -806,8 +807,33 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) {
s.syncAsUint16LE(cycler->numTimesPaused);
}
}
+}
+
+void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) {
+ if (s.getVersion() < 35) {
+ return;
+ }
+
+ s.syncAsByte(_numActiveRemaps);
+ s.syncAsByte(_blockedRangeStart);
+ s.syncAsSint16LE(_blockedRangeCount);
+
+ for (uint i = 0; i < _remaps.size(); ++i) {
+ SingleRemap &singleRemap = _remaps[i];
+ s.syncAsByte(singleRemap._type);
+ if (s.isLoading() && singleRemap._type != kRemapNone) {
+ singleRemap.reset();
+ }
+ s.syncAsByte(singleRemap._from);
+ s.syncAsByte(singleRemap._to);
+ s.syncAsByte(singleRemap._delta);
+ s.syncAsByte(singleRemap._percent);
+ s.syncAsByte(singleRemap._gray);
+ }
- // TODO: _clutTable
+ if (s.isLoading()) {
+ _needsUpdate = true;
+ }
}
#endif
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 459e992e24..43909accf2 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,6 +37,7 @@ struct EngineState;
*
* Version - new/changed feature
* =============================
+ * 35 - SCI32 remap
* 34 - SCI32 palettes, and store play time in ticks
* 33 - new overridePriority flag in MusicEntry
* 32 - new playBed flag in MusicEntry
@@ -59,7 +60,7 @@ struct EngineState;
*/
enum {
- CURRENT_SAVEGAME_VERSION = 34,
+ CURRENT_SAVEGAME_VERSION = 35,
MINIMUM_SAVEGAME_VERSION = 14
};
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 66c4184c95..1302cc94ea 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -379,12 +379,40 @@ static const SciScriptPatcherEntry ecoquest2Signatures[] = {
};
// ===========================================================================
+// Fan-made games
+// Attention: Try to make script patches as specific as possible
+
+// CascadeQuest::autosave in script 994 is called various times to auto-save the game.
+// The script use a fixed slot "999" for this purpose. This doesn't work in ScummVM, because we do not let
+// scripts save directly into specific slots, but instead use virtual slots / detect scripts wanting to
+// create a new slot.
+//
+// For this game we patch the code to use slot 99 instead. kSaveGame also checks for Cascade Quest,
+// will then check, if slot 99 is asked for and will then use the actual slot 0, which is the official
+// ScummVM auto-save slot.
+//
+// Responsible method: CascadeQuest::autosave
+// Fixes bug: #7007
+static const uint16 fanmadeSignatureCascadeQuestFixAutoSaving[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x03e7), // pushi 3E7 (999d) -> save game slot 999
+ 0x74, SIG_UINT16(0x06f8), // lofss "AutoSave"
+ 0x89, 0x1e, // lsg global[1E]
+ 0x43, 0x2d, 0x08, // callk SaveGame
+ SIG_END
+};
+
+static const uint16 fanmadePatchCascadeQuestFixAutoSaving[] = {
+ 0x38, PATCH_UINT16((SAVEGAMEID_OFFICIALRANGE_START - 1)), // fix slot
+ PATCH_END
+};
+
// EventHandler::handleEvent in Demo Quest has a bug, and it jumps to the
// wrong address when an incorrect word is typed, therefore leading to an
// infinite loop. This script bug was not apparent in SSCI, probably because
// event handling was slightly different there, so it was never discovered.
// Fixes bug: #5120
-static const uint16 fanmadeSignatureInfiniteLoop[] = {
+static const uint16 fanmadeSignatureDemoQuestInfiniteLoop[] = {
0x38, SIG_UINT16(0x004c), // pushi 004c
0x39, 0x00, // pushi 00
0x87, 0x01, // lap 01
@@ -395,15 +423,16 @@ static const uint16 fanmadeSignatureInfiniteLoop[] = {
SIG_END
};
-static const uint16 fanmadePatchInfiniteLoop[] = {
+static const uint16 fanmadePatchDemoQuestInfiniteLoop[] = {
PATCH_ADDTOOFFSET(+10),
- 0x30, SIG_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c
+ 0x30, PATCH_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c
PATCH_END
};
-// script, description, signature patch
+// script, description, signature patch
static const SciScriptPatcherEntry fanmadeSignatures[] = {
- { true, 999, "infinite loop on typo", 1, fanmadeSignatureInfiniteLoop, fanmadePatchInfiniteLoop },
+ { true, 994, "Cascade Quest: fix auto-saving", 1, fanmadeSignatureCascadeQuestFixAutoSaving, fanmadePatchCascadeQuestFixAutoSaving },
+ { true, 999, "Demo Quest: infinite loop on typo", 1, fanmadeSignatureDemoQuestInfiniteLoop, fanmadePatchDemoQuestInfiniteLoop },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -1654,9 +1683,160 @@ static const uint16 laurabow1PatchEasterEggViewFix[] = {
PATCH_END
};
+// When oiling the armor or opening the visor of the armor, the scripts
+// first check if Laura/ego is near the armor and if she is not, they will move her
+// to the armor. After that further code is executed.
+//
+// The current location is checked by a ego::inRect() call.
+//
+// The given rect for the inRect call inside openVisor::changeState was made larger for Atari ST/Amiga versions.
+// We change the PC version to use the same rect.
+//
+// Additionally the coordinate, that Laura is moved to, is 152, 107 and may not be reachable depending on where
+// Laura/ego was, when "use oil on helmet of armor" / "open visor of armor" got entered.
+// Bad coordinates are for example 82, 110, which then cause collisions and effectively an endless loop.
+// Game will effectively "freeze" and the user is only able to restore a previous game.
+// This also happened, when using the original interpreter.
+// We change the destination coordinate to 152, 110, which seems to be reachable all the time.
+//
+// The following patch fixes the rect for the PC version of the game.
+//
+// Applies to at least: English PC Floppy
+// Responsible method: openVisor::changeState (script 37)
+// Fixes bug: #7119
+static const uint16 laurabow1SignatureArmorOpenVisorFix[] = {
+ 0x39, 0x04, // pushi 04
+ SIG_MAGICDWORD,
+ 0x39, 0x6a, // pushi 6a (106d)
+ 0x38, SIG_UINT16(0x96), // pushi 0096 (150d)
+ 0x39, 0x6c, // pushi 6c (108d)
+ 0x38, SIG_UINT16(0x98), // pushi 0098 (152d)
+ SIG_END
+};
+
+static const uint16 laurabow1PatchArmorOpenVisorFix[] = {
+ PATCH_ADDTOOFFSET(+2),
+ 0x39, 0x68, // pushi 68 (104d) (-2)
+ 0x38, SIG_UINT16(0x94), // pushi 0094 (148d) (-2)
+ 0x39, 0x6f, // pushi 6f (111d) (+3)
+ 0x38, SIG_UINT16(0x9a), // pushi 009a (154d) (+2)
+ PATCH_END
+};
+
+// This here fixes the destination coordinate (exact details are above).
+//
+// Applies to at least: English PC Floppy, English Atari ST Floppy, English Amiga Floppy
+// Responsible method: openVisor::changeState, oiling::changeState (script 37)
+// Fixes bug: #7119
+static const uint16 laurabow1SignatureArmorMoveToFix[] = {
+ SIG_MAGICDWORD,
+ 0x36, // push
+ 0x39, 0x6b, // pushi 6B (107d)
+ 0x38, SIG_UINT16(0x0098), // pushi 98 (152d)
+ 0x7c, // pushSelf
+ 0x81, 0x00, // lag global[0]
+ SIG_END
+};
+
+static const uint16 laurabow1PatchArmorMoveToFix[] = {
+ PATCH_ADDTOOFFSET(+1),
+ 0x39, 0x6e, // pushi 6E (110d) - adjust x, so that no collision can occur anymore
+ PATCH_END
+};
+
+// In some cases like for example when the player oils the arm of the armor, command input stays
+// disabled, even when the player exits fast enough, so that Laura doesn't die.
+//
+// This is caused by the scripts only enabling control (directional movement), but do not enable command input as well.
+//
+// This bug also happens, when using the original interpreter.
+// And it was fixed for the Atari ST + Amiga versions of the game.
+//
+// Applies to at least: English PC Floppy
+// Responsible method: 2nd subroutine in script 37, called by oiling::changeState(7)
+// Fixes bug: #7154
+static const uint16 laurabow1SignatureArmorOilingArmFix[] = {
+ 0x38, SIG_UINT16(0x0089), // pushi 89h
+ 0x76, // push0
+ SIG_MAGICDWORD,
+ 0x72, SIG_UINT16(0x1a5c), // lofsa "Can" - offsets are not skipped to make sure only the PC version gets patched
+ 0x4a, 0x04, // send 04
+ 0x38, SIG_UINT16(0x0089), // pushi 89h
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x19a1), // lofsa "Visor"
+ 0x4a, 0x04, // send 04
+ 0x38, SIG_UINT16(0x0089), // pushi 89h
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x194a), // lofsa "note"
+ 0x4a, 0x04, // send 04
+ 0x38, SIG_UINT16(0x0089), // pushi 89h
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x18f3), // lofsa "valve"
+ 0x4a, 0x04, // send 04
+ 0x8b, 0x34, // lsl local[34h]
+ 0x35, 0x02, // ldi 02
+ 0x1c, // ne?
+ 0x30, SIG_UINT16(0x0014), // bnt [to ret]
+ 0x8b, 0x34, // lsl local[34h]
+ 0x35, 0x05, // ldi 05
+ 0x1c, // ne?
+ 0x30, SIG_UINT16(0x000c), // bnt [to ret]
+ 0x8b, 0x34, // lsl local[34h]
+ 0x35, 0x06, // ldi 06
+ 0x1c, // ne?
+ 0x30, SIG_UINT16(0x0004), // bnt [to ret]
+ // followed by code to call script 0 export to re-enable controls and call setMotion
+ SIG_END
+};
+
+static const uint16 laurabow1PatchArmorOilingArmFix[] = {
+ PATCH_ADDTOOFFSET(+3), // skip over pushi 89h
+ 0x3c, // dup
+ 0x3c, // dup
+ 0x3c, // dup
+ // saves a total of 6 bytes
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x1a59), // lofsa "Can"
+ 0x4a, 0x04, // send 04
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x19a1), // lofsa "Visor"
+ 0x4a, 0x04, // send 04
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x194d), // lofsa "note"
+ 0x4a, 0x04, // send 04
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x18f9), // lofsa "valve" 18f3
+ 0x4a, 0x04, // send 04
+ // new code to enable input as well, needs 9 spare bytes
+ 0x38, SIG_UINT16(0x00e2), // canInput
+ 0x78, // push1
+ 0x78, // push1
+ 0x51, 0x2b, // class User
+ 0x4a, 0x06, // send 06 -> call User::canInput(1)
+ // original code, but changed a bit to save some more bytes
+ 0x8b, 0x34, // lsl local[34h]
+ 0x35, 0x02, // ldi 02
+ 0x04, // sub
+ 0x31, 0x12, // bnt [to ret]
+ 0x36, // push
+ 0x35, 0x03, // ldi 03
+ 0x04, // sub
+ 0x31, 0x0c, // bnt [to ret]
+ 0x78, // push1
+ 0x1a, // eq?
+ 0x2f, 0x08, // bt [to ret]
+ // saves 7 bytes, we only need 3, so waste 4 bytes
+ 0x35, 0x00, // ldi 0
+ 0x35, 0x00, // ldi 0
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry laurabow1Signatures[] = {
- { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix },
+ { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix },
+ { true, 37, "armor open visor fix", 1, laurabow1SignatureArmorOpenVisorFix, laurabow1PatchArmorOpenVisorFix },
+ { true, 37, "armor move to fix", 2, laurabow1SignatureArmorMoveToFix, laurabow1PatchArmorMoveToFix },
+ { true, 37, "allowing input, after oiling arm", 1, laurabow1SignatureArmorOilingArmFix, laurabow1PatchArmorOilingArmFix },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -2761,7 +2941,7 @@ static const uint16 qfg3PatchImportDialog[] = {
// Teller::doChild. We jump to this call of hero::solvePuzzle to get that same
// behaviour.
// Applies to at least: English, German, Italian, French, Spanish Floppy
-// Responsible method: unknown
+// Responsible method: uhuraTell::doChild
// Fixes bug: #5172
static const uint16 qfg3SignatureWooDialog[] = {
SIG_MAGICDWORD,
@@ -3597,24 +3777,35 @@ static const uint16 sq1vgaSignatureSpiderDroidTiming[] = {
0x30, SIG_UINT16(0x0005), // bnt [further method code]
0x35, 0x00, // ldi 00
0x32, SIG_UINT16(0x0052), // jmp [super-call]
- 0x89, 0xa6, // lsg global[a6]
+ 0x89, 0xa6, // lsg global[a6] <-- flag gets set to 1 when ego went up the skeleton tail, when going down it's set to 2
0x35, 0x01, // ldi 01
0x1a, // eq?
- 0x30, SIG_UINT16(0x0012), // bnt [2nd code], in case global A6 <> 1
+ 0x30, SIG_UINT16(0x0012), // bnt [PChase set code], in case global A6 <> 1
0x81, 0xb5, // lag global[b5]
- 0x30, SIG_UINT16(0x000d), // bnt [2nd code], in case global B5 == 0
+ 0x30, SIG_UINT16(0x000d), // bnt [PChase set code], in case global B5 == 0
0x38, SIG_UINT16(0x008c), // pushi 008c
0x78, // push1
0x72, SIG_UINT16(0x1cb6), // lofsa 1CB6 (moveToPath)
0x36, // push
0x54, 0x06, // self 06
0x32, SIG_UINT16(0x0038), // jmp [super-call]
+ // PChase set call
0x81, 0xb5, // lag global[B5]
0x18, // not
0x30, SIG_UINT16(0x0032), // bnt [super-call], in case global B5 <> 0
+ // followed by:
+ // is spider in current room
+ // is global A6h == 2? -> set PChase
SIG_END
}; // 58 bytes)
+// Global A6h <> 1 (did NOT went up the skeleton)
+// Global B5h = 0 -> set PChase
+// Global B5h <> 0 -> do not do anything
+// Global A6h = 1 (did went up the skeleton)
+// Global B5h = 0 -> set PChase
+// Global B5h <> 0 -> set moveToPath
+
static const uint16 sq1vgaPatchSpiderDroidTiming[] = {
0x63, 0x4e, // pToa script
0x2f, 0x68, // bt [super-call]
@@ -3639,8 +3830,8 @@ static const uint16 sq1vgaPatchSpiderDroidTiming[] = {
0x65, 0x4c, // aTop cycleSpeed
0x65, 0x5e, // aTop moveSpeed
// new code end
- 0x89, 0xb5, // lsg global[B5]
- 0x31, 0x13, // bnt [2nd code chunk]
+ 0x81, 0xb5, // lag global[B5]
+ 0x31, 0x13, // bnt [PChase code chunk]
0x89, 0xa6, // lsg global[A6]
0x35, 0x01, // ldi 01
0x1a, // eq?
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 0cb8ff48d7..073bb93983 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -514,6 +514,14 @@ const SciWorkaroundEntry kDisposeScript_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kDoSoundPlay_workarounds[] = {
+ { GID_LSL6HIRES, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+ { GID_QFG4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+ { GID_PQ4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kDoSoundFade_workarounds[] = {
{ GID_KQ5, 213, 989, 0, "globalSound3", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when bandits leave the secret temple, parameter 4 is an object - bug #5078
{ GID_KQ6, 105, 989, 0, "globalSound", "fade", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // floppy: during intro, parameter 4 is an object
@@ -745,6 +753,12 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = {
SCI_WORKAROUNDENTRY_TERMINATOR
};
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kScrollWindowAdd_workarounds[] = {
+ { GID_PHANTASMAGORIA, 45, 64907, 0, "ScrollableWindow", "addString", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // ScrollWindow interface passes the last two parameters twice
+};
+
+
SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) {
// HACK for SCI3: Temporarily ignore this
if (getSciVersion() == SCI_VERSION_3) {
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 8f519a8c9c..248d37fc6c 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -74,6 +74,7 @@ extern const SciWorkaroundEntry kDeviceInfo_workarounds[];
extern const SciWorkaroundEntry kDisplay_workarounds[];
extern const SciWorkaroundEntry kDirLoop_workarounds[];
extern const SciWorkaroundEntry kDisposeScript_workarounds[];
+extern const SciWorkaroundEntry kDoSoundPlay_workarounds[];
extern const SciWorkaroundEntry kDoSoundFade_workarounds[];
extern const SciWorkaroundEntry kFindKey_workarounds[];
extern const SciWorkaroundEntry kDeleteKey_workarounds[];
@@ -98,6 +99,7 @@ extern const SciWorkaroundEntry kStrAt_workarounds[];
extern const SciWorkaroundEntry kStrCpy_workarounds[];
extern const SciWorkaroundEntry kStrLen_workarounds[];
extern const SciWorkaroundEntry kUnLoad_workarounds[];
+extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[];
extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin);
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 77d333a717..befa5cda18 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -26,13 +26,12 @@
#include "sci/graphics/celobj32.h"
#include "sci/graphics/frameout.h"
#include "sci/graphics/palette32.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/text32.h"
-#include "sci/graphics/view.h"
namespace Sci {
#pragma mark CelScaler
+
CelScaler *CelObj::_scaler = nullptr;
void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
@@ -323,6 +322,10 @@ public:
#pragma mark -
#pragma mark CelObj - Remappers
+/**
+ * Pixel mapper for a CelObj with transparent pixels and no
+ * remapping data.
+ */
struct MAPPER_NoMD {
inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
if (pixel != skipColor) {
@@ -330,25 +333,49 @@ struct MAPPER_NoMD {
}
}
};
+
+/**
+ * Pixel mapper for a CelObj with no transparent pixels and
+ * no remapping data.
+ */
struct MAPPER_NoMDNoSkip {
inline void draw(byte *target, const byte pixel, const uint8) const {
*target = pixel;
}
};
+/**
+ * Pixel mapper for a CelObj with transparent pixels,
+ * remapping data, and remapping enabled.
+ */
struct MAPPER_Map {
inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
if (pixel != skipColor) {
+ // NOTE: For some reason, SSCI never checks if the source
+ // pixel is *above* the range of remaps.
if (pixel < g_sci->_gfxRemap32->getStartColor()) {
*target = pixel;
- } else {
- if (g_sci->_gfxRemap32->remapEnabled(pixel))
- *target = g_sci->_gfxRemap32->remapColor(pixel, *target);
+ } else if (g_sci->_gfxRemap32->remapEnabled(pixel)) {
+ *target = g_sci->_gfxRemap32->remapColor(pixel, *target);
}
}
}
};
+/**
+ * Pixel mapper for a CelObj with transparent pixels,
+ * remapping data, and remapping disabled.
+ */
+struct MAPPER_NoMap {
+ inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+ // NOTE: For some reason, SSCI never checks if the source
+ // pixel is *above* the range of remaps.
+ if (pixel != skipColor && pixel < g_sci->_gfxRemap32->getStartColor()) {
+ *target = pixel;
+ }
+ }
+};
+
void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
const Common::Point &scaledPosition = screenItem._scaledPosition;
const Ratio &scaleX = screenItem._ratioX;
@@ -523,6 +550,7 @@ void CelObj::submitPalette() const {
#pragma mark -
#pragma mark CelObj - Caching
+
int CelObj::_nextCacheId = 1;
CelCache *CelObj::_cache = nullptr;
@@ -624,33 +652,35 @@ void dummyFill(Buffer &target, const Common::Rect &targetRect) {
}
void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawHzFlip");
- dummyFill(target, targetRect);
+ render<MAPPER_NoMap, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
}
void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawNoFlip");
- dummyFill(target, targetRect);
+ render<MAPPER_NoMap, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
}
void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawUncompNoFlip");
- dummyFill(target, targetRect);
+ render<MAPPER_NoMap, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("drawUncompHzFlip");
- dummyFill(target, targetRect);
+ render<MAPPER_NoMap, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
}
void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("scaleDraw");
- dummyFill(target, targetRect);
+ if (_drawMirrored) {
+ render<MAPPER_NoMap, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_NoMap, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- debug("scaleDrawUncomp");
- dummyFill(target, targetRect);
+ if (_drawMirrored) {
+ render<MAPPER_NoMap, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ } else {
+ render<MAPPER_NoMap, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
@@ -670,17 +700,19 @@ void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect,
}
void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- if (_drawMirrored)
+ if (_drawMirrored) {
render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
- else
+ } else {
render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- if (_drawMirrored)
+ if (_drawMirrored) {
render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
- else
+ } else {
render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
@@ -715,14 +747,16 @@ void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &sca
}
void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
- if (_drawMirrored)
+ if (_drawMirrored) {
render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
- else
+ } else {
render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+ }
}
#pragma mark -
#pragma mark CelObjView
+
CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
_info.type = kCelTypeView;
_info.resourceId = viewId;
@@ -765,8 +799,8 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
if (_scaledWidth == 0 || _scaledHeight == 0) {
byte sizeFlag = data[5];
if (sizeFlag == 0) {
- _scaledWidth = 320;
- _scaledHeight = 200;
+ _scaledWidth = kLowResX;
+ _scaledHeight = kLowResY;
} else if (sizeFlag == 1) {
_scaledWidth = 640;
_scaledHeight = 480;
@@ -840,8 +874,12 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
bool CelObjView::analyzeUncompressedForRemap() const {
byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
for (int i = 0; i < _width * _height; ++i) {
- byte pixel = pixels[i];
- if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) {
+ const byte pixel = pixels[i];
+ if (
+ pixel >= g_sci->_gfxRemap32->getStartColor() &&
+ pixel <= g_sci->_gfxRemap32->getEndColor() &&
+ pixel != _transparentColor
+ ) {
return true;
}
}
@@ -853,8 +891,12 @@ bool CelObjView::analyzeForRemap() const {
for (int y = 0; y < _height; y++) {
const byte *curRow = reader.getRow(y);
for (int x = 0; x < _width; x++) {
- byte pixel = curRow[x];
- if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) {
+ const byte pixel = curRow[x];
+ if (
+ pixel >= g_sci->_gfxRemap32->getStartColor() &&
+ pixel <= g_sci->_gfxRemap32->getEndColor() &&
+ pixel != _transparentColor
+ ) {
return true;
}
}
@@ -881,6 +923,7 @@ byte *CelObjView::getResPointer() const {
#pragma mark -
#pragma mark CelObjPic
+
CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_info.type = kCelTypePic;
_info.resourceId = picId;
@@ -942,8 +985,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_scaledWidth = sizeFlag1;
_scaledHeight = sizeFlag2;
} else if (sizeFlag1 == 0) {
- _scaledWidth = 320;
- _scaledHeight = 200;
+ _scaledWidth = kLowResX;
+ _scaledHeight = kLowResY;
} else if (sizeFlag1 == 1) {
_scaledWidth = 640;
_scaledHeight = 480;
@@ -1002,6 +1045,7 @@ byte *CelObjPic::getResPointer() const {
#pragma mark -
#pragma mark CelObjMem
+
CelObjMem::CelObjMem(const reg_t bitmapObject) {
_info.type = kCelTypeMem;
_info.bitmap = bitmapObject;
@@ -1031,6 +1075,7 @@ byte *CelObjMem::getResPointer() const {
#pragma mark -
#pragma mark CelObjColor
+
CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
_info.type = kCelTypeColor;
_info.color = color;
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
index 6e401b3df4..e405592b5f 100644
--- a/engines/sci/graphics/celobj32.h
+++ b/engines/sci/graphics/celobj32.h
@@ -31,6 +31,20 @@
namespace Sci {
typedef Common::Rational Ratio;
+// SCI32 has four different coordinate systems:
+// 1. low resolution, 2. game/script resolution,
+// 3. text/bitmap resolution, 4. screen resolution
+//
+// In CelObj, these values are used when there is
+// no baked in resolution of cels.
+//
+// In ScreenItem, it is used when deciding which
+// path to take to calculate dimensions.
+enum {
+ kLowResX = 320,
+ kLowResY = 200
+};
+
enum CelType {
kCelTypeView = 0,
kCelTypePic = 1,
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index faf1d7d1a2..61dfbedfc5 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -21,7 +21,8 @@
*/
#include "common/system.h"
-
+#include "common/translation.h"
+#include "gui/message.h"
#include "sci/sci.h"
#include "sci/console.h"
#include "sci/event.h"
@@ -41,7 +42,31 @@ GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *tex
_gfxCache(cache),
_gfxText32(text),
_overwriteMode(false),
- _nextCursorFlashTick(0) {}
+ _nextCursorFlashTick(0),
+ // SSCI used a memory handle for a ScrollWindow object
+ // as ID. We use a simple numeric handle instead.
+ _nextScrollWindowId(10000) {}
+
+GfxControls32::~GfxControls32() {
+ ScrollWindowMap::iterator it;
+ for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
+ delete it->_value;
+}
+
+#pragma mark -
+#pragma mark Garbage collection
+
+Common::Array<reg_t> GfxControls32::listObjectReferences() {
+ Common::Array<reg_t> ret;
+ ScrollWindowMap::const_iterator it;
+ for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
+ ret.push_back(it->_value->getBitmap());
+
+ return ret;
+}
+
+#pragma mark -
+#pragma mark Text input control
reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
SegManager *segMan = _segMan;
@@ -350,4 +375,470 @@ void GfxControls32::flashCursor(TextEditor &editor) {
_nextCursorFlashTick = g_sci->getTickCount() + 30;
}
}
+
+#pragma mark -
+#pragma mark Scrollable window control
+
+ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) :
+ _gfxText32(segMan, g_sci->_gfxCache),
+ _maxNumEntries(maxNumEntries),
+ _firstVisibleChar(0),
+ _topVisibleLine(0),
+ _lastVisibleChar(0),
+ _bottomVisibleLine(0),
+ _numLines(0),
+ _numVisibleLines(0),
+ _plane(plane),
+ _foreColor(defaultForeColor),
+ _backColor(defaultBackColor),
+ _borderColor(defaultBorderColor),
+ _fontId(defaultFontId),
+ _alignment(defaultAlignment),
+ _visible(false),
+ _position(position),
+ _screenItem(nullptr),
+ _nextEntryId(1) {
+
+ _entries.reserve(maxNumEntries);
+
+ _gfxText32.setFont(_fontId);
+ _pointSize = _gfxText32._font->getHeight();
+
+ const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Common::Rect bitmapRect(gameRect);
+ mulinc(bitmapRect, Ratio(_gfxText32._scaledWidth, scriptWidth), Ratio(_gfxText32._scaledHeight, scriptHeight));
+
+ _textRect.left = 2;
+ _textRect.top = 2;
+ _textRect.right = bitmapRect.width() - 2;
+ _textRect.bottom = bitmapRect.height() - 2;
+
+ uint8 skipColor = 0;
+ while (skipColor == _foreColor || skipColor == _backColor) {
+ skipColor++;
+ }
+
+ assert(bitmapRect.width() > 0 && bitmapRect.height() > 0);
+ _bitmap = _gfxText32.createFontBitmap(bitmapRect.width(), bitmapRect.height(), _textRect, "", _foreColor, _backColor, skipColor, _fontId, _alignment, _borderColor, false, false);
+
+ debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap));
+}
+
+ScrollWindow::~ScrollWindow() {
+ // _gfxText32._bitmap will get GCed once ScrollWindow is gone.
+ // _screenItem will be deleted by GfxFrameout
+}
+
+Ratio ScrollWindow::where() const {
+ return Ratio(_topVisibleLine, MAX(_numLines, 1));
+}
+
+void ScrollWindow::show() {
+ if (_visible) {
+ return;
+ }
+
+ if (_screenItem == nullptr) {
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _bitmap;
+
+ _screenItem = new ScreenItem(_plane, celInfo, _position, ScaleInfo());
+ }
+
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
+ plane->_screenItemList.add(_screenItem);
+
+ _visible = true;
+}
+
+void ScrollWindow::hide() {
+ if (!_visible) {
+ return;
+ }
+
+ g_sci->_gfxFrameout->deleteScreenItem(_screenItem, _plane);
+ _screenItem = nullptr;
+ g_sci->_gfxFrameout->frameOut(true);
+
+ _visible = false;
+}
+
+reg_t ScrollWindow::add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
+ if (_entries.size() == _maxNumEntries) {
+ ScrollWindowEntry removedEntry = _entries.remove_at(0);
+ _text.erase(0, removedEntry.text.size());
+ // `_firstVisibleChar` will be reset shortly if
+ // `scrollTo` is true, so there is no reason to
+ // update it
+ if (!scrollTo) {
+ _firstVisibleChar -= removedEntry.text.size();
+ }
+ }
+
+ _entries.push_back(ScrollWindowEntry());
+ ScrollWindowEntry &entry = _entries.back();
+
+ // NOTE: In SSCI the line ID was a memory handle for the
+ // string of this line. We use a numeric ID instead.
+ entry.id = make_reg(0, _nextEntryId++);
+
+ if (_nextEntryId > _maxNumEntries) {
+ _nextEntryId = 1;
+ }
+
+ // NOTE: In SSCI this was updated after _text was
+ // updated, which meant there was an extra unnecessary
+ // subtraction operation (subtracting `entry.text` size)
+ if (scrollTo) {
+ _firstVisibleChar = _text.size();
+ }
+
+ fillEntry(entry, text, fontId, foreColor, alignment);
+ _text += entry.text;
+
+ computeLineIndices();
+ update(true);
+
+ return entry.id;
+}
+
+void ScrollWindow::fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment) {
+ entry.alignment = alignment;
+ entry.foreColor = foreColor;
+ entry.fontId = fontId;
+
+ Common::String formattedText;
+
+ // NB: There are inconsistencies here.
+ // If there is a multi-line entry with non-default properties, and it
+ // is only partially displayed, it may not be displayed right, since the
+ // property directives are only added to the first line.
+ // (Verified by trying this in SSCI SQ6 with a custom ScrollWindowAdd call.)
+ //
+ // The converse is also a potential issue (but unverified), where lines
+ // with properties -1 can inherit properties from the previously rendered
+ // line instead of the defaults.
+
+ // NOTE: SSCI added "|s<lineIndex>|" here, but |s| is
+ // not a valid control code, so it just always ended up
+ // getting skipped
+ if (entry.fontId != -1) {
+ formattedText += Common::String::format("|f%d|", entry.fontId);
+ }
+ if (entry.foreColor != -1) {
+ formattedText += Common::String::format("|c%d|", entry.foreColor);
+ }
+ if (entry.alignment != -1) {
+ formattedText += Common::String::format("|a%d|", entry.alignment);
+ }
+ formattedText += text;
+ entry.text = formattedText;
+}
+
+reg_t ScrollWindow::modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
+
+ EntriesList::iterator it = _entries.begin();
+ uint firstCharLocation = 0;
+ for ( ; it != _entries.end(); ++it) {
+ if (it->id == id) {
+ break;
+ }
+ firstCharLocation += it->text.size();
+ }
+
+ if (it == _entries.end()) {
+ return make_reg(0, 0);
+ }
+
+ ScrollWindowEntry &entry = *it;
+
+ uint oldTextLength = entry.text.size();
+
+ fillEntry(entry, text, fontId, foreColor, alignment);
+ _text.replace(firstCharLocation, oldTextLength, entry.text);
+
+ if (scrollTo) {
+ _firstVisibleChar = firstCharLocation;
+ }
+
+ computeLineIndices();
+ update(true);
+
+ return entry.id;
+}
+
+void ScrollWindow::upArrow() {
+ if (_topVisibleLine == 0) {
+ return;
+ }
+
+ _topVisibleLine--;
+ _bottomVisibleLine--;
+
+ if (_bottomVisibleLine - _topVisibleLine + 1 < _numVisibleLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ Common::String lineText(_text.c_str() + _startsOfLines[_topVisibleLine], _text.c_str() + _startsOfLines[_topVisibleLine + 1] - 1);
+
+ debugC(3, kDebugLevelGraphics, "ScrollWindow::upArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
+
+ _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollUp);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+}
+
+void ScrollWindow::downArrow() {
+ if (_topVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ _topVisibleLine++;
+ _bottomVisibleLine++;
+
+ if (_bottomVisibleLine + 1 >= _numLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ Common::String lineText;
+ if (_bottomVisibleLine - _topVisibleLine + 1 == _numVisibleLines) {
+ lineText = Common::String(_text.c_str() + _startsOfLines[_bottomVisibleLine], _text.c_str() + _startsOfLines[_bottomVisibleLine + 1] - 1);
+ } else {
+ // scroll in empty string
+ }
+
+ debugC(3, kDebugLevelGraphics, "ScrollWindow::downArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
+
+
+ _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollDown);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+}
+
+void ScrollWindow::go(const Ratio location) {
+ const int line = (location * _numLines).toInt();
+ if (line < 0 || line > _numLines) {
+ error("Index is Out of Range in ScrollWindow");
+ }
+
+ _firstVisibleChar = _startsOfLines[line];
+ update(true);
+
+ // HACK:
+ // It usually isn't possible to set _topVisibleLine >= _numLines, and so
+ // update() doesn't. However, in this case we should set _topVisibleLine
+ // past the end. This is clearly visible in Phantasmagoria when dragging
+ // the slider in the About dialog to the very end. The slider ends up lower
+ // than where it can be moved by scrolling down with the arrows.
+ if (location.isOne()) {
+ _topVisibleLine = _numLines;
+ }
+}
+
+void ScrollWindow::home() {
+ if (_firstVisibleChar == 0) {
+ return;
+ }
+
+ _firstVisibleChar = 0;
+ update(true);
+}
+
+void ScrollWindow::end() {
+ if (_bottomVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ int line = _numLines - _numVisibleLines;
+ if (line < 0) {
+ line = 0;
+ }
+ _firstVisibleChar = _startsOfLines[line];
+ update(true);
+}
+
+void ScrollWindow::pageUp() {
+ if (_topVisibleLine == 0) {
+ return;
+ }
+
+ _topVisibleLine -= _numVisibleLines;
+ if (_topVisibleLine < 0) {
+ _topVisibleLine = 0;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ update(true);
+}
+
+void ScrollWindow::pageDown() {
+ if (_topVisibleLine + 1 >= _numLines) {
+ return;
+ }
+
+ _topVisibleLine += _numVisibleLines;
+ if (_topVisibleLine + 1 >= _numLines) {
+ _topVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+ update(true);
+}
+
+void ScrollWindow::computeLineIndices() {
+ _gfxText32.setFont(_fontId);
+ // NOTE: Unlike SSCI, foreColor and alignment are not
+ // set since these properties do not affect the width of
+ // lines
+
+ if (_gfxText32._font->getHeight() != _pointSize) {
+ error("Illegal font size font = %d pointSize = %d, should be %d.", _fontId, _gfxText32._font->getHeight(), _pointSize);
+ }
+
+ Common::Rect lineRect(0, 0, _textRect.width(), _pointSize + 3);
+
+ _startsOfLines.clear();
+
+ // NOTE: The original engine had a 1000-line limit; we
+ // do not enforce any limit
+ for (uint charIndex = 0; charIndex < _text.size(); ) {
+ _startsOfLines.push_back(charIndex);
+ charIndex += _gfxText32.getTextCount(_text, charIndex, lineRect, false);
+ }
+
+ _numLines = _startsOfLines.size();
+
+ _startsOfLines.push_back(_text.size());
+
+ _lastVisibleChar = _gfxText32.getTextCount(_text, 0, _fontId, _textRect, false) - 1;
+
+ _bottomVisibleLine = 0;
+ while (
+ _bottomVisibleLine < _numLines - 1 &&
+ _startsOfLines[_bottomVisibleLine + 1] < _lastVisibleChar
+ ) {
+ ++_bottomVisibleLine;
+ }
+
+ _numVisibleLines = _bottomVisibleLine + 1;
+}
+
+void ScrollWindow::update(const bool doFrameOut) {
+ _topVisibleLine = 0;
+ while (
+ _topVisibleLine < _numLines - 1 &&
+ _firstVisibleChar >= _startsOfLines[_topVisibleLine + 1]
+ ) {
+ ++_topVisibleLine;
+ }
+
+ _bottomVisibleLine = _topVisibleLine + _numVisibleLines - 1;
+ if (_bottomVisibleLine >= _numLines) {
+ _bottomVisibleLine = _numLines - 1;
+ }
+
+ _firstVisibleChar = _startsOfLines[_topVisibleLine];
+
+ if (_bottomVisibleLine >= 0) {
+ _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
+ } else {
+ _lastVisibleChar = -1;
+ }
+
+ _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
+
+ _gfxText32.erase(_textRect, false);
+ _gfxText32.drawTextBox(_visibleText);
+
+ if (_visible) {
+ assert(_screenItem);
+
+ _screenItem->update();
+ if (doFrameOut) {
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+ }
+}
+
+reg_t GfxControls32::makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) {
+
+ ScrollWindow *scrollWindow = new ScrollWindow(_segMan, gameRect, position, planeObj, defaultForeColor, defaultBackColor, defaultFontId, defaultAlignment, defaultBorderColor, maxNumEntries);
+
+ const uint16 id = _nextScrollWindowId++;
+ _scrollWindows[id] = scrollWindow;
+ return make_reg(0, id);
+}
+
+ScrollWindow *GfxControls32::getScrollWindow(const reg_t id) {
+ ScrollWindowMap::iterator it;
+ it = _scrollWindows.find(id.toUint16());
+ if (it == _scrollWindows.end())
+ error("Invalid ScrollWindow ID");
+
+ return it->_value;
+}
+
+void GfxControls32::destroyScrollWindow(const reg_t id) {
+ ScrollWindow *scrollWindow = getScrollWindow(id);
+ scrollWindow->hide();
+ _scrollWindows.erase(id.getOffset());
+ delete scrollWindow;
+}
+
+#pragma mark -
+#pragma mark Message box
+
+int16 GfxControls32::showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue) {
+ GUI::MessageDialog dialog(message, okLabel, altLabel);
+ return (dialog.runModal() == GUI::kMessageOK) ? okValue : altValue;
+}
+
+reg_t GfxControls32::kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style) {
+ if (g_engine) {
+ g_engine->pauseEngine(true);
+ }
+
+ int16 result;
+
+ switch (style & 0xF) {
+ case kMessageBoxOK:
+ result = showMessageBox(message, _("OK"), NULL, 1, 1);
+ break;
+ case kMessageBoxYesNo:
+ result = showMessageBox(message, _("Yes"), _("No"), 6, 7);
+ break;
+ default:
+ error("Unsupported MessageBox style 0x%x", style & 0xF);
+ }
+
+ if (g_engine) {
+ g_engine->pauseEngine(false);
+ }
+
+ return make_reg(0, result);
+}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h
index 1bb7679ddd..460b0b5625 100644
--- a/engines/sci/graphics/controls32.h
+++ b/engines/sci/graphics/controls32.h
@@ -23,12 +23,19 @@
#ifndef SCI_GRAPHICS_CONTROLS32_H
#define SCI_GRAPHICS_CONTROLS32_H
+#include "sci/graphics/text32.h"
+
namespace Sci {
class GfxCache;
class GfxScreen;
class GfxText32;
+enum MessageBoxStyle {
+ kMessageBoxOK = 0x0,
+ kMessageBoxYesNo = 0x4
+};
+
struct TextEditor {
/**
* The bitmap where the editor is rendered.
@@ -100,24 +107,403 @@ struct TextEditor {
};
/**
+ * A single block of text written to a ScrollWindow.
+ */
+struct ScrollWindowEntry {
+ /**
+ * ID of the line. In SSCI this was actually a memory
+ * handle for the string of this line. We use a simple
+ * numeric ID instead.
+ */
+ reg_t id;
+
+ /**
+ * The alignment to use when rendering this line of
+ * text. If -1, the default alignment from the
+ * corresponding ScrollWindow will be used.
+ */
+ TextAlign alignment;
+
+ /**
+ * The color to use to render this line of text. If -1,
+ * the default foreground color from the corresponding
+ * ScrollWindow will be used.
+ */
+ int16 foreColor;
+
+ /**
+ * The font to use to render this line of text. If -1,
+ * the default font from the corresponding ScrollWindow
+ * will be used.
+ */
+ GuiResourceId fontId;
+
+ /**
+ * The text.
+ */
+ Common::String text;
+};
+
+class ScreenItem;
+
+/**
+ * A scrollable text window.
+ */
+class ScrollWindow {
+public:
+ ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries);
+ ~ScrollWindow();
+
+ /**
+ * Adds a new text entry to the window. If `fontId`,
+ * `foreColor`, or `alignment` are `-1`, the
+ * ScrollWindow's default values will be used.
+ */
+ reg_t add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo);
+
+ /**
+ * Modifies an existing text entry with the given ID. If
+ * `fontId`, `foreColor`, or `alignment` are `-1`, the
+ * ScrollWindow's default values will be used.
+ */
+ reg_t modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo);
+
+ /**
+ * Shows the ScrollWindow if it is not already visible.
+ */
+ void show();
+
+ /**
+ * Hides the ScrollWindow if it is currently visible.
+ */
+ void hide();
+
+ /**
+ * Gets the number of lines that the content of a
+ * ScrollWindow is scrolled upward, as a ratio of the
+ * total number of lines of content.
+ */
+ Ratio where() const;
+
+ /**
+ * Scrolls the window to a specific location.
+ */
+ void go(const Ratio location);
+
+ /**
+ * Scrolls the window to the top.
+ */
+ void home();
+
+ /**
+ * Scrolls the window to the bottom.
+ */
+ void end();
+
+ /**
+ * Scrolls the window up one line.
+ */
+ void upArrow();
+
+ /**
+ * Scrolls the window down one line.
+ */
+ void downArrow();
+
+ /**
+ * Scrolls the window up by one page.
+ */
+ void pageUp();
+
+ /**
+ * Scrolls the window down by one page.
+ */
+ void pageDown();
+
+ /**
+ * Gets a reference to the in-memory bitmap that
+ * is used to render the text in the ScrollWindow.
+ */
+ const reg_t getBitmap() const { return _bitmap; }
+
+private:
+ typedef Common::Array<ScrollWindowEntry> EntriesList;
+
+ /**
+ * A convenience function that fills a
+ * ScrollWindowEntry's properties.
+ */
+ void fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment);
+
+ /**
+ * Rescans the entire text of the ScrollWindow when an
+ * entry is added or modified, calculating the character
+ * offsets of all line endings, the total number of
+ * lines of text, the height of the viewport (in lines
+ * of text), the last character visible in the viewport
+ * (assuming the viewport is scrolled to the top), and
+ * the line index of the bottommost visible line
+ * (assuming the viewport is scrolled to the top).
+ */
+ void computeLineIndices();
+
+ /**
+ * Calculates which text is visible within the
+ * ScrollWindow's viewport and renders the text to the
+ * internal bitmap.
+ *
+ * If `doFrameOut` is true, the screen will be refreshed
+ * immediately instead of waiting for the next call to
+ * `kFrameOut`.
+ */
+ void update(const bool doFrameOut);
+
+ /**
+ * The text renderer.
+ */
+ GfxText32 _gfxText32;
+
+ /**
+ * The individual text entries added to the
+ * ScrollWindow.
+ */
+ EntriesList _entries;
+
+ /**
+ * The maximum number of entries allowed. Once this
+ * limit is reached, the oldest entry will be removed
+ * when a new entry is added.
+ */
+ uint _maxNumEntries;
+
+ /**
+ * A mapping from a line index to the line's character
+ * offset in `_text`.
+ */
+ Common::Array<int> _startsOfLines;
+
+ /**
+ * All text added to the window.
+ */
+ Common::String _text;
+
+ /**
+ * Text that is within the viewport of the ScrollWindow.
+ */
+ Common::String _visibleText;
+
+ /**
+ * The offset of the first visible character in `_text`.
+ */
+ int _firstVisibleChar;
+
+ /**
+ * The index of the line that is at the top of the
+ * viewport.
+ */
+ int _topVisibleLine;
+
+ /**
+ * The index of the last visible character in `_text`,
+ * or -1 if there is no text.
+ */
+ int _lastVisibleChar;
+
+ /**
+ * The index of the line that is at the bottom of the
+ * viewport, or -1 if there is no text.
+ */
+ int _bottomVisibleLine;
+
+ /**
+ * The total number of lines in the backbuffer. This
+ * number may be higher than the total number of entries
+ * if an entry contains newlines.
+ */
+ int _numLines;
+
+ /**
+ * The number of lines that are currently visible in the
+ * text area of the window.
+ */
+ int _numVisibleLines;
+
+ /**
+ * The plane in which the ScrollWindow should be
+ * rendered.
+ */
+ reg_t _plane;
+
+ /**
+ * The default text color.
+ */
+ uint8 _foreColor;
+
+ /**
+ * The default background color of the text bitmap.
+ */
+ uint8 _backColor;
+
+ /**
+ * The default border color of the text bitmap. If -1,
+ * the viewport will have no border.
+ */
+ int16 _borderColor;
+
+ /**
+ * The default font used for rendering text into the
+ * ScrollWindow.
+ */
+ GuiResourceId _fontId;
+
+ /**
+ * The default text alignment used for rendering text
+ * into the ScrollWindow.
+ */
+ TextAlign _alignment;
+
+ /**
+ * The visibility of the ScrollWindow.
+ */
+ bool _visible;
+
+ /**
+ * The dimensions of the text box inside the font
+ * bitmap, in text-system coordinates.
+ */
+ Common::Rect _textRect;
+
+ /**
+ * The top-left corner of the ScrollWindow's screen
+ * item, in game script coordinates, relative to the
+ * parent plane.
+ */
+ Common::Point _position;
+
+ /**
+ * The height of the default font in screen pixels. All
+ * fonts rendered into the ScrollWindow must have this
+ * same height.
+ */
+ uint8 _pointSize;
+
+ /**
+ * The bitmap used to render text.
+ */
+ reg_t _bitmap;
+
+ /**
+ * A monotonically increasing ID used to identify
+ * text entries added to the ScrollWindow.
+ */
+ uint16 _nextEntryId;
+
+ /**
+ * The ScrollWindow's screen item.
+ */
+ ScreenItem *_screenItem;
+};
+
+/**
* Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games
*/
class GfxControls32 {
public:
GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text);
-
- reg_t kernelEditText(const reg_t controlObject);
+ ~GfxControls32();
private:
SegManager *_segMan;
GfxCache *_gfxCache;
GfxText32 *_gfxText32;
+#pragma mark -
+#pragma mark Garbage collection
+public:
+ Common::Array<reg_t> listObjectReferences();
+
+#pragma mark -
+#pragma mark Text input control
+public:
+ reg_t kernelEditText(const reg_t controlObject);
+
+private:
+ /**
+ * If true, typing will overwrite text that already
+ * exists at the text cursor's current position.
+ */
bool _overwriteMode;
+
+ /**
+ * The tick at which the text cursor should be toggled
+ * by `flashCursor`.
+ */
uint32 _nextCursorFlashTick;
+
+ /**
+ * Draws the text cursor for the given editor.
+ */
void drawCursor(TextEditor &editor);
+
+ /**
+ * Erases the text cursor for the given editor.
+ */
void eraseCursor(TextEditor &editor);
+
+ /**
+ * Toggles the text cursor for the given editor to be
+ * either drawn or erased.
+ */
void flashCursor(TextEditor &editor);
+
+#pragma mark -
+#pragma mark Scrollable window control
+public:
+ /**
+ * Creates a new scrollable window and returns the ID
+ * for the new window, which is used by game scripts to
+ * interact with scrollable windows.
+ */
+ reg_t makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries);
+
+ /**
+ * Gets a registered ScrollWindow instance by ID.
+ */
+ ScrollWindow *getScrollWindow(const reg_t id);
+
+ /**
+ * Destroys the scroll window with the given ID.
+ */
+ void destroyScrollWindow(const reg_t id);
+
+private:
+ typedef Common::HashMap<uint16, ScrollWindow *> ScrollWindowMap;
+
+ /**
+ * Monotonically increasing ID used to identify
+ * ScrollWindow instances.
+ */
+ uint16 _nextScrollWindowId;
+
+ /**
+ * A lookup table for registered ScrollWindow instances.
+ */
+ ScrollWindowMap _scrollWindows;
+
+#pragma mark -
+#pragma mark Message box
+public:
+ /**
+ * Displays an OS-level message dialog.
+ */
+ reg_t kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style);
+
+private:
+ /**
+ * Convenience function for creating and showing a
+ * message box.
+ */
+ int16 showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index 64ae828a50..fd37020896 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -42,15 +42,13 @@
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/font.h"
-#include "sci/graphics/view.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/paint32.h"
#include "sci/graphics/palette32.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/remap.h"
-#include "sci/graphics/text32.h"
#include "sci/graphics/plane32.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/screen_item32.h"
+#include "sci/graphics/text32.h"
#include "sci/graphics/frameout.h"
#include "sci/video/robot_decoder.h"
@@ -69,14 +67,14 @@ static int16 unknownCDefaults[2][16] = {
/* SCI2.1mid+ */ { 0, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, 0, 7, 7, 0 }
};
-GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) :
+GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette) :
_isHiRes(false),
- _cache(cache),
_palette(palette),
_resMan(resMan),
_screen(screen),
_segMan(segMan),
- _paint32(paint32),
+ _benchmarkingFinished(false),
+ _throttleFrameOut(true),
_showStyles(nullptr),
// TODO: Stop using _gfxScreen
_currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr),
@@ -255,9 +253,55 @@ void GfxFrameout::syncWithScripts(bool addElements) {
}
#pragma mark -
+#pragma mark Benchmarking
+
+bool GfxFrameout::checkForFred(const reg_t object) {
+ const int16 viewId = readSelectorValue(_segMan, object, SELECTOR(view));
+ const SciGameId gameId = g_sci->getGameId();
+
+ if (gameId == GID_QFG4 && viewId == 9999) {
+ return true;
+ }
+
+ if (gameId != GID_QFG4 && viewId == -556) {
+ return true;
+ }
+
+ if (Common::String(_segMan->getObjectName(object)) == "fred") {
+ return true;
+ }
+
+ return false;
+}
+
+#pragma mark -
#pragma mark Screen items
+void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, Plane *plane) {
+ if (screenItem->_created == 0) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = getScreenCount();
+ } else {
+ plane->_screenItemList.erase(screenItem);
+ plane->_screenItemList.pack();
+ }
+}
+
+void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, const reg_t planeObject) {
+ Plane *plane = _planes.findByObject(planeObject);
+ deleteScreenItem(screenItem, plane);
+}
+
void GfxFrameout::kernelAddScreenItem(const reg_t object) {
+ // The "fred" object is used to test graphics performance;
+ // it is impacted by framerate throttling, so disable the
+ // throttling when this item is on the screen for the
+ // performance check to pass.
+ if (!_benchmarkingFinished && _throttleFrameOut && checkForFred(object)) {
+ _throttleFrameOut = false;
+ }
+
const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
_segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted);
@@ -297,6 +341,15 @@ void GfxFrameout::kernelUpdateScreenItem(const reg_t object) {
}
void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
+ // The "fred" object is used to test graphics performance;
+ // it is impacted by framerate throttling, so disable the
+ // throttling when this item is on the screen for the
+ // performance check to pass.
+ if (!_benchmarkingFinished && checkForFred(object)) {
+ _benchmarkingFinished = true;
+ _throttleFrameOut = true;
+ }
+
_segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted);
const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
@@ -310,14 +363,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
return;
}
- if (screenItem->_created == 0) {
- screenItem->_created = 0;
- screenItem->_updated = 0;
- screenItem->_deleted = getScreenCount();
- } else {
- plane->_screenItemList.erase(screenItem);
- plane->_screenItemList.pack();
- }
+ deleteScreenItem(screenItem, plane);
}
#pragma mark -
@@ -446,7 +492,7 @@ void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pi
#pragma mark -
#pragma mark Rendering
-void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) {
+void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) {
// TODO: Robot
// if (_robot != nullptr) {
// _robot.doRobot();
@@ -464,7 +510,7 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)
remapMarkRedraw();
}
- calcLists(screenItemLists, eraseLists, rect);
+ calcLists(screenItemLists, eraseLists, eraseRect);
for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
list->sort();
@@ -507,130 +553,221 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)
// }
}
-// Determine the parts of 'r' that aren't overlapped by 'other'.
-// Returns -1 if r and other have no intersection.
-// Returns number of returned parts (in outRects) otherwise.
-// (In particular, this returns 0 if r is contained in other.)
+/**
+ * Determines the parts of `r` that aren't overlapped by `other`.
+ * Returns -1 if `r` and `other` have no intersection.
+ * Returns number of returned parts (in `outRects`) otherwise.
+ * (In particular, this returns 0 if `r` is contained in `other`.)
+ */
int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) {
if (!r.intersects(other)) {
return -1;
}
- int count = 0;
+ int splitCount = 0;
if (r.top < other.top) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.bottom = other.top;
r.top = other.top;
}
if (r.bottom > other.bottom) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.top = other.bottom;
r.bottom = other.bottom;
}
if (r.left < other.left) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.right = other.left;
r.left = other.left;
}
if (r.right > other.right) {
- Common::Rect &t = outRects[count++];
+ Common::Rect &t = outRects[splitCount++];
t = r;
t.left = other.right;
}
- return count;
+ return splitCount;
}
-void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) {
- RectList rectlist;
- Common::Rect outRects[4];
+/**
+ * Determines the parts of `middleRect` that aren't overlapped
+ * by `showRect`, optimised for contiguous memory writes.
+ * Returns -1 if `middleRect` and `showRect` have no intersection.
+ * Returns number of returned parts (in `outRects`) otherwise.
+ * (In particular, this returns 0 if `middleRect` is contained
+ * in `other`.)
+ *
+ * `middleRect` is modified directly to extend into the upper
+ * and lower rects.
+ */
+int splitRectsForRender(Common::Rect &middleRect, const Common::Rect &showRect, Common::Rect(&outRects)[2]) {
+ if (!middleRect.intersects(showRect)) {
+ return -1;
+ }
+
+ const int16 minLeft = MIN(middleRect.left, showRect.left);
+ const int16 maxRight = MAX(middleRect.right, showRect.right);
+
+ int16 upperLeft, upperTop, upperRight, upperMaxTop;
+ if (middleRect.top < showRect.top) {
+ upperLeft = middleRect.left;
+ upperTop = middleRect.top;
+ upperRight = middleRect.right;
+ upperMaxTop = showRect.top;
+ }
+ else {
+ upperLeft = showRect.left;
+ upperTop = showRect.top;
+ upperRight = showRect.right;
+ upperMaxTop = middleRect.top;
+ }
+
+ int16 lowerLeft, lowerRight, lowerBottom, lowerMinBottom;
+ if (middleRect.bottom > showRect.bottom) {
+ lowerLeft = middleRect.left;
+ lowerRight = middleRect.right;
+ lowerBottom = middleRect.bottom;
+ lowerMinBottom = showRect.bottom;
+ } else {
+ lowerLeft = showRect.left;
+ lowerRight = showRect.right;
+ lowerBottom = showRect.bottom;
+ lowerMinBottom = middleRect.bottom;
+ }
+
+ int splitCount = 0;
+ middleRect.left = minLeft;
+ middleRect.top = upperMaxTop;
+ middleRect.right = maxRight;
+ middleRect.bottom = lowerMinBottom;
+
+ if (upperTop != upperMaxTop) {
+ Common::Rect &upperRect = outRects[0];
+ upperRect.left = upperLeft;
+ upperRect.top = upperTop;
+ upperRect.right = upperRight;
+ upperRect.bottom = upperMaxTop;
+
+ // Merge upper rect into middle rect if possible
+ if (upperRect.left == middleRect.left && upperRect.right == middleRect.right) {
+ middleRect.top = upperRect.top;
+ } else {
+ ++splitCount;
+ }
+ }
+
+ if (lowerBottom != lowerMinBottom) {
+ Common::Rect &lowerRect = outRects[splitCount];
+ lowerRect.left = lowerLeft;
+ lowerRect.top = lowerMinBottom;
+ lowerRect.right = lowerRight;
+ lowerRect.bottom = lowerBottom;
+
+ // Merge lower rect into middle rect if possible
+ if (lowerRect.left == middleRect.left && lowerRect.right == middleRect.right) {
+ middleRect.bottom = lowerRect.bottom;
+ } else {
+ ++splitCount;
+ }
+ }
+
+ assert(splitCount <= 2);
+ return splitCount;
+}
+// NOTE: The third rectangle parameter is only ever given a non-empty rect
+// by VMD code, via `frameOut`
+void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect) {
+ RectList eraseList;
+ Common::Rect outRects[4];
int deletedPlaneCount = 0;
- bool addedToRectList = false;
- int planeCount = _planes.size();
+ bool addedToEraseList = false;
bool foundTransparentPlane = false;
- if (!calcRect.isEmpty()) {
- addedToRectList = true;
- rectlist.add(calcRect);
+ if (!eraseRect.isEmpty()) {
+ addedToEraseList = true;
+ eraseList.add(eraseRect);
}
+ PlaneList::size_type planeCount = _planes.size();
for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
- Plane *outerPlane = _planes[outerPlaneIndex];
+ const Plane *outerPlane = _planes[outerPlaneIndex];
+ const Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
+ // NOTE: SSCI only ever checks for kPlaneTypeTransparent here, even
+ // though kPlaneTypeTransparentPicture is also a transparent plane
if (outerPlane->_type == kPlaneTypeTransparent) {
foundTransparentPlane = true;
}
- Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
-
if (outerPlane->_deleted) {
- if (visiblePlane != nullptr) {
- if (!visiblePlane->_screenRect.isEmpty()) {
- addedToRectList = true;
- rectlist.add(visiblePlane->_screenRect);
- }
+ if (visiblePlane != nullptr && !visiblePlane->_screenRect.isEmpty()) {
+ eraseList.add(visiblePlane->_screenRect);
+ addedToEraseList = true;
}
++deletedPlaneCount;
- } else if (visiblePlane != nullptr) {
- if (outerPlane->_updated) {
- --outerPlane->_updated;
-
- int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
- if (splitcount) {
- if (splitcount == -1) {
- if (!visiblePlane->_screenRect.isEmpty()) {
- rectlist.add(visiblePlane->_screenRect);
- }
+ } else if (visiblePlane != nullptr && outerPlane->_moved) {
+ // _moved will be decremented in the final loop through the planes,
+ // at the end of this function
+
+ {
+ const int splitCount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
+ if (splitCount) {
+ if (splitCount == -1 && !visiblePlane->_screenRect.isEmpty()) {
+ eraseList.add(visiblePlane->_screenRect);
} else {
- for (int i = 0; i < splitcount; ++i) {
- rectlist.add(outRects[i]);
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
}
}
-
- addedToRectList = true;
+ addedToEraseList = true;
}
+ }
- if (!outerPlane->_redrawAllCount) {
- int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
- if (splitCount) {
- for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[i]);
- }
- addedToRectList = true;
+ if (!outerPlane->_redrawAllCount) {
+ const int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
+ if (splitCount) {
+ for (int i = 0; i < splitCount; ++i) {
+ eraseList.add(outRects[i]);
}
+ addedToEraseList = true;
}
}
}
- if (addedToRectList) {
- for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) {
- for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
- Plane *innerPlane = _planes[innerPlaneIndex];
-
- if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) {
- if (innerPlane->_redrawAllCount == 0) {
- eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect));
+ if (addedToEraseList) {
+ for (int rectIndex = 0; rectIndex < eraseList.size(); ++rectIndex) {
+ const Common::Rect &rect = *eraseList[rectIndex];
+ for (int innerPlaneIndex = planeCount - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
+ const Plane &innerPlane = *_planes[innerPlaneIndex];
+
+ if (
+ !innerPlane._deleted &&
+ innerPlane._type != kPlaneTypeTransparent &&
+ innerPlane._screenRect.intersects(rect)
+ ) {
+ if (!innerPlane._redrawAllCount) {
+ eraseLists[innerPlaneIndex].add(innerPlane._screenRect.findIntersectingRect(rect));
}
- int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects);
+ const int splitCount = splitRects(rect, innerPlane._screenRect, outRects);
for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[i]);
+ eraseList.add(outRects[i]);
}
- rectlist.erase(rect);
+ eraseList.erase_at(rectIndex);
break;
}
}
}
- rectlist.pack();
+ eraseList.pack();
}
}
@@ -642,9 +779,9 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
if (plane->_deleted) {
--plane->_deleted;
if (plane->_deleted <= 0) {
- PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
- if (visiblePlaneIt != _visiblePlanes.end()) {
- _visiblePlanes.erase(visiblePlaneIt);
+ const int visiblePlaneIndex = _visiblePlanes.findIndexByObject(plane->_object);
+ if (visiblePlaneIndex != -1) {
+ _visiblePlanes.remove_at(visiblePlaneIndex);
}
_planes.remove_at(planeIndex);
@@ -659,103 +796,114 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
}
}
+ // Some planes may have been deleted, so re-retrieve count
planeCount = _planes.size();
- for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
- // "outer" just refers to the outer loop
- Plane *outerPlane = _planes[outerIndex];
- if (outerPlane->_priorityChanged) {
- --outerPlane->_priorityChanged;
- Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object);
+ for (PlaneList::size_type outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
+ // "outer" just refers to the outer loop
+ Plane &outerPlane = *_planes[outerIndex];
+ if (outerPlane._priorityChanged) {
+ --outerPlane._priorityChanged;
+
+ const Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane._object);
+ if (visibleOuterPlane == nullptr) {
+ warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane._object));
+ continue;
+ }
- rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
+ eraseList.add(outerPlane._screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
- for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) {
+ for (PlaneList::size_type innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) {
// "inner" just refers to the inner loop
- Plane *innerPlane = _planes[innerIndex];
- Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object);
-
- int rectCount = rectlist.size();
- for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
- int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects);
+ const Plane &innerPlane = *_planes[innerIndex];
+ const Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane._object);
+ const RectList::size_type rectCount = eraseList.size();
+ for (RectList::size_type rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
+ const int splitCount = splitRects(*eraseList[rectIndex], innerPlane._screenRect, outRects);
if (splitCount == 0) {
- if (visibleInnerPlane != nullptr && visibleOuterPlane != nullptr) {
+ if (visibleInnerPlane != nullptr) {
// same priority, or relative priority between inner/outer changed
- if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
- if (outerPlane->_priority <= innerPlane->_priority) {
- eraseLists[innerIndex].add(*rectlist[rectIndex]);
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
+ if (outerPlane._priority <= innerPlane._priority) {
+ eraseLists[innerIndex].add(*eraseList[rectIndex]);
} else {
- eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ eraseLists[outerIndex].add(*eraseList[rectIndex]);
}
}
}
- rectlist.erase_at(rectIndex);
+ eraseList.erase_at(rectIndex);
} else if (splitCount != -1) {
for (int i = 0; i < splitCount; ++i) {
- rectlist.add(outRects[i]);
+ eraseList.add(outRects[i]);
}
- if (visibleInnerPlane != nullptr && visibleOuterPlane != nullptr) {
+ if (visibleInnerPlane != nullptr) {
// same priority, or relative priority between inner/outer changed
- if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
- *rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect);
- if (outerPlane->_priority <= innerPlane->_priority) {
- eraseLists[innerIndex].add(*rectlist[rectIndex]);
- }
- else {
- eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
+ *eraseList[rectIndex] = outerPlane._screenRect.findIntersectingRect(innerPlane._screenRect);
+
+ if (outerPlane._priority <= innerPlane._priority) {
+ eraseLists[innerIndex].add(*eraseList[rectIndex]);
+ } else {
+ eraseLists[outerIndex].add(*eraseList[rectIndex]);
}
}
}
- rectlist.erase_at(rectIndex);
+ eraseList.erase_at(rectIndex);
}
}
- rectlist.pack();
+ eraseList.pack();
}
}
}
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- Plane *plane = _planes[planeIndex];
- Plane *visiblePlane = nullptr;
+ for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ Plane &plane = *_planes[planeIndex];
+ Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
- PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
- if (visiblePlaneIt != _visiblePlanes.end()) {
- visiblePlane = *visiblePlaneIt;
- }
+ if (!plane._screenRect.isEmpty()) {
+ if (plane._redrawAllCount) {
+ plane.redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ } else {
+ if (visiblePlane == nullptr) {
+ error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane._object));
+ }
- if (plane->_redrawAllCount) {
- plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
- } else {
- if (visiblePlane == nullptr) {
- error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object));
+ plane.calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
}
+ } else {
+ plane.decrementScreenItemArrayCounts(visiblePlane, false);
+ }
- plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ if (plane._moved) {
+ // the work for handling moved/resized planes was already done
+ // earlier in the function, we are just cleaning up now
+ --plane._moved;
}
- if (plane->_created) {
- _visiblePlanes.add(new Plane(*plane));
- --plane->_created;
- } else if (plane->_moved) {
- assert(visiblePlaneIt != _visiblePlanes.end());
- **visiblePlaneIt = *plane;
- --plane->_moved;
+ if (plane._created) {
+ _visiblePlanes.add(new Plane(plane));
+ --plane._created;
+ } else if (plane._updated) {
+ *visiblePlane = plane;
+ --plane._updated;
}
}
+ // NOTE: SSCI only looks for kPlaneTypeTransparent, not
+ // kPlaneTypeTransparentPicture
if (foundTransparentPlane) {
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- for (int i = planeIndex + 1; i < planeCount; ++i) {
+ for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
if (_planes[i]->_type == kPlaneTypeTransparent) {
_planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]);
}
}
if (_planes[planeIndex]->_type == kPlaneTypeTransparent) {
- for (int i = planeIndex - 1; i >= 0; --i) {
+ for (PlaneList::size_type i = planeIndex - 1; i >= 0; --i) {
_planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);
}
@@ -764,7 +912,7 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL
}
}
- for (int i = planeIndex + 1; i < planeCount; ++i) {
+ for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
if (_planes[i]->_type == kPlaneTypeTransparent) {
_planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);
}
@@ -778,17 +926,19 @@ void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {
return;
}
- for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) {
- mergeToShowList(**it, _showList, _overdrawThreshold);
- _currentBuffer.fillRect(**it, plane._back);
+ const RectList::size_type eraseListSize = eraseList.size();
+ for (RectList::size_type i = 0; i < eraseListSize; ++i) {
+ mergeToShowList(*eraseList[i], _showList, _overdrawThreshold);
+ _currentBuffer.fillRect(*eraseList[i], plane._back);
}
}
void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
- for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
- DrawItem &drawItem = **it;
+ const DrawList::size_type drawListSize = screenItemList.size();
+ for (DrawList::size_type i = 0; i < drawListSize; ++i) {
+ const DrawItem &drawItem = *screenItemList[i];
mergeToShowList(drawItem.rect, _showList, _overdrawThreshold);
- ScreenItem &screenItem = *drawItem.screenItem;
+ const ScreenItem &screenItem = *drawItem.screenItem;
// TODO: Remove
// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect));
CelObj &celObj = *screenItem._celObj;
@@ -797,36 +947,65 @@ void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
}
void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) {
- Common::Rect merged(drawRect);
-
- bool didDelete = true;
- RectList::size_type count = showList.size();
- while (didDelete && count) {
- didDelete = false;
-
- for (RectList::size_type i = 0; i < count; ++i) {
- Common::Rect existing = *showList[i];
- Common::Rect candidate;
- candidate.left = MIN(merged.left, existing.left);
- candidate.top = MIN(merged.top, existing.top);
- candidate.right = MAX(merged.right, existing.right);
- candidate.bottom = MAX(merged.bottom, existing.bottom);
-
- if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) {
- merged = candidate;
- showList.erase_at(i);
- didDelete = true;
+ RectList mergeList;
+ Common::Rect merged;
+ mergeList.add(drawRect);
+
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ bool didMerge = false;
+ const Common::Rect &r1 = *mergeList[i];
+ if (!r1.isEmpty()) {
+ for (RectList::size_type j = 0; j < showList.size(); ++j) {
+ const Common::Rect &r2 = *showList[j];
+ if (!r2.isEmpty()) {
+ merged = r1;
+ merged.extend(r2);
+
+ int difference = merged.width() * merged.height();
+ difference -= r1.width() * r1.height();
+ difference -= r2.width() * r2.height();
+ if (r1.intersects(r2)) {
+ const Common::Rect overlap = r1.findIntersectingRect(r2);
+ difference += overlap.width() * overlap.height();
+ }
+
+ if (difference <= overdrawThreshold) {
+ mergeList.erase_at(i);
+ showList.erase_at(j);
+ mergeList.add(merged);
+ didMerge = true;
+ break;
+ } else {
+ Common::Rect outRects[2];
+ int splitCount = splitRectsForRender(*mergeList[i], *showList[j], outRects);
+ if (splitCount != -1) {
+ mergeList.add(*mergeList[i]);
+ mergeList.erase_at(i);
+ showList.erase_at(j);
+ didMerge = true;
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
+ }
+ break;
+ }
+ }
+ }
}
- }
- count = showList.pack();
+ if (didMerge) {
+ showList.pack();
+ }
+ }
}
- showList.add(merged);
+ mergeList.pack();
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ showList.add(*mergeList[i]);
+ }
}
void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) {
- Palette sourcePalette(*_palette->getNextPalette());
+ Palette sourcePalette(_palette->getNextPalette());
alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
@@ -835,8 +1014,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
_showList.add(rect);
showBits();
- Common::Rect calcRect(0, 0);
-
// NOTE: The original engine allocated these as static arrays of 100
// pointers to ScreenItemList / RectList
ScreenItemListList screenItemLists;
@@ -849,7 +1026,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
remapMarkRedraw();
}
- calcLists(screenItemLists, eraseLists, calcRect);
+ calcLists(screenItemLists, eraseLists);
for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
list->sort();
}
@@ -868,7 +1045,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
drawScreenItemList(screenItemLists[i]);
}
- Palette nextPalette(*_palette->getNextPalette());
+ Palette nextPalette(_palette->getNextPalette());
if (prevRoom < 1000) {
for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
@@ -879,6 +1056,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
}
} else {
for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ // TODO: Limiting range 72 to 103 is NOT present in every game
if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) {
sourcePalette.colors[i] = nextPalette.colors[i];
sourcePalette.colors[i].used = true;
@@ -909,7 +1087,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
remapMarkRedraw();
}
- calcLists(screenItemLists, eraseLists, calcRect);
+ calcLists(screenItemLists, eraseLists);
for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
list->sort();
}
@@ -938,10 +1116,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
_frameNowVisible = true;
}
-// TODO: What does the bit masking for the show rects do,
-// and does it cause an off-by-one error in rect calculations
-// since SOL_Rect is BR inclusive and Common::Rect is BR
-// exclusive?
void GfxFrameout::showBits() {
for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
Common::Rect rounded(**rect);
@@ -1391,6 +1565,11 @@ void GfxFrameout::kernelFrameOut(const bool shouldShowBits) {
frameOut(shouldShowBits);
}
+
+ if (_throttleFrameOut) {
+ g_sci->getEngineState()->speedThrottler(16);
+ g_sci->getEngineState()->_throttleTrigger = true;
+ }
}
#pragma mark -
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index 8ed95a00de..e736872773 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -149,10 +149,7 @@ struct ShowStyleEntry {
typedef Common::Array<DrawList> ScreenItemListList;
typedef Common::Array<RectList> EraseListList;
-class GfxCache;
class GfxCoordAdjuster32;
-class GfxPaint32;
-class GfxPalette;
class GfxScreen;
/**
@@ -162,16 +159,14 @@ class GfxScreen;
class GfxFrameout {
private:
bool _isHiRes;
- GfxCache *_cache;
GfxCoordAdjuster32 *_coordAdjuster;
GfxPalette32 *_palette;
ResourceManager *_resMan;
GfxScreen *_screen;
SegManager *_segMan;
- GfxPaint32 *_paint32;
public:
- GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32);
+ GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette);
~GfxFrameout();
void clear();
@@ -179,12 +174,43 @@ public:
void run();
#pragma mark -
+#pragma mark Benchmarking
+private:
+ /**
+ * Optimization to avoid the more expensive object name
+ * comparision on every call to kAddScreenItem and
+ * kRemoveScreenItem.
+ */
+ bool _benchmarkingFinished;
+
+ /**
+ * Whether or not calls to kFrameOut should be framerate
+ * limited to ~60fps.
+ */
+ bool _throttleFrameOut;
+
+ /**
+ * Determines whether or not a screen item is the "Fred"
+ * object.
+ */
+ bool checkForFred(const reg_t object);
+
+#pragma mark -
#pragma mark Screen items
private:
- void deleteScreenItem(ScreenItem *screenItem, const reg_t plane);
void remapMarkRedraw();
public:
+ /**
+ * Deletes a screen item from the given plane.
+ */
+ void deleteScreenItem(ScreenItem *screenItem, Plane *plane);
+
+ /**
+ * Deletes a screen item from the given plane.
+ */
+ void deleteScreenItem(ScreenItem *screenItem, const reg_t plane);
+
void kernelAddScreenItem(const reg_t object);
void kernelUpdateScreenItem(const reg_t object);
void kernelDeleteScreenItem(const reg_t object);
@@ -350,8 +376,10 @@ private:
* over the entire screen for rendering the next frame.
* The draw and erase lists in `drawLists` and
* `eraseLists` each represent one plane on the screen.
+ * The optional `eraseRect` argument allows a specific
+ * area of the screen to be erased.
*/
- void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect);
+ void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect = Common::Rect());
/**
* Erases the areas in the given erase list from the
@@ -404,9 +432,10 @@ public:
/**
* Updates the internal screen buffer for the next
* frame. If `shouldShowBits` is true, also sends the
- * buffer to hardware.
+ * buffer to hardware. If `eraseRect` is non-empty,
+ * it is added to the erase list for this frame.
*/
- void frameOut(const bool shouldShowBits, const Common::Rect &rect = Common::Rect());
+ void frameOut(const bool shouldShowBits, const Common::Rect &eraseRect = Common::Rect());
/**
* Modifies the raw pixel data for the next frame with
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index 19dddd74b8..3fcc83c5e2 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -191,6 +191,12 @@ struct Buffer : public Graphics::Surface {
uint16 scriptWidth;
uint16 scriptHeight;
+ Buffer() :
+ screenWidth(0),
+ screenHeight(0),
+ scriptWidth(320),
+ scriptHeight(200) {}
+
Buffer(const uint16 width, const uint16 height, uint8 *const pix) :
screenWidth(width),
screenHeight(height),
@@ -231,7 +237,7 @@ struct Color {
return used == other.used && r == other.r && g == other.g && b == other.b;
}
inline bool operator!=(const Color &other) const {
- return !(*this == other);
+ return !operator==(other);
}
#endif
};
diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h
index 955cfdec8f..317388b2df 100644
--- a/engines/sci/graphics/paint16.h
+++ b/engines/sci/graphics/paint16.h
@@ -23,8 +23,6 @@
#ifndef SCI_GRAPHICS_PAINT16_H
#define SCI_GRAPHICS_PAINT16_H
-#include "sci/graphics/paint.h"
-
namespace Sci {
class GfxPorts;
@@ -36,7 +34,7 @@ class GfxView;
/**
* Paint16 class, handles painting/drawing for SCI16 (SCI0-SCI1.1) games
*/
-class GfxPaint16 : public GfxPaint {
+class GfxPaint16 {
public:
GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio);
~GfxPaint16();
diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp
index a210a469f1..bfd46484e9 100644
--- a/engines/sci/graphics/paint32.cpp
+++ b/engines/sci/graphics/paint32.cpp
@@ -20,49 +20,162 @@
*
*/
-#include "sci/sci.h"
-#include "sci/engine/state.h"
-#include "sci/engine/selector.h"
-#include "sci/graphics/coordadjuster.h"
-#include "sci/graphics/cache.h"
+#include "graphics/primitives.h"
+#include "sci/engine/seg_manager.h"
#include "sci/graphics/paint32.h"
-#include "sci/graphics/font.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/view.h"
-#include "sci/graphics/screen.h"
-#include "sci/graphics/palette.h"
+#include "sci/graphics/text32.h"
namespace Sci {
-GfxPaint32::GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette)
- : _resMan(resMan), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette) {
+GfxPaint32::GfxPaint32(SegManager *segMan) :
+ _segMan(segMan) {}
+
+reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject);
+ if (plane == nullptr) {
+ error("kAddLine: Plane %04x:%04x not found", PRINT_REG(planeObject));
+ }
+
+ Common::Rect gameRect;
+ BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = bitmap.getObject();
+ // SSCI stores the line color on `celInfo`, even though
+ // this is not a `kCelTypeColor`, as a hack so that
+ // `kUpdateLine` can get the originally used color
+ celInfo.color = color;
+
+ ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, Common::Rect(startPoint.x, startPoint.y, startPoint.x + bitmap.getWidth(), startPoint.y + bitmap.getHeight()));
+ screenItem->_priority = priority;
+ screenItem->_fixedPriority = true;
+
+ plane->_screenItemList.add(screenItem);
+
+ return screenItem->_object;
+}
+
+void GfxPaint32::kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) {
+
+ Common::Rect gameRect;
+ BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect);
+
+ _segMan->freeHunkEntry(screenItem->_celInfo.bitmap);
+ screenItem->_celInfo.bitmap = bitmap.getObject();
+ screenItem->_celInfo.color = color;
+ screenItem->_position = startPoint;
+ screenItem->_priority = priority;
+ screenItem->update();
}
-GfxPaint32::~GfxPaint32() {
+void GfxPaint32::kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject) {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject);
+ if (plane == nullptr) {
+ return;
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
+ if (screenItem == nullptr) {
+ return;
+ }
+
+ _segMan->freeHunkEntry(screenItem->_celInfo.bitmap);
+ g_sci->_gfxFrameout->deleteScreenItem(screenItem, plane);
}
-void GfxPaint32::fillRect(Common::Rect rect, byte color) {
- int16 y, x;
- Common::Rect clipRect = rect;
+void GfxPaint32::plotter(int x, int y, int color, void *data) {
+ LineProperties &properties = *static_cast<LineProperties *>(data);
+ byte *pixels = properties.bitmap->getPixels();
+
+ const uint32 index = properties.bitmap->getWidth() * y + x;
+
+ if (index < properties.bitmap->getDataSize()) {
+ if (properties.solid) {
+ pixels[index] = (uint8)color;
+ return;
+ }
+
+ if (properties.horizontal && x != properties.lastAddress) {
+ properties.lastAddress = x;
+ ++properties.patternIndex;
+ } else if (!properties.horizontal && y != properties.lastAddress) {
+ properties.lastAddress = y;
+ ++properties.patternIndex;
+ }
- clipRect.clip(_screen->getWidth(), _screen->getHeight());
+ if (properties.pattern[properties.patternIndex]) {
+ pixels[index] = (uint8)color;
+ }
- for (y = clipRect.top; y < clipRect.bottom; y++) {
- for (x = clipRect.left; x < clipRect.right; x++) {
- _screen->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0, 0);
+ if (properties.patternIndex == ARRAYSIZE(properties.pattern)) {
+ properties.patternIndex = 0;
}
+ } else {
+ warning("GfxPaint32::plotter: Attempted to write out of bounds (%u >= %u)", index, properties.bitmap->getDataSize());
}
}
-void GfxPaint32::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) {
- GfxPicture *picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false);
+BitmapResource GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, uint16 pattern, uint8 thickness, Common::Rect &outRect) {
+ const uint8 skipColor = color != 250 ? 250 : 0;
- picture->draw(animationNr, mirroredFlag, addToFlag, EGApaletteNo);
- delete picture;
-}
+ // Thickness is expected to be 2n+1
+ thickness = ((MAX((uint8)1, thickness) - 1) | 1);
+ const uint8 halfThickness = thickness >> 1;
+
+ outRect.left = (startPoint.x < endPoint.x ? startPoint.x : endPoint.x) - halfThickness;
+ outRect.top = (startPoint.y < endPoint.y ? startPoint.y : endPoint.y) - halfThickness;
+ outRect.right = (startPoint.x > endPoint.x ? startPoint.x : endPoint.x) + halfThickness + 1;
+ outRect.bottom = (startPoint.y > endPoint.y ? startPoint.y : endPoint.y) + halfThickness + 1;
+
+ BitmapResource bitmap(_segMan, outRect.width(), outRect.height(), skipColor, 0, 0, g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, 0, false);
+
+ byte *pixels = bitmap.getPixels();
+ memset(pixels, skipColor, bitmap.getWidth() * bitmap.getHeight());
+
+ LineProperties properties;
+ properties.bitmap = &bitmap;
+
+ switch (style) {
+ case kLineStyleSolid:
+ pattern = 0xFFFF;
+ properties.solid = true;
+ break;
+ case kLineStyleDashed:
+ pattern = 0xFF00;
+ properties.solid = false;
+ break;
+ case kLineStylePattern:
+ properties.solid = pattern == 0xFFFF;
+ break;
+ }
+
+ const Common::Rect drawRect(
+ startPoint.x - outRect.left,
+ startPoint.y - outRect.top,
+ endPoint.x - outRect.left,
+ endPoint.y - outRect.top
+ );
-void GfxPaint32::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) {
- _screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control);
+ if (!properties.solid) {
+ for (int i = 0; i < ARRAYSIZE(properties.pattern); ++i) {
+ properties.pattern[i] = (pattern & 0x8000);
+ pattern <<= 1;
+ }
+
+ properties.patternIndex = 0;
+ properties.horizontal = ABS(drawRect.right - drawRect.left) > ABS(drawRect.bottom - drawRect.top);
+ properties.lastAddress = properties.horizontal ? drawRect.left : drawRect.top;
+ }
+
+ if (thickness <= 1) {
+ Graphics::drawLine(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, color, plotter, &properties);
+ } else {
+ Graphics::drawThickLine2(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, thickness, color, plotter, &properties);
+ }
+
+ return bitmap;
}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/paint32.h b/engines/sci/graphics/paint32.h
index e7a3ec256d..6d5a957fcd 100644
--- a/engines/sci/graphics/paint32.h
+++ b/engines/sci/graphics/paint32.h
@@ -23,30 +23,48 @@
#ifndef SCI_GRAPHICS_PAINT32_H
#define SCI_GRAPHICS_PAINT32_H
-#include "sci/graphics/paint.h"
-
namespace Sci {
+class BitmapResource;
+class Plane;
+class ScreenItem;
+class SegManager;
-class GfxPorts;
+enum LineStyle {
+ kLineStyleSolid,
+ kLineStyleDashed,
+ kLineStylePattern
+};
/**
* Paint32 class, handles painting/drawing for SCI32 (SCI2+) games
*/
-class GfxPaint32 : public GfxPaint {
+class GfxPaint32 {
public:
- GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette);
- ~GfxPaint32();
+ GfxPaint32(SegManager *segMan);
- void fillRect(Common::Rect rect, byte color);
+private:
+ SegManager *_segMan;
- void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo);
- void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control);
+#pragma mark -
+#pragma mark Line drawing
+public:
+ reg_t kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness);
+ void kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness);
+ void kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject);
private:
- ResourceManager *_resMan;
- GfxCoordAdjuster *_coordAdjuster;
- GfxScreen *_screen;
- GfxPalette *_palette;
+ typedef struct {
+ BitmapResource *bitmap;
+ bool pattern[16];
+ uint8 patternIndex;
+ bool solid;
+ bool horizontal;
+ int lastAddress;
+ } LineProperties;
+
+ static void plotter(int x, int y, int color, void *data);
+
+ BitmapResource makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness, Common::Rect &outRect);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index 6844011675..0840e82a40 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -28,7 +28,7 @@
#include "sci/event.h"
#include "sci/resource.h"
#include "sci/graphics/palette32.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/screen.h"
namespace Sci {
@@ -78,10 +78,6 @@ inline void mergePaletteInternal(Palette *const to, const Palette *const from) {
}
}
-const Palette *GfxPalette32::getNextPalette() const {
- return &_nextPalette;
-}
-
void GfxPalette32::submit(Palette &palette) {
// TODO: The resource manager in SCI32 retains raw data of palettes from
// the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and
@@ -178,49 +174,6 @@ void GfxPalette32::set(Palette *newPalette, bool force, bool forceRealMerge) {
submit(*newPalette);
}
-// In SCI32 engine this method is SOLPalette::Match(Rgb24 *, int, int *, int *)
-// and is used by Remap
-// TODO: Anything that calls GfxPalette::matchColor(int, int, int) is going to
-// match using an algorithm from SCI16 engine right now. This needs to be
-// corrected in the future so either nothing calls
-// GfxPalette::matchColor(int, int, int), or it is fixed to match the other
-// SCI32 algorithms.
-int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) {
- int16 bestIndex = -1;
- int bestDifference = 0xFFFFF;
- int difference = defaultDifference;
-
- // SQ6 DOS really does check only the first 236 entries
- for (int i = 0, channelDifference; i < 236; ++i) {
- if (matchTable[i] == 0) {
- continue;
- }
-
- difference = _sysPalette.colors[i].r - r;
- difference *= difference;
- if (bestDifference <= difference) {
- continue;
- }
- channelDifference = _sysPalette.colors[i].g - g;
- difference += channelDifference * channelDifference;
- if (bestDifference <= difference) {
- continue;
- }
- channelDifference = _sysPalette.colors[i].b - b;
- difference += channelDifference * channelDifference;
- if (bestDifference <= difference) {
- continue;
- }
- bestDifference = difference;
- bestIndex = i;
- }
-
- // NOTE: This value is only valid if the last index to
- // perform a difference calculation was the best index
- lastCalculatedDifference = difference;
- return bestIndex;
-}
-
bool GfxPalette32::updateForFrame() {
applyAll();
_versionUpdated = false;
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index a5450776dc..7dda53e5c1 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -113,12 +113,12 @@ private:
public:
virtual void saveLoadWithSerializer(Common::Serializer &s) override;
- const Palette *getNextPalette() const;
+ inline const Palette &getNextPalette() const { return _nextPalette; };
+ inline const Palette &getCurrentPalette() const { return _sysPalette; };
bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
- int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
/**
* Submits a palette to display. Entries marked as “used” in the
@@ -133,7 +133,7 @@ public:
void applyAll();
#pragma mark -
-#pragma mark color look-up
+#pragma mark Color look-up
private:
/**
* An optional lookup table used to remap RGB565 colors to a palette
@@ -240,6 +240,11 @@ private:
* According to SCI engine code, when two cyclers overlap,
* a fatal error has occurred and the engine will display
* an error and then exit.
+ *
+ * The cycle map is also by the color remapping system to
+ * avoid attempting to remap to palette entries that are
+ * cycling (so won't be the expected color once the cycler
+ * runs again).
*/
bool _cycleMap[256];
inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
@@ -257,7 +262,7 @@ public:
void cycleAllOff();
void applyAllCycles();
void applyCycles();
- const bool *getCyclemap() { return _cycleMap; }
+ inline const bool *getCycleMap() const { return _cycleMap; }
#pragma mark -
#pragma mark Fading
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
index 470986fb3c..175875c414 100644
--- a/engines/sci/graphics/plane32.cpp
+++ b/engines/sci/graphics/plane32.cpp
@@ -27,7 +27,7 @@
#include "sci/graphics/frameout.h"
#include "sci/graphics/lists32.h"
#include "sci/graphics/plane32.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/screen_item32.h"
@@ -163,11 +163,15 @@ void Plane::printDebugInfo(Console *con) const {
void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) {
uint16 celCount = 1000;
+ bool transparent = true;
for (uint16 celNo = 0; celNo < celCount; ++celNo) {
CelObjPic *celObj = new CelObjPic(pictureId, celNo);
if (celCount == 1000) {
celCount = celObj->_celCount;
}
+ if (!celObj->_transparent) {
+ transparent = false;
+ }
ScreenItem *screenItem = new ScreenItem(_object, celObj->_info);
screenItem->_pictureId = pictureId;
@@ -184,6 +188,7 @@ void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *p
delete screenItem->_celObj;
screenItem->_celObj = celObj;
}
+ _type = transparent ? kPlaneTypeTransparentPicture : kPlaneTypePicture;
}
void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) {
@@ -196,7 +201,7 @@ void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position,
void Plane::changePic() {
_pictureChanged = false;
- if (_type != kPlaneTypePicture) {
+ if (_type != kPlaneTypePicture && _type != kPlaneTypeTransparentPicture) {
return;
}
@@ -240,16 +245,20 @@ void Plane::deleteAllPics() {
#pragma mark Plane - Rendering
void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const {
- int index = planeList.findIndexByObject(_object);
+ const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1;
+ const PlaneList::size_type planeCount = planeList.size();
for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
- for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
- if (planeList[j]->_type != kPlaneTypeTransparent) {
- Common::Rect ptr[4];
- int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- drawList.add(drawList[i]->screenItem, ptr[k]);
+ for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) {
+ if (
+ planeList[j]->_type != kPlaneTypeTransparent &&
+ planeList[j]->_type != kPlaneTypeTransparentPicture
+ ) {
+ Common::Rect outRects[4];
+ int splitCount = splitRects(drawList[i]->rect, planeList[j]->_screenRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ drawList.add(drawList[i]->screenItem, outRects[splitCount]);
}
drawList.erase_at(i);
@@ -262,17 +271,20 @@ void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList
}
void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const {
- int index = planeList.findIndexByObject(_object);
+ const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1;
+ const PlaneList::size_type planeCount = planeList.size();
for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
- if (planeList[j]->_type != kPlaneTypeTransparent) {
- Common::Rect ptr[4];
-
- int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- eraseList.add(ptr[k]);
+ for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) {
+ if (
+ planeList[j]->_type != kPlaneTypeTransparent &&
+ planeList[j]->_type != kPlaneTypeTransparentPicture
+ ) {
+ Common::Rect outRects[4];
+ int splitCount = splitRects(*eraseList[i], planeList[j]->_screenRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ eraseList.add(outRects[splitCount]);
}
eraseList.erase_at(i);
@@ -285,94 +297,109 @@ void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeLi
}
void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
- ScreenItemList::size_type planeItemCount = _screenItemList.size();
- ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ const ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ // Items can be added to ScreenItemList and we don't want to process
+ // those new items, but the list also can grow smaller, so we need
+ // to check that we are still within the upper bound of the list and
+ // quit if we aren't any more
+ if (i >= _screenItemList.size()) {
+ break;
+ }
+
+ ScreenItem *item = _screenItemList[i];
+ if (item == nullptr) {
+ continue;
+ }
- for (ScreenItemList::size_type i = 0; i < planeItemCount; ++i) {
- ScreenItem *vitem = nullptr;
// NOTE: The original engine used an array without bounds checking
// so could just get the visible screen item directly; we need to
// verify that the index is actually within the valid range for
// the visible plane before accessing the item to avoid a range
// error.
+ const ScreenItem *visibleItem = nullptr;
if (i < visiblePlaneItemCount) {
- vitem = visiblePlane._screenItemList[i];
+ visibleItem = visiblePlane._screenItemList[i];
}
- ScreenItem *item = _screenItemList[i];
- if (i < _screenItemList.size() && item != nullptr) {
- if (item->_deleted) {
- // add item's rect to erase list
- if (
- i < visiblePlane._screenItemList.size() &&
- vitem != nullptr &&
- !vitem->_screenRect.isEmpty()
- ) {
- if (g_sci->_gfxRemap32->getRemapCount()) {
- mergeToRectList(vitem->_screenRect, eraseList);
- } else {
- eraseList.add(vitem->_screenRect);
- }
- }
- } else if (item->_created) {
- // add item to draw list
- item->calcRects(*this);
-
- if(!item->_screenRect.isEmpty()) {
- if (g_sci->_gfxRemap32->getRemapCount()) {
- drawList.add(item, item->_screenRect);
- mergeToRectList(item->_screenRect, eraseList);
- } else {
- drawList.add(item, item->_screenRect);
- }
- }
- } else if (item->_updated) {
- // add old rect to erase list, new item to draw list
- item->calcRects(*this);
+ // Keep erase rects for this screen item from drawing outside
+ // of its owner plane
+ Common::Rect visibleItemScreenRect;
+ if (visibleItem != nullptr) {
+ visibleItemScreenRect = visibleItem->_screenRect;
+ visibleItemScreenRect.clip(_screenRect);
+ }
+
+ if (item->_deleted) {
+ // Add item's rect to erase list
+ if (
+ visibleItem != nullptr &&
+ !visibleItemScreenRect.isEmpty()
+ ) {
if (g_sci->_gfxRemap32->getRemapCount()) {
- // if item and vitem don't overlap, ...
- if (item->_screenRect.isEmpty() ||
- i >= visiblePlaneItemCount ||
- vitem == nullptr ||
- vitem->_screenRect.isEmpty() ||
- !vitem->_screenRect.intersects(item->_screenRect)
- ) {
- // add item to draw list, and old rect to erase list
- if (!item->_screenRect.isEmpty()) {
- drawList.add(item, item->_screenRect);
- mergeToRectList(item->_screenRect, eraseList);
- }
- if (
- i < visiblePlaneItemCount &&
- vitem != nullptr &&
- !vitem->_screenRect.isEmpty()
- ) {
- mergeToRectList(vitem->_screenRect, eraseList);
- }
- } else {
- // otherwise, add bounding box of old+new to erase list,
- // and item to draw list
+ mergeToRectList(visibleItemScreenRect, eraseList);
+ } else {
+ eraseList.add(visibleItemScreenRect);
+ }
+ }
+ }
- // TODO: This was changed from disasm, verify please!
- Common::Rect extendedScreenRect = vitem->_screenRect;
- extendedScreenRect.extend(item->_screenRect);
+ if (!item->_created && !item->_updated) {
+ continue;
+ }
- drawList.add(item, item->_screenRect);
- mergeToRectList(extendedScreenRect, eraseList);
- }
+ item->calcRects(*this);
+ const Common::Rect itemScreenRect(item->_screenRect);
+
+ if (item->_created) {
+ // Add item to draw list
+ if(!itemScreenRect.isEmpty()) {
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(itemScreenRect, eraseList);
} else {
- // if no active remaps, just add item to draw list and old rect
- // to erase list
- if (!item->_screenRect.isEmpty()) {
- drawList.add(item, item->_screenRect);
+ drawList.add(item, itemScreenRect);
+ }
+ }
+ } else {
+ // Add old rect to erase list, new item to draw list
+
+ if (g_sci->_gfxRemap32->getRemapCount()) {
+ // If item and visibleItem don't overlap...
+ if (itemScreenRect.isEmpty() ||
+ visibleItem == nullptr ||
+ visibleItemScreenRect.isEmpty() ||
+ !visibleItemScreenRect.intersects(itemScreenRect)
+ ) {
+ // ...add item to draw list, and old rect to erase list...
+ if (!itemScreenRect.isEmpty()) {
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(itemScreenRect, eraseList);
}
- if (
- i < visiblePlaneItemCount &&
- vitem != nullptr &&
- !vitem->_screenRect.isEmpty()
- ) {
- eraseList.add(vitem->_screenRect);
+ if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) {
+ mergeToRectList(visibleItemScreenRect, eraseList);
}
+ } else {
+ // ...otherwise, add bounding box of old+new to erase list,
+ // and item to draw list
+ Common::Rect extendedScreenRect = visibleItemScreenRect;
+ extendedScreenRect.extend(itemScreenRect);
+
+ drawList.add(item, itemScreenRect);
+ mergeToRectList(extendedScreenRect, eraseList);
+ }
+ } else {
+ // If no active remaps, just add item to draw list and old rect
+ // to erase list
+
+ // TODO: SCI3 update rects for VMD?
+ if (!itemScreenRect.isEmpty()) {
+ drawList.add(item, itemScreenRect);
+ }
+ if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) {
+ eraseList.add(visibleItemScreenRect);
}
}
}
@@ -385,40 +412,44 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
// We store the current size of the drawlist, as we want to loop
// over the currently inserted entries later.
DrawList::size_type drawListSizePrimary = drawList.size();
+ const RectList::size_type eraseListCount = eraseList.size();
- if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"????
+ // TODO: Figure out which games need which rendering method
+ if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"
_screenItemList.sort();
- bool encounteredPic = false;
- bool v81 = false;
+ bool pictureDrawn = false;
+ bool screenItemDrawn = false;
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- const Common::Rect *rect = eraseList[i];
+ for (RectList::size_type i = 0; i < eraseListCount; ++i) {
+ const Common::Rect &rect = *eraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
ScreenItem *item = _screenItemList[j];
- if (j < _screenItemList.size() && item != nullptr) {
- if (rect->intersects(item->_screenRect)) {
- const Common::Rect intersection = rect->findIntersectingRect(item->_screenRect);
- if (!item->_deleted) {
- if (encounteredPic) {
- if (item->_celInfo.type == kCelTypePic) {
- if (v81 || item->_celInfo.celNo == 0) {
- drawList.add(item, intersection);
- }
- } else {
- if (!item->_updated && !item->_created) {
- drawList.add(item, intersection);
- }
- v81 = true;
+ if (item == nullptr) {
+ continue;
+ }
+
+ if (rect.intersects(item->_screenRect)) {
+ const Common::Rect intersection = rect.findIntersectingRect(item->_screenRect);
+ if (!item->_deleted) {
+ if (pictureDrawn) {
+ if (item->_celInfo.type == kCelTypePic) {
+ if (screenItemDrawn || item->_celInfo.celNo == 0) {
+ mergeToDrawList(j, intersection, drawList);
}
} else {
if (!item->_updated && !item->_created) {
- drawList.add(item, intersection);
- }
- if (item->_celInfo.type == kCelTypePic) {
- encounteredPic = true;
+ mergeToDrawList(j, intersection, drawList);
}
+ screenItemDrawn = true;
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ mergeToDrawList(j, intersection, drawList);
+ }
+ if (item->_celInfo.type == kCelTypePic) {
+ pictureDrawn = true;
}
}
}
@@ -428,22 +459,23 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
_screenItemList.unsort();
} else {
- // add all items overlapping the erase list to the draw list
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ // Add all items overlapping the erase list to the draw list
+ for (RectList::size_type i = 0; i < eraseListCount; ++i) {
+ const Common::Rect &rect = *eraseList[i];
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
ScreenItem *item = _screenItemList[j];
if (
item != nullptr &&
!item->_created && !item->_updated && !item->_deleted &&
- eraseList[i]->intersects(item->_screenRect)
+ rect.intersects(item->_screenRect)
) {
- drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect));
+ drawList.add(item, rect.findIntersectingRect(item->_screenRect));
}
}
}
}
- if (g_sci->_gfxRemap32->getRemapCount() == 0) { // no remaps active?
+ if (g_sci->_gfxRemap32->getRemapCount() == 0) {
// Add all items that overlap with items in the drawlist and have higher
// priority.
@@ -451,23 +483,28 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
// those that were added because of the erase list in the previous loop,
// or those to be added in this loop.
for (DrawList::size_type i = 0; i < drawListSizePrimary; ++i) {
- DrawItem *dli = drawList[i];
+ const DrawItem *drawListEntry = nullptr;
+ if (i < drawList.size()) {
+ drawListEntry = drawList[i];
+ }
- for (ScreenItemList::size_type j = 0; j < planeItemCount; ++j) {
- ScreenItem *sli = _screenItemList[j];
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ ScreenItem *newItem = nullptr;
+ if (j < _screenItemList.size()) {
+ newItem = _screenItemList[j];
+ }
if (
- i < drawList.size() && dli != nullptr &&
- j < _screenItemList.size() && sli != nullptr &&
- !sli->_created && !sli->_updated && !sli->_deleted
+ drawListEntry != nullptr && newItem != nullptr &&
+ !newItem->_created && !newItem->_updated && !newItem->_deleted
) {
- ScreenItem *item = dli->screenItem;
+ const ScreenItem *drawnItem = drawListEntry->screenItem;
if (
- (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) &&
- dli->rect.intersects(sli->_screenRect)
+ (newItem->_priority > drawnItem->_priority || (newItem->_priority == drawnItem->_priority && newItem->_object > drawnItem->_object)) &&
+ drawListEntry->rect.intersects(newItem->_screenRect)
) {
- drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect));
+ mergeToDrawList(j, drawListEntry->rect.findIntersectingRect(newItem->_screenRect), drawList);
}
}
}
@@ -475,14 +512,11 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList
}
decrementScreenItemArrayCounts(&visiblePlane, false);
- _screenItemList.pack();
- visiblePlane._screenItemList.pack();
}
void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) {
- // The size of the screenItemList may change, so it is
- // critical to re-check the size on each iteration
- for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
ScreenItem *item = _screenItemList[i];
if (item != nullptr) {
@@ -495,7 +529,7 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force
visiblePlane->_screenItemList.findByObject(item->_object) != nullptr
)
) {
- *visiblePlane->_screenItemList[i] = *_screenItemList[i];
+ *visiblePlane->_screenItemList[i] = *item;
}
if (item->_updated) {
@@ -514,175 +548,180 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force
if (item->_deleted) {
item->_deleted--;
if (!item->_deleted) {
- visiblePlane->_screenItemList.erase_at(i);
+ if (visiblePlane != nullptr && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr) {
+ visiblePlane->_screenItemList.erase_at(i);
+ }
_screenItemList.erase_at(i);
}
}
}
}
+
+ _screenItemList.pack();
+ if (visiblePlane != nullptr) {
+ visiblePlane->_screenItemList.pack();
+ }
}
-void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const {
- if (_type == kPlaneTypeTransparent) {
- for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
- const Common::Rect *r = transparentEraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
- if (item != nullptr) {
- if (r->intersects(item->_screenRect)) {
- mergeToDrawList(j, *r, drawList);
- }
+void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const {
+ const RectList::size_type higherEraseCount = higherEraseList.size();
+
+ if (_type == kPlaneTypeTransparent || _type == kPlaneTypeTransparentPicture) {
+ for (RectList::size_type i = 0; i < higherEraseCount; ++i) {
+ const Common::Rect &r = *higherEraseList[i];
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
}
}
}
} else {
- for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
- Common::Rect *r = transparentEraseList[i];
- if (r->intersects(_screenRect)) {
- r->clip(_screenRect);
- mergeToRectList(*r, eraseList);
-
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
-
- if (item != nullptr) {
- if (r->intersects(item->_screenRect)) {
- mergeToDrawList(j, *r, drawList);
- }
+ for (RectList::size_type i = 0; i < higherEraseCount; ++i) {
+ Common::Rect r = *higherEraseList[i];
+ if (r.intersects(_screenRect)) {
+ r.clip(_screenRect);
+ mergeToRectList(r, eraseList);
+
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
}
}
- Common::Rect ptr[4];
- const Common::Rect *r2 = transparentEraseList[i];
- int count = splitRects(*r2, *r, ptr);
- for (int k = count - 1; k >= 0; --k) {
- transparentEraseList.add(ptr[k]);
+ Common::Rect outRects[4];
+ const Common::Rect &r2 = *higherEraseList[i];
+ int splitCount = splitRects(r2, r, outRects);
+ while (splitCount--) {
+ higherEraseList.add(outRects[splitCount]);
}
- transparentEraseList.erase_at(i);
+ higherEraseList.erase_at(i);
}
}
- transparentEraseList.pack();
+ higherEraseList.pack();
}
}
-void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const {
- for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
- const Common::Rect &r = drawList[i]->rect;
-
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
- if (item != nullptr) {
- if (r.intersects(item->_screenRect)) {
- mergeToDrawList(j, r, transparentDrawList);
- }
+void Plane::filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const {
+ const DrawList::size_type lowerDrawCount = lowerDrawList.size();
+ for (DrawList::size_type i = 0; i < lowerDrawCount; ++i) {
+ const Common::Rect &r = lowerDrawList[i]->rect;
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
}
}
}
}
-void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const {
- for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
- const Common::Rect &r = *eraseList[i];
- for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
- ScreenItem *item = _screenItemList[j];
-
- if (item != nullptr) {
- if (r.intersects(item->_screenRect)) {
- mergeToDrawList(j, r, drawList);
- }
+void Plane::filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const {
+ const RectList::size_type lowerEraseCount = lowerEraseList.size();
+ for (RectList::size_type i = 0; i < lowerEraseCount; ++i) {
+ const Common::Rect &r = *lowerEraseList[i];
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {
+ const ScreenItem *item = _screenItemList[j];
+ if (item != nullptr && r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
}
}
}
}
void Plane::mergeToDrawList(const ScreenItemList::size_type index, const Common::Rect &rect, DrawList &drawList) const {
- RectList rects;
-
- ScreenItem *item = _screenItemList[index];
- Common::Rect r = item->_screenRect;
+ RectList mergeList;
+ ScreenItem &item = *_screenItemList[index];
+ Common::Rect r = item._screenRect;
r.clip(rect);
- rects.add(r);
+ mergeList.add(r);
- for (RectList::size_type i = 0; i < rects.size(); ++i) {
- r = *rects[i];
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ r = *mergeList[i];
- for (DrawList::size_type j = 0; j < drawList.size(); ++j) {
- const DrawItem *drawitem = drawList[j];
- if (item->_object == drawitem->screenItem->_object) {
- if (drawitem->rect.contains(r)) {
- rects.erase_at(i);
+ const DrawList::size_type drawCount = drawList.size();
+ for (DrawList::size_type j = 0; j < drawCount; ++j) {
+ const DrawItem &drawItem = *drawList[j];
+ if (item._object == drawItem.screenItem->_object) {
+ if (drawItem.rect.contains(r)) {
+ mergeList.erase_at(i);
break;
}
Common::Rect outRects[4];
- const int count = splitRects(r, drawitem->rect, outRects);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- rects.add(outRects[k]);
+ int splitCount = splitRects(r, drawItem.rect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
}
- rects.erase_at(i);
+ mergeList.erase_at(i);
// proceed to the next rect
- r = *rects[++i];
+ r = *mergeList[++i];
}
}
}
}
- rects.pack();
+ mergeList.pack();
- for (RectList::size_type i = 0; i < rects.size(); ++i) {
- drawList.add(item, *rects[i]);
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ drawList.add(&item, *mergeList[i]);
}
}
-void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const {
- RectList temp;
- temp.add(rect);
+void Plane::mergeToRectList(const Common::Rect &rect, RectList &eraseList) const {
+ RectList mergeList;
+ Common::Rect r;
+ mergeList.add(rect);
- for (RectList::size_type i = 0; i < temp.size(); ++i) {
- Common::Rect r = *temp[i];
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ r = *mergeList[i];
- for (RectList::size_type j = 0; j < rectList.size(); ++j) {
- const Common::Rect *innerRect = rectList[j];
- if (innerRect->contains(r)) {
- temp.erase_at(i);
+ const RectList::size_type eraseCount = eraseList.size();
+ for (RectList::size_type j = 0; j < eraseCount; ++j) {
+ const Common::Rect &eraseRect = *eraseList[j];
+ if (eraseRect.contains(r)) {
+ mergeList.erase_at(i);
break;
}
- Common::Rect out[4];
- const int count = splitRects(r, *innerRect, out);
- if (count != -1) {
- for (int k = count - 1; k >= 0; --k) {
- temp.add(out[k]);
+ Common::Rect outRects[4];
+ int splitCount = splitRects(r, eraseRect, outRects);
+ if (splitCount != -1) {
+ while (splitCount--) {
+ mergeList.add(outRects[splitCount]);
}
- temp.erase_at(i);
+ mergeList.erase_at(i);
// proceed to the next rect
- r = *temp[++i];
+ r = *mergeList[++i];
}
}
}
- temp.pack();
+ mergeList.pack();
- for (RectList::size_type i = 0; i < temp.size(); ++i) {
- rectList.add(*temp[i]);
+ for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
+ eraseList.add(*mergeList[i]);
}
}
void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
- for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
- if (*screenItemPtr != nullptr) {
- ScreenItem &screenItem = **screenItemPtr;
- if (!screenItem._deleted) {
- screenItem.calcRects(*this);
- if (!screenItem._screenRect.isEmpty()) {
- drawList.add(&screenItem, screenItem._screenRect);
- }
+ const ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ ScreenItem *screenItem = _screenItemList[i];
+ if (screenItem != nullptr && !screenItem->_deleted) {
+ screenItem->calcRects(*this);
+ if (!screenItem->_screenRect.isEmpty()) {
+ mergeToDrawList(i, screenItem->_screenRect, drawList);
}
}
}
@@ -696,21 +735,27 @@ void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList
breakDrawListByPlanes(drawList, planeList);
--_redrawAllCount;
decrementScreenItemArrayCounts(visiblePlane, true);
- _screenItemList.pack();
- if (visiblePlane != nullptr) {
- visiblePlane->_screenItemList.pack();
- }
}
void Plane::setType() {
- if (_pictureId == kPlanePicOpaque) {
- _type = kPlaneTypeOpaque;
- } else if (_pictureId == kPlanePicTransparent) {
- _type = kPlaneTypeTransparent;
- } else if (_pictureId == kPlanePicColored) {
+ switch (_pictureId) {
+ case kPlanePicColored:
_type = kPlaneTypeColored;
- } else {
- _type = kPlaneTypePicture;
+ break;
+ case kPlanePicTransparent:
+ _type = kPlaneTypeTransparent;
+ break;
+ case kPlanePicOpaque:
+ _type = kPlaneTypeOpaque;
+ break;
+ case kPlanePicTransparentPicture:
+ _type = kPlaneTypeTransparentPicture;
+ break;
+ default:
+ if (_type != kPlaneTypeTransparentPicture) {
+ _type = kPlaneTypePicture;
+ }
+ break;
}
}
@@ -731,10 +776,12 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
_planeRect.right > other->_planeRect.right ||
_planeRect.bottom > other->_planeRect.bottom
) {
+ // the plane moved or got larger
_redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
- _updated = g_sci->_gfxFrameout->getScreenCount();
+ _moved = g_sci->_gfxFrameout->getScreenCount();
} else if (_planeRect != other->_planeRect) {
- _updated = g_sci->_gfxFrameout->getScreenCount();
+ // the plane got smaller
+ _moved = g_sci->_gfxFrameout->getScreenCount();
}
if (_priority != other->_priority) {
@@ -755,7 +802,7 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
_deleted = 0;
if (_created == 0) {
- _moved = g_sci->_gfxFrameout->getScreenCount();
+ _updated = g_sci->_gfxFrameout->getScreenCount();
}
convertGameRectToPlaneRect();
@@ -801,18 +848,22 @@ void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool
}
void Plane::remapMarkRedraw() {
- for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
- if (*screenItemPtr != nullptr) {
- ScreenItem &screenItem = **screenItemPtr;
- if (screenItem.getCelObj()._remap && !screenItem._deleted && !screenItem._created) {
- screenItem._updated = g_sci->_gfxFrameout->getScreenCount();
- }
+ ScreenItemList::size_type screenItemCount = _screenItemList.size();
+ for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ ScreenItem *screenItem = _screenItemList[i];
+ if (
+ screenItem != nullptr &&
+ !screenItem->_deleted && !screenItem->_created &&
+ screenItem->getCelObj()._remap
+ ) {
+ screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
}
}
}
#pragma mark -
#pragma mark PlaneList
+
void PlaneList::add(Plane *plane) {
for (iterator it = begin(); it != end(); ++it) {
if ((*it)->_priority > plane->_priority) {
diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h
index c93fb5b64e..acd535e75a 100644
--- a/engines/sci/graphics/plane32.h
+++ b/engines/sci/graphics/plane32.h
@@ -32,19 +32,21 @@
namespace Sci {
enum PlaneType {
- kPlaneTypeColored = 0,
- kPlaneTypePicture = 1,
- kPlaneTypeTransparent = 2,
- kPlaneTypeOpaque = 3
+ kPlaneTypeColored = 0,
+ kPlaneTypePicture = 1,
+ kPlaneTypeTransparent = 2,
+ kPlaneTypeOpaque = 3,
+ kPlaneTypeTransparentPicture = 4
};
enum PlanePictureCodes {
- // NOTE: Any value at or below 65532 means the plane
+ // NOTE: Any value at or below 65531 means the plane
// is a kPlaneTypePicture.
- kPlanePic = 65532,
- kPlanePicOpaque = 65533,
- kPlanePicTransparent = 65534,
- kPlanePicColored = 65535
+ kPlanePic = 65531,
+ kPlanePicTransparentPicture = 65532,
+ kPlanePicOpaque = 65533,
+ kPlanePicTransparent = 65534,
+ kPlanePicColored = 65535
};
#pragma mark -
@@ -62,7 +64,14 @@ public:
#pragma mark DrawList
struct DrawItem {
+ /**
+ * The screen item to draw.
+ */
ScreenItem *screenItem;
+
+ /**
+ * The target rectangle of the draw operation.
+ */
Common::Rect rect;
inline bool operator<(const DrawItem &other) const {
@@ -189,16 +198,15 @@ public:
* not match
* - `deleted` is set when the plane is deleted by a
* kernel call
- * - `moved` is set when the plane is synchronised from
- * another plane and is not already in the "created"
- * state
+ * - `moved` is set when the plane has been moved or
+ * resized
*/
int _created, _updated, _deleted, _moved;
/**
* The vanishing point for the plane. Used when
- * calculating the correct scaling of the plane's screen
- * items according to their position.
+ * automatically calculating the correct scaling of the
+ * plane's screen items according to their position.
*/
Common::Point _vanishingPoint;
@@ -358,42 +366,33 @@ public:
private:
/**
* Splits all rects in the given draw list at the edges
- * of all non-transparent planes above the current
- * plane.
+ * of all higher-priority, non-transparent, intersecting
+ * planes.
*/
void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;
/**
- * Splits all rects in the given erase list rects at the
- * edges of all non-transparent planes above the current
- * plane.
+ * Splits all rects in the given erase list at the
+ * edges of higher-priority, non-transparent,
+ * intersecting planes.
*/
void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const;
/**
- * Synchronises changes to screen items from the current
- * plane to the visible plane and deletes screen items
- * from the current plane that have been marked as
- * deleted. If `forceUpdate` is true, all screen items
- * on the visible plane will be updated, even if they
- * are not marked as having changed.
- */
- void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);
-
- /**
- * Merges the screen item from this plane at the given
- * index into the given draw list, clipped to the given
- * rect. TODO: Finish documenting
+ * Adds the screen item at `index` into `drawList`,
+ * ensuring it is only drawn within the bounds of
+ * `rect`. If an existing draw list entry exists
+ * for this screen item, it will be modified.
+ * Otherwise, a new entry will be added.
*/
void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const;
/**
- * Adds the given rect into the given rect list,
- * merging it with other rects already inside the list,
- * if possible, to avoid overdraw. TODO: Finish
- * documenting
+ * Merges `rect` with an existing rect in `eraseList`,
+ * if possible. Otherwise, adds the rect as a new entry
+ * to `eraseList`.
*/
- void mergeToRectList(const Common::Rect &rect, RectList &rectList) const;
+ void mergeToRectList(const Common::Rect &rect, RectList &eraseList) const;
public:
/**
@@ -406,19 +405,73 @@ public:
void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
/**
- * TODO: Documentation
+ * Synchronises changes to screen items from the current
+ * plane to the visible plane and deletes screen items
+ * from the current plane that have been marked as
+ * deleted. If `forceUpdate` is true, all screen items
+ * on the visible plane will be updated, even if they
+ * are not marked as having changed.
+ */
+ void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);
+
+ /**
+ * This method is called from the highest priority plane
+ * to the lowest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be redrawn because they intersect entries
+ * in the `higherEraseList`.
+ *
+ * If this plane is opaque, all intersecting erase rects
+ * in `lowerEraseList` are removed, as they would be
+ * completely overwritten by the contents of this plane.
+ *
+ * If this plane is transparent, erase rects from the
+ * `lowerEraseList` are added to the erase list for this
+ * plane, so that lower planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param eraseList The erase list for this plane.
+ * @param higherEraseList The erase list for a plane
+ * above this plane.
*/
- void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const;
+ void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const;
/**
- * TODO: Documentation
+ * This method is called from the lowest priority plane
+ * to the highest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be drawn because the lower plane is being
+ * redrawn and potentially transparent screen items
+ * from this plane would draw over the lower priority
+ * plane's screen items.
+ *
+ * This method applies only to transparent planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param eraseList The erase list for a plane below
+ * this plane.
*/
- void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const;
+ void filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const;
/**
- * TODO: Documentation
+ * This method is called from the lowest priority plane
+ * to the highest priority plane.
+ *
+ * Adds screen items from this plane to the draw list
+ * that must be drawn because the lower plane is being
+ * redrawn and potentially transparent screen items
+ * from this plane would draw over the lower priority
+ * plane's screen items.
+ *
+ * This method applies only to transparent planes.
+ *
+ * @param drawList The draw list for this plane.
+ * @param lowerDrawList The draw list for a plane below
+ * this plane.
*/
- void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const;
+ void filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const;
/**
* Updates all of the plane's non-deleted screen items
@@ -442,6 +495,8 @@ private:
using PlaneListBase::push_back;
public:
+ typedef int size_type;
+
// A method for finding the index of a plane inside a
// PlaneList is used because entries in the main plane
// list and visible plane list of GfxFrameout are
diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp
index e331eaf971..2abf03ea29 100644
--- a/engines/sci/graphics/remap.cpp
+++ b/engines/sci/graphics/remap.cpp
@@ -21,31 +21,23 @@
*/
#include "sci/sci.h"
-#include "sci/resource.h"
#include "sci/graphics/palette.h"
-#include "sci/graphics/palette32.h"
#include "sci/graphics/remap.h"
#include "sci/graphics/screen.h"
namespace Sci {
-#pragma mark -
-#pragma mark SCI16 remapping (QFG4 demo)
-
GfxRemap::GfxRemap(GfxPalette *palette)
: _palette(palette) {
_remapOn = false;
resetRemapping();
}
-GfxRemap::~GfxRemap() {
-}
-
byte GfxRemap::remapColor(byte remappedColor, byte screenColor) {
assert(_remapOn);
- if (_remappingType[remappedColor] == kRemappingByRange)
+ if (_remappingType[remappedColor] == kRemapByRange)
return _remappingByRange[screenColor];
- else if (_remappingType[remappedColor] == kRemappingByPercent)
+ else if (_remappingType[remappedColor] == kRemapByPercent)
return _remappingByPercent[screenColor];
else
error("remapColor(): Color %d isn't remapped", remappedColor);
@@ -58,7 +50,7 @@ void GfxRemap::resetRemapping() {
_remappingPercentToSet = 0;
for (int i = 0; i < 256; i++) {
- _remappingType[i] = kRemappingNone;
+ _remappingType[i] = kRemapNone;
_remappingByPercent[i] = i;
_remappingByRange[i] = i;
}
@@ -80,7 +72,7 @@ void GfxRemap::setRemappingPercent(byte color, byte percent) {
_remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
}
- _remappingType[color] = kRemappingByPercent;
+ _remappingType[color] = kRemapByPercent;
}
void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {
@@ -90,7 +82,7 @@ void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {
_remappingByRange[i] = i + base;
}
- _remappingType[color] = kRemappingByRange;
+ _remappingType[color] = kRemapByRange;
}
void GfxRemap::updateRemapping() {
@@ -104,283 +96,4 @@ void GfxRemap::updateRemapping() {
}
}
}
-
-#pragma mark -
-#pragma mark SCI32 remapping
-
-#ifdef ENABLE_SCI32
-
-GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) {
- for (int i = 0; i < REMAP_COLOR_COUNT; i++)
- _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
- _noMapStart = _noMapCount = 0;
- _update = false;
- _remapCount = 0;
-
- // The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle
- _remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) ? 244 : 254;
-}
-
-void GfxRemap32::remapOff(byte color) {
- if (!color) {
- for (int i = 0; i < REMAP_COLOR_COUNT; i++)
- _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
-
- _remapCount = 0;
- } else {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- const byte index = _remapEndColor - color;
- _remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
- _remapCount--;
- }
-
- _update = true;
-}
-
-void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- _remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemappingByRange);
- initColorArrays(_remapEndColor - color);
- _remapCount++;
- _update = true;
-}
-
-void GfxRemap32::setRemappingPercent(byte color, byte percent) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemappingByPercent);
- initColorArrays(_remapEndColor - color);
- _remapCount++;
- _update = true;
-}
-
-void GfxRemap32::setRemappingToGray(byte color, byte gray) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemappingToGray);
- initColorArrays(_remapEndColor - color);
- _remapCount++;
- _update = true;
-}
-
-void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemappingToPercentGray);
- initColorArrays(_remapEndColor - color);
- _remapCount++;
- _update = true;
-}
-
-void GfxRemap32::setNoMatchRange(byte from, byte count) {
- _noMapStart = from;
- _noMapCount = count;
-}
-
-bool GfxRemap32::remapEnabled(byte color) const {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- const byte index = _remapEndColor - color;
- return (_remaps[index].type != kRemappingNone);
-}
-
-byte GfxRemap32::remapColor(byte color, byte target) {
- assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
- const byte index = _remapEndColor - color;
- if (_remaps[index].type != kRemappingNone)
- return _remaps[index].remap[target];
- else
- return target;
-}
-
-void GfxRemap32::initColorArrays(byte index) {
- Palette *curPalette = &_palette->_sysPalette;
- RemapParams *curRemap = &_remaps[index];
-
- memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color));
- memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color));
-}
-
-bool GfxRemap32::updateRemap(byte index, bool palChanged) {
- int result;
- RemapParams *curRemap = &_remaps[index];
- const Palette *curPalette = &_palette->_sysPalette;
- const Palette *nextPalette = _palette->getNextPalette();
- bool changed = false;
-
- if (!_update && !palChanged)
- return false;
-
- Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false);
-
- switch (curRemap->type) {
- case kRemappingNone:
- return false;
- case kRemappingByRange:
- for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) {
- if (curRemap->from <= i && i <= curRemap->to)
- result = i + curRemap->base;
- else
- result = i;
-
- if (curRemap->remap[i] != result) {
- changed = true;
- curRemap->remap[i] = result;
- }
-
- curRemap->colorChanged[i] = true;
- }
- return changed;
- case kRemappingByPercent:
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- // NOTE: This method uses nextPalette instead of curPalette
- Color color = nextPalette->colors[i];
-
- if (curRemap->curColor[i] != color) {
- curRemap->colorChanged[i] = true;
- curRemap->curColor[i] = color;
- }
-
- if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i]) {
- byte red = CLIP<byte>(color.r * curRemap->percent / 100, 0, 255);
- byte green = CLIP<byte>(color.g * curRemap->percent / 100, 0, 255);
- byte blue = CLIP<byte>(color.b * curRemap->percent / 100, 0, 255);
- byte used = curRemap->targetColor[i].used;
-
- Color newColor = { used, red, green, blue };
- if (curRemap->targetColor[i] != newColor) {
- _targetChanged[i] = true;
- curRemap->targetColor[i] = newColor;
- }
- }
- }
-
- changed = applyRemap(index);
- Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
- curRemap->oldPercent = curRemap->percent;
- return changed;
- case kRemappingToGray:
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- Color color = curPalette->colors[i];
-
- if (curRemap->curColor[i] != color) {
- curRemap->colorChanged[i] = true;
- curRemap->curColor[i] = color;
- }
-
- if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) {
- byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8;
- byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255);
- byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255);
- byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255);
- byte used = curRemap->targetColor[i].used;
-
- Color newColor = { used, red, green, blue };
- if (curRemap->targetColor[i] != newColor) {
- _targetChanged[i] = true;
- curRemap->targetColor[i] = newColor;
- }
- }
- }
-
- changed = applyRemap(index);
- Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
- curRemap->oldGray = curRemap->gray;
- return changed;
- case kRemappingToPercentGray:
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- Color color = curPalette->colors[i];
-
- if (curRemap->curColor[i] != color) {
- curRemap->colorChanged[i] = true;
- curRemap->curColor[i] = color;
- }
-
- if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) {
- byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8;
- lumosity = lumosity * curRemap->percent / 100;
- byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255);
- byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255);
- byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255);
- byte used = curRemap->targetColor[i].used;
-
- Color newColor = { used, red, green, blue };
- if (curRemap->targetColor[i] != newColor) {
- _targetChanged[i] = true;
- curRemap->targetColor[i] = newColor;
- }
- }
- }
-
- changed = applyRemap(index);
- Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
- curRemap->oldPercent = curRemap->percent;
- curRemap->oldGray = curRemap->gray;
- return changed;
- default:
- return false;
- }
-}
-
-static int colorDistance(Color a, Color b) {
- int rDiff = (a.r - b.r) * (a.r - b.r);
- int gDiff = (a.g - b.g) * (a.g - b.g);
- int bDiff = (a.b - b.b) * (a.b - b.b);
- return rDiff + gDiff + bDiff;
-}
-
-bool GfxRemap32::applyRemap(byte index) {
- RemapParams *curRemap = &_remaps[index];
- const bool *cycleMap = _palette->getCyclemap();
- bool unmappedColors[NON_REMAPPED_COLOR_COUNT];
- Color newColors[NON_REMAPPED_COLOR_COUNT];
- bool changed = false;
-
- Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false);
- if (_noMapCount)
- Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true);
-
- for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) {
- if (cycleMap[i])
- unmappedColors[i] = true;
- }
-
- int curColor = 0;
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- if (curRemap->colorChanged[i] && !unmappedColors[i])
- newColors[curColor++] = curRemap->curColor[i];
- }
-
- for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
- Color targetColor = curRemap->targetColor[i];
- bool colorChanged = curRemap->colorChanged[curRemap->remap[i]];
-
- if (!_targetChanged[i] && !colorChanged)
- continue;
-
- if (_targetChanged[i] && colorChanged)
- if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i])
- continue;
-
- int diff = 0;
- int16 result = _palette->matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors);
- if (result != -1 && curRemap->remap[i] != result) {
- changed = true;
- curRemap->remap[i] = result;
- curRemap->distance[i] = diff;
- }
- }
-
- return changed;
-}
-
-bool GfxRemap32::remapAllTables(bool palChanged) {
- bool changed = false;
-
- for (int i = 0; i < REMAP_COLOR_COUNT; i++) {
- changed |= updateRemap(i, palChanged);
- }
-
- _update = false;
- return changed;
-}
-
-#endif
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h
index d012568f7f..98177f6d19 100644
--- a/engines/sci/graphics/remap.h
+++ b/engines/sci/graphics/remap.h
@@ -24,42 +24,36 @@
#define SCI_GRAPHICS_REMAP_H
#include "common/array.h"
-#include "sci/graphics/helpers.h"
+#include "common/serializer.h"
namespace Sci {
class GfxScreen;
-enum ColorRemappingType {
- kRemappingNone = 0,
- kRemappingByRange = 1,
- kRemappingByPercent = 2,
- kRemappingToGray = 3,
- kRemappingToPercentGray = 4
-};
-
-#define REMAP_COLOR_COUNT 9
-#define NON_REMAPPED_COLOR_COUNT 236
-
/**
- * Remap class, handles color remapping
+ * This class handles color remapping for the QFG4 demo.
*/
class GfxRemap {
+private:
+ enum ColorRemappingType {
+ kRemapNone = 0,
+ kRemapByRange = 1,
+ kRemapByPercent = 2
+ };
+
public:
GfxRemap(GfxPalette *_palette);
- ~GfxRemap();
void resetRemapping();
void setRemappingPercent(byte color, byte percent);
void setRemappingRange(byte color, byte from, byte to, byte base);
bool isRemapped(byte color) const {
- return _remapOn && (_remappingType[color] != kRemappingNone);
+ return _remapOn && (_remappingType[color] != kRemapNone);
}
byte remapColor(byte remappedColor, byte screenColor);
void updateRemapping();
private:
- GfxScreen *_screen;
GfxPalette *_palette;
bool _remapOn;
@@ -68,87 +62,6 @@ private:
byte _remappingByRange[256];
uint16 _remappingPercentToSet;
};
-
-#ifdef ENABLE_SCI32
-
-struct RemapParams {
- byte from;
- byte to;
- byte base;
- byte gray;
- byte oldGray;
- byte percent;
- byte oldPercent;
- ColorRemappingType type;
- Color curColor[256];
- Color targetColor[256];
- byte distance[256];
- byte remap[256];
- bool colorChanged[256];
-
- RemapParams() {
- from = to = base = gray = oldGray = percent = oldPercent = 0;
- type = kRemappingNone;
-
- // curColor and targetColor are initialized in GfxRemap32::initColorArrays
- memset(curColor, 0, 256 * sizeof(Color));
- memset(targetColor, 0, 256 * sizeof(Color));
- memset(distance, 0, 256);
- for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)
- remap[i] = i;
- Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true);
- }
-
- RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, ColorRemappingType type_) {
- from = from_;
- to = to_;
- base = base_;
- gray = oldGray = gray_;
- percent = oldPercent = percent_;
- type = type_;
-
- // curColor and targetColor are initialized in GfxRemap32::initColorArrays
- memset(curColor, 0, 256 * sizeof(Color));
- memset(targetColor, 0, 256 * sizeof(Color));
- memset(distance, 0, 256);
- for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)
- remap[i] = i;
- Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true);
- }
-};
-
-class GfxRemap32 {
-public:
- GfxRemap32(GfxPalette32 *palette);
- ~GfxRemap32() {}
-
- void remapOff(byte color);
- void setRemappingRange(byte color, byte from, byte to, byte base);
- void setRemappingPercent(byte color, byte percent);
- void setRemappingToGray(byte color, byte gray);
- void setRemappingToPercentGray(byte color, byte gray, byte percent);
- void setNoMatchRange(byte from, byte count);
- bool remapEnabled(byte color) const;
- byte remapColor(byte color, byte target);
- bool remapAllTables(bool palChanged);
- int getRemapCount() const { return _remapCount; }
- int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; }
- int getEndColor() const { return _remapEndColor; }
-private:
- GfxPalette32 *_palette;
- RemapParams _remaps[REMAP_COLOR_COUNT];
- bool _update;
- byte _noMapStart, _noMapCount;
- bool _targetChanged[NON_REMAPPED_COLOR_COUNT];
- byte _remapEndColor;
- int _remapCount;
-
- void initColorArrays(byte index);
- bool applyRemap(byte index);
- bool updateRemap(byte index, bool palChanged);
-};
-#endif
-
} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp
new file mode 100644
index 0000000000..d5a2362f14
--- /dev/null
+++ b/engines/sci/graphics/remap32.cpp
@@ -0,0 +1,468 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/sci.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
+
+namespace Sci {
+
+#pragma mark SingleRemap
+
+void SingleRemap::reset() {
+ _lastPercent = 100;
+ _lastGray = 0;
+
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 0; i < remapStartColor; ++i) {
+ const Color &color = currentPalette.colors[i];
+ _remapColors[i] = i;
+ _originalColors[i] = color;
+ _originalColorsChanged[i] = true;
+ _idealColors[i] = color;
+ _idealColorsChanged[i] = false;
+ _matchDistances[i] = 0;
+ }
+}
+
+bool SingleRemap::update() {
+ switch (_type) {
+ case kRemapNone:
+ break;
+ case kRemapByRange:
+ return updateRange();
+ case kRemapByPercent:
+ return updateBrightness();
+ case kRemapToGray:
+ return updateSaturation();
+ case kRemapToPercentGray:
+ return updateSaturationAndBrightness();
+ default:
+ error("Illegal remap type %d", _type);
+ }
+
+ return false;
+}
+
+bool SingleRemap::updateRange() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ bool updated = false;
+
+ for (uint i = 0; i < remapStartColor; ++i) {
+ uint8 targetColor;
+ if (_from <= i && i <= _to) {
+ targetColor = i + _delta;
+ } else {
+ targetColor = i;
+ }
+
+ if (_remapColors[i] != targetColor) {
+ updated = true;
+ _remapColors[i] = targetColor;
+ }
+
+ _originalColorsChanged[i] = true;
+ }
+
+ return updated;
+}
+
+bool SingleRemap::updateBrightness() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette();
+ for (uint i = 1; i < remapStartColor; ++i) {
+ Color color(nextPalette.colors[i]);
+
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_percent != _lastPercent || _originalColorsChanged[i]) {
+ // NOTE: SSCI checked if percent was over 100 and only
+ // then clipped values, but we always unconditionally
+ // ensure the result is in the correct range
+ color.r = MIN(255, (uint16)color.r * _percent / 100);
+ color.g = MIN(255, (uint16)color.g * _percent / 100);
+ color.b = MIN(255, (uint16)color.b * _percent / 100);
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastPercent = _percent;
+ return updated;
+}
+
+bool SingleRemap::updateSaturation() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 1; i < remapStartColor; ++i) {
+ Color color(currentPalette.colors[i]);
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_gray != _lastGray || _originalColorsChanged[i]) {
+ const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100;
+
+ color.r = MIN(255, color.r - ((color.r - luminosity) * _gray / 100));
+ color.g = MIN(255, color.g - ((color.g - luminosity) * _gray / 100));
+ color.b = MIN(255, color.b - ((color.b - luminosity) * _gray / 100));
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastGray = _gray;
+ return updated;
+}
+
+bool SingleRemap::updateSaturationAndBrightness() {
+ const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+ const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+ for (uint i = 1; i < remapStartColor; i++) {
+ Color color(currentPalette.colors[i]);
+ if (_originalColors[i] != color) {
+ _originalColorsChanged[i] = true;
+ _originalColors[i] = color;
+ }
+
+ if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) {
+ const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100;
+
+ color.r = MIN(255, color.r - ((color.r - luminosity) * _gray) / 100);
+ color.g = MIN(255, color.g - ((color.g - luminosity) * _gray) / 100);
+ color.b = MIN(255, color.b - ((color.b - luminosity) * _gray) / 100);
+
+ if (_idealColors[i] != color) {
+ _idealColorsChanged[i] = true;
+ _idealColors[i] = color;
+ }
+ }
+ }
+
+ const bool updated = apply();
+ Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+ Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+ _lastPercent = _percent;
+ _lastGray = _gray;
+ return updated;
+}
+
+bool SingleRemap::apply() {
+ const GfxRemap32 *const gfxRemap32 = g_sci->_gfxRemap32;
+ const uint8 remapStartColor = gfxRemap32->getStartColor();
+
+ // Blocked colors are not allowed to be used as target
+ // colors for the remap
+ bool blockedColors[236];
+ Common::fill(blockedColors, blockedColors + remapStartColor, false);
+
+ const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap();
+
+ const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount();
+ if (blockedRangeCount) {
+ const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart();
+ Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true);
+ }
+
+ for (uint i = 0; i < remapStartColor; ++i) {
+ if (paletteCycleMap[i]) {
+ blockedColors[i] = true;
+ }
+ }
+
+ // NOTE: SSCI did a loop over colors here to create a
+ // new array of updated, unblocked colors, but then
+ // never used it
+
+ bool updated = false;
+ for (uint i = 1; i < remapStartColor; ++i) {
+ int distance;
+
+ if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) {
+ continue;
+ }
+
+ if (
+ _idealColorsChanged[i] &&
+ _originalColorsChanged[_remapColors[i]] &&
+ _matchDistances[i] < 100 &&
+ colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i]
+ ) {
+ continue;
+ }
+
+ const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors);
+
+ if (bestColor != -1 && _remapColors[i] != bestColor) {
+ updated = true;
+ _remapColors[i] = bestColor;
+ _matchDistances[i] = distance;
+ }
+ }
+
+ return updated;
+}
+
+int SingleRemap::colorDistance(const Color &a, const Color &b) const {
+ int channelDistance = a.r - b.r;
+ int distance = channelDistance * channelDistance;
+ channelDistance = a.g - b.g;
+ distance += channelDistance * channelDistance;
+ channelDistance = a.b - b.b;
+ distance += channelDistance * channelDistance;
+ return distance;
+}
+
+int16 SingleRemap::matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const {
+ int16 bestIndex = -1;
+ int bestDistance = 0xFFFFF;
+ int distance = minimumDistance;
+ const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette();
+
+ for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) {
+ if (blockedIndexes[i]) {
+ continue;
+ }
+
+ distance = nextPalette.colors[i].r - color.r;
+ distance *= distance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ channelDistance = nextPalette.colors[i].g - color.g;
+ distance += channelDistance * channelDistance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ channelDistance = nextPalette.colors[i].b - color.b;
+ distance += channelDistance * channelDistance;
+ if (bestDistance <= distance) {
+ continue;
+ }
+ bestDistance = distance;
+ bestIndex = i;
+ }
+
+ // This value is only valid if the last index to
+ // perform a distance calculation was the best index
+ outDistance = distance;
+ return bestIndex;
+}
+
+#pragma mark -
+#pragma mark GfxRemap32
+
+GfxRemap32::GfxRemap32() :
+ _needsUpdate(false),
+ _blockedRangeStart(0),
+ _blockedRangeCount(0),
+ _remapStartColor(236),
+ _numActiveRemaps(0) {
+ // The `_remapStartColor` seems to always be 236 in SSCI,
+ // but if it is ever changed then the various C-style
+ // member arrays hard-coded to 236 need to be changed to
+ // match the highest possible value of `_remapStartColor`
+ assert(_remapStartColor == 236);
+
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) {
+ _remaps.resize(9);
+ } else {
+ _remaps.resize(19);
+ }
+
+ _remapEndColor = _remapStartColor + _remaps.size() - 1;
+}
+
+void GfxRemap32::remapOff(const uint8 color) {
+ if (color == 0) {
+ remapAllOff();
+ return;
+ }
+
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapOff: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+ singleRemap._type = kRemapNone;
+ --_numActiveRemaps;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapAllOff() {
+ for (uint i = 0, len = _remaps.size(); i < len; ++i) {
+ _remaps[i]._type = kRemapNone;
+ }
+
+ _numActiveRemaps = 0;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapByRange: %d out of remap range", color);
+ return;
+ }
+
+ if (from < 0) {
+ warning("GfxRemap32::remapByRange: attempt to remap negative color %d", from);
+ return;
+ }
+
+ if (to >= _remapStartColor) {
+ warning("GfxRemap32::remapByRange: attempt to remap into the remap zone at %d", to);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._from = from;
+ singleRemap._to = to;
+ singleRemap._delta = delta;
+ singleRemap._type = kRemapByRange;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapByPercent(const uint8 color, const int16 percent) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapByPercent: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._percent = percent;
+ singleRemap._type = kRemapByPercent;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapToGray(const uint8 color, const int8 gray) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapToGray: %d out of remap range", color);
+ return;
+ }
+
+ if (gray < 0 || gray > 100) {
+ error("RemapToGray percent out of range; gray = %d", gray);
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._gray = gray;
+ singleRemap._type = kRemapToGray;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::remapToPercentGray(const uint8 color, const int16 gray, const int16 percent) {
+ // NOTE: SSCI simply ignored invalid input values, but
+ // we at least give a warning so games can be investigated
+ // for script bugs
+ if (color < _remapStartColor || color > _remapEndColor) {
+ warning("GfxRemap32::remapToPercentGray: %d out of remap range", color);
+ return;
+ }
+
+ const uint8 index = _remapEndColor - color;
+ SingleRemap &singleRemap = _remaps[index];
+
+ if (singleRemap._type == kRemapNone) {
+ ++_numActiveRemaps;
+ singleRemap.reset();
+ }
+
+ singleRemap._percent = percent;
+ singleRemap._gray = gray;
+ singleRemap._type = kRemapToPercentGray;
+ _needsUpdate = true;
+}
+
+void GfxRemap32::blockRange(const uint8 from, const int16 count) {
+ _blockedRangeStart = from;
+ _blockedRangeCount = count;
+}
+
+bool GfxRemap32::remapAllTables(const bool paletteUpdated) {
+ if (!_needsUpdate && !paletteUpdated) {
+ return false;
+ }
+
+ bool updated = false;
+
+ for (SingleRemapsList::iterator it = _remaps.begin(); it != _remaps.end(); ++it) {
+ if (it->_type != kRemapNone) {
+ updated |= it->update();
+ }
+ }
+
+ _needsUpdate = false;
+ return updated;
+}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/remap32.h b/engines/sci/graphics/remap32.h
new file mode 100644
index 0000000000..5f629d733e
--- /dev/null
+++ b/engines/sci/graphics/remap32.h
@@ -0,0 +1,400 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_REMAP32_H
+#define SCI_GRAPHICS_REMAP32_H
+
+#include "common/algorithm.h"
+#include "common/array.h"
+#include "common/scummsys.h"
+#include "sci/graphics/helpers.h"
+
+namespace Sci {
+class GfxPalette32;
+
+enum RemapType {
+ kRemapNone = 0,
+ kRemapByRange = 1,
+ kRemapByPercent = 2,
+ kRemapToGray = 3,
+ kRemapToPercentGray = 4
+};
+
+#pragma mark -
+#pragma mark SingleRemap
+
+/**
+ * SingleRemap objects each manage one remapping operation.
+ */
+class SingleRemap {
+public:
+ SingleRemap() : _type(kRemapNone) {}
+
+ /**
+ * The type of remap.
+ */
+ RemapType _type;
+
+ /**
+ * The first color that should be shifted by a range
+ * remap.
+ */
+ uint8 _from;
+
+ /**
+ * The last color that should be shifted a range remap.
+ */
+ uint8 _to;
+
+ /**
+ * The direction and amount that the colors should be
+ * shifted in a range remap.
+ */
+ int16 _delta;
+
+ /**
+ * The difference in brightness that should be
+ * applied by a brightness (percent) remap.
+ *
+ * This value may be be greater than 100, in
+ * which case the color will be oversaturated.
+ */
+ int16 _percent;
+
+ /**
+ * The amount of desaturation that should be
+ * applied by a saturation (gray) remap, where
+ * 0 is full saturation and 100 is full
+ * desaturation.
+ */
+ uint8 _gray;
+
+ /**
+ * The final array used by CelObj renderers to composite
+ * remapped pixels to the screen buffer.
+ *
+ * Here is how it works:
+ *
+ * The source bitmap being rendered will have pixels
+ * within the remap range (236-245 or 236-254), and the
+ * target buffer will have colors in the non-remapped
+ * range (0-235).
+ *
+ * To arrive at the correct color, first the source
+ * pixel is used to look up the correct SingleRemap for
+ * that pixel. Then, the final composited color is
+ * looked up in this array using the target's pixel
+ * color. In other words,
+ * `target = _remaps[remapEndColor - source].remapColors[target]`.
+ */
+ uint8 _remapColors[236];
+
+ /**
+ * Resets this SingleRemap's color information to
+ * default values.
+ */
+ void reset();
+
+ /**
+ * Recalculates and reapplies remap colors to the
+ * `_remapColors` array.
+ */
+ bool update();
+
+private:
+ /**
+ * The previous brightness value. Used to
+ * determine whether or not targetColors needs
+ * to be updated.
+ */
+ int16 _lastPercent;
+
+ /**
+ * The previous saturation value. Used to
+ * determine whether or not targetColors needs
+ * to be updated.
+ */
+ uint8 _lastGray;
+
+ /**
+ * The colors from the current GfxPalette32 palette
+ * before this SingleRemap is applied.
+ */
+ Color _originalColors[236];
+
+ /**
+ * Map of colors that changed in `_originalColors`
+ * when this SingleRemap was updated. This map is
+ * transient and gets reset to `false` after the
+ * SingleRemap finishes updating.
+ */
+ bool _originalColorsChanged[236];
+
+ /**
+ * The ideal target RGB color values for each generated
+ * remap color.
+ */
+ Color _idealColors[236];
+
+ /**
+ * Map of colors that changed in `_idealColors` when
+ * this SingleRemap was updated. This map is transient
+ * and gets reset to `false` after the SingleRemap
+ * finishes applying.
+ */
+ bool _idealColorsChanged[236];
+
+ /**
+ * When applying a SingleRemap, finding an appropriate
+ * color in the palette is the responsibility of a
+ * distance function. Once a match is found, the
+ * distance of that match is stored here so that the
+ * next time the SingleRemap is applied, it can check
+ * the distance from the previous application and avoid
+ * triggering an expensive redraw of the entire screen
+ * if the new palette value only changed slightly.
+ */
+ int _matchDistances[236];
+
+ /**
+ * Computes the final target values for a range remap
+ * and applies them directly to the `_remaps` map.
+ *
+ * @note Was ByRange in SSCI.
+ */
+ bool updateRange();
+
+ /**
+ * Computes the intermediate target values for a
+ * brightness remap and applies them indirectly via
+ * the `apply` method.
+ *
+ * @note Was ByPercent in SSCI.
+ */
+ bool updateBrightness();
+
+ /**
+ * Computes the intermediate target values for a
+ * saturation remap and applies them indirectly via
+ * the `apply` method.
+ *
+ * @note Was ToGray in SSCI.
+ */
+ bool updateSaturation();
+
+ /**
+ * Computes the intermediate target values for a
+ * saturation + brightness bitmap and applies them
+ * indirectly via the `apply` method.
+ *
+ * @note Was ToPercentGray in SSCI.
+ */
+ bool updateSaturationAndBrightness();
+
+ /**
+ * Computes and applies the final values to the
+ * `_remaps` map.
+ *
+ * @note In SSCI, a boolean array of changed values
+ * was passed into this method, but this was done by
+ * creating arrays on the stack in the caller. Instead
+ * of doing this, we simply add another member property
+ * `_idealColorsChanged` and use that instead.
+ */
+ bool apply();
+
+ /**
+ * Calculates the square distance of two colors.
+ *
+ * @note In SSCI this method is Rgb24::Dist, but it is
+ * only used by SingleRemap.
+ */
+ int colorDistance(const Color &a, const Color &b) const;
+
+ /**
+ * Finds the closest index in the next palette matching
+ * the given RGB color. Returns -1 if no match can be
+ * found that is closer than `minimumDistance`.
+ *
+ * @note In SSCI, this method is SOLPalette::Match, but
+ * this particular signature is only used by
+ * SingleRemap.
+ */
+ int16 matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const;
+};
+
+#pragma mark -
+#pragma mark GfxRemap32
+
+/**
+ * This class provides color remapping support for SCI32
+ * games.
+ */
+class GfxRemap32 : public Common::Serializable {
+public:
+ GfxRemap32();
+
+ void saveLoadWithSerializer(Common::Serializer &s);
+
+ inline uint8 getRemapCount() const { return _numActiveRemaps; }
+ inline uint8 getStartColor() const { return _remapStartColor; }
+ inline uint8 getEndColor() const { return _remapEndColor; }
+ inline uint8 getBlockedRangeStart() const { return _blockedRangeStart; }
+ inline int16 getBlockedRangeCount() const { return _blockedRangeCount; }
+
+ /**
+ * Turns off remapping of the given color. If `color` is
+ * 0, all remaps are turned off.
+ */
+ void remapOff(const uint8 color);
+
+ /**
+ * Turns off all color remaps.
+ */
+ void remapAllOff();
+
+ /**
+ * Configures a SingleRemap for the remap color `color`.
+ * The SingleRemap will shift palette colors between
+ * `from` and `to` (inclusive) by `delta` palette
+ * entries when the remap is applied.
+ */
+ void remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the brightness of remapped colors by
+ * `percent`.
+ */
+ void remapByPercent(const uint8 color, const int16 percent);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the saturation of remapped colors by
+ * `gray`.
+ */
+ void remapToGray(const uint8 color, const int8 gray);
+
+ /**
+ * Configures a SingleRemap for the remap color `color`
+ * to modify the brightness of remapped colors by
+ * `percent`, and saturation of remapped colors by
+ * `gray`.
+ */
+ void remapToPercentGray(const uint8 color, const int16 gray, const int16 percent);
+
+ /**
+ * Prevents GfxRemap32 from using the given range of
+ * palette entries as potential remap targets.
+ *
+ * @NOTE Was DontMapToRange in SSCI.
+ */
+ void blockRange(const uint8 from, const int16 count);
+
+ /**
+ * Determines whether or not the given color has an
+ * active remapper. If it does not, it is treated as a
+ * skip color and the pixel is not drawn.
+ *
+ * @note SSCI uses a boolean array to decide whether a
+ * a pixel is remapped, but it is possible to get the
+ * same information from `_remaps`, as this function
+ * does.
+ * Presumably, the separate array was created for
+ * performance reasons, since this is called a lot in
+ * the most critical section of the renderer.
+ */
+ inline bool remapEnabled(uint8 color) const {
+ const uint8 index = _remapEndColor - color;
+ assert(index < _remaps.size());
+ return (_remaps[index]._type != kRemapNone);
+ }
+
+ /**
+ * Calculates the correct color for a target by looking
+ * up the target color in the SingleRemap that controls
+ * the given sourceColor. If there is no remap for the
+ * given color, it will be treated as a skip color.
+ */
+ inline uint8 remapColor(const uint8 sourceColor, const uint8 targetColor) const {
+ const uint8 index = _remapEndColor - sourceColor;
+ assert(index < _remaps.size());
+ const SingleRemap &singleRemap = _remaps[index];
+ assert(singleRemap._type != kRemapNone);
+ return singleRemap._remapColors[targetColor];
+ }
+
+ /**
+ * Updates all active remaps in response to a palette
+ * change or a remap settings change.
+ *
+ * `paletteChanged` is true if the next palette in
+ * GfxPalette32 has been previously modified by other
+ * palette operations.
+ */
+ bool remapAllTables(const bool paletteUpdated);
+
+private:
+ typedef Common::Array<SingleRemap> SingleRemapsList;
+
+ /**
+ * The first index of the remap area in the system
+ * palette.
+ */
+ const uint8 _remapStartColor;
+
+ /**
+ * The last index of the remap area in the system
+ * palette.
+ */
+ uint8 _remapEndColor;
+
+ /**
+ * The number of currently active remaps.
+ */
+ uint8 _numActiveRemaps;
+
+ /**
+ * The list of SingleRemaps.
+ */
+ SingleRemapsList _remaps;
+
+ /**
+ * If true, indicates that one or more SingleRemaps were
+ * reconfigured and all remaps need to be recalculated.
+ */
+ bool _needsUpdate;
+
+ /**
+ * The first color that is blocked from being used as a
+ * remap target color.
+ */
+ uint8 _blockedRangeStart;
+
+ /**
+ * The size of the range of blocked colors. If zero,
+ * all colors are potential targets for remapping.
+ */
+ int16 _blockedRangeCount;
+};
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
index fba0fa0422..c1644a5ea3 100644
--- a/engines/sci/graphics/screen_item32.cpp
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -273,7 +273,9 @@ void ScreenItem::calcRects(const Plane &plane) {
// Cel may use a coordinate system that is not the same size as the
// script coordinate system (usually this means high-resolution
// pictures with low-resolution scripts)
- if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) {
+ if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) {
+ // high resolution coordinates
+
if (_useInsetRect) {
const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);
const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight);
@@ -345,6 +347,8 @@ void ScreenItem::calcRects(const Plane &plane) {
_ratioX = scaleX * celToScreenX;
_ratioY = scaleY * celToScreenY;
} else {
+ // low resolution coordinates
+
int displaceX = celObj._displace.x;
if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {
displaceX = celObj._width - celObj._displace.x - 1;
@@ -515,6 +519,25 @@ void ScreenItem::update(const reg_t object) {
_deleted = 0;
}
+void ScreenItem::update() {
+ Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
+ if (plane == nullptr) {
+ error("ScreenItem::update: Invalid plane %04x:%04x", PRINT_REG(_plane));
+ }
+
+ if (plane->_screenItemList.findByObject(_object) == nullptr) {
+ error("ScreenItem::update: %04x:%04x not in plane %04x:%04x", PRINT_REG(_object), PRINT_REG(_plane));
+ }
+
+ if (!_created) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+ _deleted = 0;
+
+ delete _celObj;
+ _celObj = nullptr;
+}
+
// TODO: This code is quite similar to calcRects, so try to deduplicate
// if possible
Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
@@ -563,7 +586,9 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
displaceX = celObj._width - displaceX - 1;
}
- if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) {
+ if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) {
+ // high resolution coordinates
+
if (_useInsetRect) {
Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);
Ratio scriptToCelY(celObj._scaledHeight, scriptHeight);
@@ -597,6 +622,8 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
mulinc(nsRect, celToScriptX, celToScriptY);
nsRect.translate(_position.x - displaceX, _position.y - displaceY);
} else {
+ // low resolution coordinates
+
if (!scaleX.isOne() || !scaleY.isOne()) {
mulinc(nsRect, scaleX, scaleY);
// TODO: This was in the original code, baked into the
@@ -630,23 +657,43 @@ ScreenItem *ScreenItemList::findByObject(const reg_t object) const {
return *screenItemIt;
}
void ScreenItemList::sort() {
- // TODO: SCI engine used _unsorted as an array of indexes into the
- // list itself and then performed the same swap operations on the
- // _unsorted array as the _storage array during sorting, but the
- // only reason to do this would be if some of the pointers in the
- // list were replaced so the pointer values themselves couldn’t
- // simply be recorded and then restored later. It is not yet
- // verified whether this simplification of the sort/unsort is
- // safe.
+ if (size() < 2) {
+ return;
+ }
+
for (size_type i = 0; i < size(); ++i) {
- _unsorted[i] = (*this)[i];
+ _unsorted[i] = i;
}
- Common::sort(begin(), end(), sortHelper);
+ for (size_type i = size() - 1; i > 0; --i) {
+ bool swap = false;
+
+ for (size_type j = 0; j < i; ++j) {
+ value_type &a = operator[](j);
+ value_type &b = operator[](j + 1);
+
+ if (a == nullptr || *a > *b) {
+ SWAP(a, b);
+ SWAP(_unsorted[j], _unsorted[j + 1]);
+ swap = true;
+ }
+ }
+
+ if (!swap) {
+ break;
+ }
+ }
}
void ScreenItemList::unsort() {
+ if (size() < 2) {
+ return;
+ }
+
for (size_type i = 0; i < size(); ++i) {
- (*this)[i] = _unsorted[i];
+ while (_unsorted[i] != i) {
+ SWAP(operator[](_unsorted[i]), operator[](i));
+ SWAP(_unsorted[_unsorted[i]], _unsorted[i]);
+ }
}
}
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
index 91f54b48e9..2e44e418ce 100644
--- a/engines/sci/graphics/screen_item32.h
+++ b/engines/sci/graphics/screen_item32.h
@@ -236,6 +236,24 @@ public:
return false;
}
+ inline bool operator>(const ScreenItem &other) const {
+ if (_priority > other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ if (_position.y + _z > other._position.y + other._z) {
+ return true;
+ }
+
+ if (_position.y + _z == other._position.y + other._z) {
+ return _object > other._object;
+ }
+ }
+
+ return false;
+ }
+
/**
* Calculates the dimensions and scaling parameters for
* the screen item, using the given plane as the parent
@@ -261,6 +279,12 @@ public:
void update(const reg_t object);
/**
+ * Updates the properties of the screen item for one not belonging
+ * to a VM object. Originally GraphicsMgr::UpdateScreenItem.
+ */
+ void update();
+
+ /**
* Gets the "now seen" rect for the screen item, which
* represents the current size and position of the
* screen item on the screen in script coordinates.
@@ -273,12 +297,10 @@ public:
typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;
class ScreenItemList : public ScreenItemListBase {
- inline static bool sortHelper(const ScreenItem *a, const ScreenItem *b) {
- return *a < *b;
- }
-public:
- ScreenItem *_unsorted[250];
+private:
+ size_type _unsorted[250];
+public:
ScreenItem *findByObject(const reg_t object) const;
void sort();
void unsort();
diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp
index d1c223d5d5..277e6e93d0 100644
--- a/engines/sci/graphics/text32.cpp
+++ b/engines/sci/graphics/text32.cpp
@@ -39,18 +39,24 @@
namespace Sci {
int16 GfxText32::_defaultFontId = 0;
+int16 GfxText32::_scaledWidth = 0;
+int16 GfxText32::_scaledHeight = 0;
GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) :
_segMan(segMan),
_cache(fonts),
- _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
- _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
// Not a typo, the original engine did not initialise height, only width
_width(0),
_text(""),
_bitmap(NULL_REG) {
_fontId = _defaultFontId;
_font = _cache->getFont(_defaultFontId);
+
+ if (_scaledWidth == 0) {
+ // initialize the statics
+ _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ }
}
reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling) {
@@ -115,7 +121,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- int borderSize = 1;
mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight));
CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo);
@@ -132,7 +137,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);
_bitmap = bitmap.getObject();
- Buffer buffer(_width, _height, bitmap.getPixels());
// NOTE: The engine filled the bitmap pixels with 11 here, which is silly
// because then it just erased the bitmap using the skip color. So we don't
@@ -142,7 +146,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
erase(bitmapRect, false);
_backColor = backColor;
- view.draw(buffer, bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight));
+ view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight));
if (_backColor != skipColor && _foreColor != skipColor) {
erase(_textRect, false);
@@ -153,7 +157,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &
error("TODO: Implement transparent text");
} else {
if (borderColor != -1) {
- drawFrame(bitmapRect, borderSize, _borderColor, false);
+ drawFrame(bitmapRect, 1, _borderColor, false);
}
drawTextBox();
@@ -231,8 +235,11 @@ void GfxText32::drawTextBox() {
int16 textRectWidth = _textRect.width();
_drawPosition.y = _textRect.top;
uint charIndex = 0;
- if (getLongest(&charIndex, textRectWidth) == 0) {
- error("DrawTextBox GetLongest=0");
+
+ if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+ if (getLongest(&charIndex, textRectWidth) == 0) {
+ error("DrawTextBox GetLongest=0");
+ }
}
charIndex = 0;
@@ -311,6 +318,10 @@ void GfxText32::drawText(const uint index, uint length) {
++text;
--length;
}
+ if (length > 0) {
+ ++text;
+ --length;
+ }
} else {
drawChar(currentChar);
}
@@ -498,7 +509,7 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {
--length;
fontId = fontId * 10 + currentChar - '0';
- } while (length > 0 && currentChar >= '0' && currentChar <= '9');
+ } while (length > 0 && *text >= '0' && *text <= '9');
if (length > 0) {
font = _cache->getFont(fontId);
@@ -506,7 +517,11 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {
}
// Forward through any more unknown control character data
- while (length > 0 && currentChar != '|') {
+ while (length > 0 && *text != '|') {
+ ++text;
+ --length;
+ }
+ if (length > 0) {
++text;
--length;
}
@@ -514,8 +529,10 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {
width += font->getCharWidth(currentChar);
}
- currentChar = *text++;
- --length;
+ if (length > 0) {
+ currentChar = *text++;
+ --length;
+ }
}
return width;
@@ -573,11 +590,16 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth,
}
} else {
result.right = getTextWidth(0, 10000);
- // NOTE: In the original engine code, the bottom was not decremented
- // by 1, which means that the rect was actually a pixel taller than
- // the height of the font. This was not the case in the other branch,
- // which decremented the bottom by 1 at the end of the loop.
- result.bottom = _font->getHeight() + 1;
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ result.bottom = 0;
+ } else {
+ // NOTE: In the original engine code, the bottom was not decremented
+ // by 1, which means that the rect was actually a pixel taller than
+ // the height of the font. This was not the case in the other branch,
+ // which decremented the bottom by 1 at the end of the loop.
+ result.bottom = _font->getHeight() + 1;
+ }
}
if (doScaling) {
@@ -593,14 +615,8 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth,
void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {
Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
- byte *bitmap = _segMan->getHunkPointer(_bitmap);
- byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28);
-
- // NOTE: There is an extra optimisation within the SCI code to
- // do a single memset if the scaledRect is the same size as
- // the bitmap, not implemented here.
- Buffer buffer(_width, _height, pixels);
- buffer.fillRect(targetRect, _backColor);
+ BitmapResource bitmap(_bitmap);
+ bitmap.getBuffer().fillRect(targetRect, _backColor);
}
int16 GfxText32::getStringWidth(const Common::String &text) {
@@ -635,5 +651,69 @@ int16 GfxText32::getTextCount(const Common::String &text, const uint index, cons
return getTextCount(text, index, textRect, doScaling);
}
+void GfxText32::scrollLine(const Common::String &lineText, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir) {
+ BitmapResource bmr(_bitmap);
+ byte *pixels = bmr.getPixels();
+
+ int h = _font->getHeight();
+
+ if (dir == kScrollUp) {
+ // Scroll existing text down
+ for (int i = 0; i < (numLines - 1) * h; ++i) {
+ int y = _textRect.top + numLines * h - i - 1;
+ memcpy(pixels + y * _width + _textRect.left,
+ pixels + (y - h) * _width + _textRect.left,
+ _textRect.width());
+ }
+ } else {
+ // Scroll existing text up
+ for (int i = 0; i < (numLines - 1) * h; ++i) {
+ int y = _textRect.top + i;
+ memcpy(pixels + y * _width + _textRect.left,
+ pixels + (y + h) * _width + _textRect.left,
+ _textRect.width());
+ }
+ }
+
+ Common::Rect lineRect = _textRect;
+
+ if (dir == kScrollUp) {
+ lineRect.bottom = lineRect.top + h;
+ } else {
+ // It is unclear to me what the purpose of this bottom++ is.
+ // It does not seem to be the usual inc/exc issue.
+ lineRect.top += (numLines - 1) * h;
+ lineRect.bottom++;
+ }
+
+ erase(lineRect, false);
+
+ _drawPosition.x = _textRect.left;
+ _drawPosition.y = _textRect.top;
+ if (dir == kScrollDown) {
+ _drawPosition.y += (numLines - 1) * h;
+ }
+
+ _foreColor = color;
+ _alignment = align;
+ //int fc = _foreColor;
+
+ setFont(fontId);
+
+ _text = lineText;
+ int16 textWidth = getTextWidth(0, lineText.size());
+
+ if (_alignment == kTextAlignCenter) {
+ _drawPosition.x += (_textRect.width() - textWidth) / 2;
+ } else if (_alignment == kTextAlignRight) {
+ _drawPosition.x += _textRect.width() - textWidth;
+ }
+
+ //_foreColor = fc;
+ //setFont(fontId);
+
+ drawText(0, lineText.size());
+}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h
index 20adb3d7c7..a61760dd87 100644
--- a/engines/sci/graphics/text32.h
+++ b/engines/sci/graphics/text32.h
@@ -23,15 +23,23 @@
#ifndef SCI_GRAPHICS_TEXT32_H
#define SCI_GRAPHICS_TEXT32_H
+#include "sci/engine/state.h"
#include "sci/graphics/celobj32.h"
#include "sci/graphics/frameout.h"
+#include "sci/graphics/helpers.h"
namespace Sci {
enum TextAlign {
- kTextAlignLeft = 0,
- kTextAlignCenter = 1,
- kTextAlignRight = 2
+ kTextAlignDefault = -1,
+ kTextAlignLeft = 0,
+ kTextAlignCenter = 1,
+ kTextAlignRight = 2
+};
+
+enum ScrollDirection {
+ kScrollUp,
+ kScrollDown
};
enum BitmapFlags {
@@ -53,6 +61,7 @@ inline void set##property(uint##size value) {\
class BitmapResource {
byte *_bitmap;
reg_t _object;
+ Buffer _buffer;
/**
* Gets the size of the bitmap header for the current
@@ -96,6 +105,8 @@ public:
if (_bitmap == nullptr || getUncompressedDataOffset() != getBitmapHeaderSize()) {
error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap));
}
+
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
}
/**
@@ -103,7 +114,6 @@ public:
* segment manager.
*/
inline BitmapResource(SegManager *segMan, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool remap) {
-
_object = segMan->allocateHunkEntry("Bitmap()", getBitmapSize(width, height));
_bitmap = segMan->getHunkPointer(_object);
@@ -124,12 +134,18 @@ public:
setControlOffset(0);
setScaledWidth(scaledWidth);
setScaledHeight(scaledHeight);
+
+ _buffer = Buffer(getWidth(), getHeight(), getPixels());
}
- reg_t getObject() const {
+ inline reg_t getObject() const {
return _object;
}
+ inline Buffer &getBuffer() {
+ return _buffer;
+ }
+
BITMAP_PROPERTY(16, Width, 0);
BITMAP_PROPERTY(16, Height, 2);
@@ -173,7 +189,7 @@ public:
return READ_SCI11ENDIAN_UINT32(_bitmap + 20);
}
- void setHunkPaletteOffset(uint32 hunkPaletteOffset) {
+ inline void setHunkPaletteOffset(uint32 hunkPaletteOffset) {
if (hunkPaletteOffset) {
hunkPaletteOffset += getBitmapHeaderSize();
}
@@ -351,15 +367,15 @@ public:
/**
* The size of the x-dimension of the coordinate system
- * used by the text renderer.
+ * used by the text renderer. Static since it was global in SSCI.
*/
- int16 _scaledWidth;
+ static int16 _scaledWidth;
/**
* The size of the y-dimension of the coordinate system
- * used by the text renderer.
+ * used by the text renderer. Static since it was global in SSCI.
*/
- int16 _scaledHeight;
+ static int16 _scaledHeight;
/**
* The currently active font resource used to write text
@@ -456,6 +472,13 @@ public:
* `textRect` using the given font.
*/
int16 getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling);
+
+ /**
+ * Scroll up/down one line. `numLines` is the number of the lines in the
+ * textarea, and `textLine` contains the text to draw as the newly
+ * visible line. Originally FontMgr::DrawOneLine and FontMgr::UpOneLine.
+ */
+ void scrollLine(const Common::String &textLine, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir);
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h
index 91590208c1..96b48c0477 100644
--- a/engines/sci/graphics/view.h
+++ b/engines/sci/graphics/view.h
@@ -55,6 +55,7 @@ struct LoopInfo {
class GfxScreen;
class GfxPalette;
+class Resource;
/**
* View class, handles loading of view resources and drawing contained cels to screen
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index a02147e4d0..5d54e2a52c 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -51,7 +51,6 @@ MODULE_OBJS := \
graphics/fontsjis.o \
graphics/maciconbar.o \
graphics/menu.o \
- graphics/paint.o \
graphics/paint16.o \
graphics/palette.o \
graphics/picture.o \
@@ -69,6 +68,7 @@ MODULE_OBJS := \
sound/midiparser_sci.o \
sound/music.o \
sound/soundcmd.o \
+ sound/sync.o \
sound/drivers/adlib.o \
sound/drivers/amigamac.o \
sound/drivers/cms.o \
@@ -88,8 +88,11 @@ MODULE_OBJS += \
graphics/paint32.o \
graphics/plane32.o \
graphics/palette32.o \
+ graphics/remap32.o \
graphics/screen_item32.o \
graphics/text32.o \
+ sound/audio32.o \
+ sound/decoders/sol.o \
video/robot_decoder.o
endif
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 6a5af1a6d6..3e50fc1082 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -26,6 +26,9 @@
#include "common/fs.h"
#include "common/macresman.h"
#include "common/textconsole.h"
+#ifdef ENABLE_SCI32
+#include "common/memstream.h"
+#endif
#include "sci/resource.h"
#include "sci/resource_intern.h"
@@ -221,6 +224,12 @@ void Resource::writeToStream(Common::WriteStream *stream) const {
stream->write(data, size);
}
+#ifdef ENABLE_SCI32
+Common::SeekableReadStream *Resource::makeStream() const {
+ return new Common::MemoryReadStream(data, size, DisposeAfterUse::NO);
+}
+#endif
+
uint32 Resource::getAudioCompressionType() const {
return _source->getAudioCompressionType();
}
@@ -229,7 +238,6 @@ uint32 AudioVolumeResourceSource::getAudioCompressionType() const {
return _audioCompressionType;
}
-
ResourceSource::ResourceSource(ResSourceType type, const Common::String &name, int volNum, const Common::FSNode *resFile)
: _sourceType(type), _name(name), _volumeNumber(volNum), _resourceFile(resFile) {
_scanned = false;
@@ -1043,7 +1051,13 @@ Resource *ResourceManager::findResource(ResourceId id, bool lock) {
if (retval->_status == kResStatusNoMalloc)
loadResource(retval);
else if (retval->_status == kResStatusEnqueued)
+ // The resource is removed from its current position
+ // in the LRU list because it has been requested
+ // again. Below, it will either be locked, or it
+ // will be added back to the LRU list at the 'most
+ // recent' position.
removeFromLRU(retval);
+
// Unless an error occurred, the resource is now either
// locked or allocated, but never queued or freed.
@@ -1352,6 +1366,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
Common::File *file = new Common::File();
if (!file->open(source->getLocationName())) {
warning("ResourceManager::processPatch(): failed to open %s", source->getLocationName().c_str());
+ delete source;
return;
}
fileStream = file;
@@ -1360,6 +1375,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
int fsize = fileStream->size();
if (fsize < 3) {
debug("Patching %s failed - file too small", source->getLocationName().c_str());
+ delete source;
return;
}
@@ -1370,6 +1386,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
if (patchType != checkForType) {
debug("Patching %s failed - resource type mismatch", source->getLocationName().c_str());
+ delete source;
return;
}
@@ -1394,6 +1411,7 @@ void ResourceManager::processPatch(ResourceSource *source, ResourceType resource
if (patchDataOffset + 2 >= fsize) {
debug("Patching %s failed - patch starting at offset %d can't be in file of size %d",
source->getLocationName().c_str(), patchDataOffset + 2, fsize);
+ delete source;
return;
}
@@ -1441,13 +1459,18 @@ void ResourceManager::readResourcePatchesBase36() {
files.clear();
// audio36 resources start with a @, A, or B
- // sync36 resources start with a #
+ // sync36 resources start with a #, S, or T
if (i == kResourceTypeAudio36) {
SearchMan.listMatchingMembers(files, "@???????.???");
SearchMan.listMatchingMembers(files, "A???????.???");
SearchMan.listMatchingMembers(files, "B???????.???");
- } else
+ } else {
SearchMan.listMatchingMembers(files, "#???????.???");
+#ifdef ENABLE_SCI32
+ SearchMan.listMatchingMembers(files, "S???????.???");
+ SearchMan.listMatchingMembers(files, "T???????.???");
+#endif
+ }
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
name = (*x)->getName();
diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index ef474d97c2..f70bf48bd4 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -84,7 +84,10 @@ enum ResourceType {
kResourceTypePatch,
kResourceTypeBitmap,
kResourceTypePalette,
- kResourceTypeCdAudio,
+ kResourceTypeCdAudio = 12,
+#ifdef ENABLE_SCI32
+ kResourceTypeWave = 12,
+#endif
kResourceTypeAudio,
kResourceTypeSync,
kResourceTypeMessage,
@@ -212,6 +215,10 @@ public:
return (_type == other._type) && (_number == other._number) && (_tuple == other._tuple);
}
+ bool operator!=(const ResourceId &other) const {
+ return !operator==(other);
+ }
+
bool operator<(const ResourceId &other) const {
return (_type < other._type) || ((_type == other._type) && (_number < other._number))
|| ((_type == other._type) && (_number == other._number) && (_tuple < other._tuple));
@@ -259,6 +266,10 @@ public:
*/
void writeToStream(Common::WriteStream *stream) const;
+#ifdef ENABLE_SCI32
+ Common::SeekableReadStream *makeStream() const;
+#endif
+
const Common::String &getResourceLocation() const;
// FIXME: This audio specific method is a hack. After all, why should a
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 5717a09121..5ab443a16d 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -25,7 +25,7 @@
#include "common/archive.h"
#include "common/file.h"
#include "common/textconsole.h"
-
+#include "common/memstream.h"
#include "sci/resource.h"
#include "sci/resource_intern.h"
#include "sci/util.h"
@@ -869,6 +869,7 @@ void WaveResourceSource::loadResource(ResourceManager *resMan, Resource *res) {
if (!fileStream)
return;
+ assert(fileStream->size() == -1 || res->_fileOffset < fileStream->size());
fileStream->seek(res->_fileOffset, SEEK_SET);
res->loadFromWaveFile(fileStream);
if (_resourceFile)
@@ -922,6 +923,7 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *
break;
}
} else {
+ assert(fileStream->size() == -1 || res->_fileOffset < fileStream->size());
// original file, directly seek to given offset and get SCI1/SCI1.1 audio resource
fileStream->seek(res->_fileOffset, SEEK_SET);
}
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index e14d12b918..41fa144b06 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -43,12 +43,12 @@
#include "sci/sound/audio.h"
#include "sci/sound/music.h"
+#include "sci/sound/sync.h"
#include "sci/sound/soundcmd.h"
#include "sci/graphics/animate.h"
#include "sci/graphics/cache.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/controls16.h"
-#include "sci/graphics/controls32.h"
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/cursor.h"
#include "sci/graphics/maciconbar.h"
@@ -64,9 +64,12 @@
#include "sci/graphics/transitions.h"
#ifdef ENABLE_SCI32
+#include "sci/graphics/controls32.h"
+#include "sci/graphics/frameout.h"
#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
#include "sci/graphics/text32.h"
-#include "sci/graphics/frameout.h"
+#include "sci/sound/audio32.h"
#include "sci/video/robot_decoder.h"
#endif
@@ -86,6 +89,10 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
_gfxMacIconBar = 0;
_audio = 0;
+ _sync = nullptr;
+#ifdef ENABLE_SCI32
+ _audio32 = nullptr;
+#endif
_features = 0;
_resMan = 0;
_gamestate = 0;
@@ -161,16 +168,18 @@ SciEngine::~SciEngine() {
// and will be destroyed when _gfxPalette16 is
// destroyed
delete _gfxControls32;
+ delete _gfxPaint32;
delete _gfxText32;
delete _robotDecoder;
delete _gfxFrameout;
delete _gfxRemap32;
+ delete _audio32;
#endif
delete _gfxMenu;
delete _gfxControls16;
delete _gfxText16;
delete _gfxAnimate;
- delete _gfxPaint;
+ delete _gfxPaint16;
delete _gfxTransitions;
delete _gfxCompare;
delete _gfxCoordAdjuster;
@@ -182,6 +191,7 @@ SciEngine::~SciEngine() {
delete _gfxScreen;
delete _audio;
+ delete _sync;
delete _soundCmd;
delete _kernel;
delete _vocabulary;
@@ -266,7 +276,14 @@ Common::Error SciEngine::run() {
// Also, XMAS1990 apparently had a parser too. Refer to http://forums.scummvm.org/viewtopic.php?t=9135
if (getGameId() == GID_CHRISTMAS1990)
_vocabulary = new Vocabulary(_resMan, false);
- _audio = new AudioPlayer(_resMan);
+
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
+ _audio32 = new Audio32(_resMan);
+ } else
+#endif
+ _audio = new AudioPlayer(_resMan);
+ _sync = new Sync(_resMan, segMan);
_gamestate = new EngineState(segMan);
_eventMan = new EventManager(_resMan->detectFontExtended());
@@ -660,7 +677,6 @@ void SciEngine::initGraphics() {
_gfxCursor = 0;
_gfxMacIconBar = 0;
_gfxMenu = 0;
- _gfxPaint = 0;
_gfxPaint16 = 0;
_gfxPalette16 = 0;
_gfxRemap16 = 0;
@@ -684,7 +700,7 @@ void SciEngine::initGraphics() {
if (getSciVersion() >= SCI_VERSION_2) {
_gfxPalette32 = new GfxPalette32(_resMan, _gfxScreen);
_gfxPalette16 = _gfxPalette32;
- _gfxRemap32 = new GfxRemap32(_gfxPalette32);
+ _gfxRemap32 = new GfxRemap32();
} else {
#endif
_gfxPalette16 = new GfxPalette(_resMan, _gfxScreen);
@@ -703,10 +719,9 @@ void SciEngine::initGraphics() {
_gfxCoordAdjuster = new GfxCoordAdjuster32(_gamestate->_segMan);
_gfxCursor->init(_gfxCoordAdjuster, _eventMan);
_gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster);
- _gfxPaint32 = new GfxPaint32(_resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32);
- _gfxPaint = _gfxPaint32;
+ _gfxPaint32 = new GfxPaint32(_gamestate->_segMan);
_robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
- _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32);
+ _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32);
_gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache);
_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
_gfxFrameout->run();
@@ -719,7 +734,6 @@ void SciEngine::initGraphics() {
_gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster);
_gfxTransitions = new GfxTransitions(_gfxScreen, _gfxPalette16);
_gfxPaint16 = new GfxPaint16(_resMan, _gamestate->_segMan, _gfxCache, _gfxPorts, _gfxCoordAdjuster, _gfxScreen, _gfxPalette16, _gfxTransitions, _audio);
- _gfxPaint = _gfxPaint16;
_gfxAnimate = new GfxAnimate(_gamestate, _scriptPatcher, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette16, _gfxCursor, _gfxTransitions);
_gfxText16 = new GfxText16(_gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen);
_gfxControls16 = new GfxControls16(_gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen);
@@ -801,7 +815,10 @@ void SciEngine::runGame() {
void SciEngine::exitGame() {
if (_gamestate->abortScriptProcessing != kAbortLoadGame) {
_gamestate->_executionStack.clear();
- _audio->stopAllAudio();
+ if (_audio) {
+ _audio->stopAllAudio();
+ }
+ _sync->stop();
_soundCmd->clearPlayList();
}
@@ -897,6 +914,10 @@ Common::String SciEngine::unwrapFilename(const Common::String &name) const {
return name;
}
+const char *SciEngine::getGameObjectName() {
+ return _gamestate->_segMan->getObjectName(_gameObjectAddress);
+}
+
int SciEngine::inQfGImportRoom() const {
if (_gameId == GID_QFG2 && _gamestate->currentRoomNumber() == 805) {
// QFG2 character import screen
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index c49a516d01..956187ce69 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -56,6 +56,7 @@ class SoundCommandParser;
class EventManager;
class SegManager;
class ScriptPatcher;
+class Sync;
class GfxAnimate;
class GfxCache;
@@ -66,7 +67,6 @@ class GfxCoordAdjuster;
class GfxCursor;
class GfxMacIconBar;
class GfxMenu;
-class GfxPaint;
class GfxPaint16;
class GfxPaint32;
class GfxPalette;
@@ -82,6 +82,7 @@ class GfxTransitions;
#ifdef ENABLE_SCI32
class RobotDecoder;
class GfxFrameout;
+class Audio32;
#endif
// our engine debug levels
@@ -302,6 +303,8 @@ public:
/** Remove the 'TARGET-' prefix of the given filename, if present. */
Common::String unwrapFilename(const Common::String &name) const;
+ const char *getGameObjectName(); // Gets the name of the game object (should only be used for identifying fanmade games)
+
/**
* Checks if we are in a QfG import screen, where special handling
* of file-listings is performed.
@@ -356,7 +359,6 @@ public:
GfxPalette32 *_gfxPalette32; // Palette for 32-bit gfx
GfxRemap *_gfxRemap16; // Remapping for the QFG4 demo
GfxRemap32 *_gfxRemap32; // Remapping for 32-bit gfx
- GfxPaint *_gfxPaint;
GfxPaint16 *_gfxPaint16; // Painting in 16-bit gfx
GfxPaint32 *_gfxPaint32; // Painting in 32-bit gfx
GfxPorts *_gfxPorts; // Port managment for 16-bit gfx
@@ -367,11 +369,13 @@ public:
GfxMacIconBar *_gfxMacIconBar; // Mac Icon Bar manager
#ifdef ENABLE_SCI32
+ Audio32 *_audio32;
RobotDecoder *_robotDecoder;
GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx
#endif
AudioPlayer *_audio;
+ Sync *_sync;
SoundCommandParser *_soundCmd;
GameFeatures *_features;
diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp
index a74bfa245f..4fb9a58003 100644
--- a/engines/sci/sound/audio.cpp
+++ b/engines/sci/sound/audio.cpp
@@ -22,7 +22,6 @@
#include "sci/resource.h"
#include "sci/engine/kernel.h"
-#include "sci/engine/selector.h"
#include "sci/engine/seg_manager.h"
#include "sci/sound/audio.h"
@@ -45,7 +44,7 @@
namespace Sci {
AudioPlayer::AudioPlayer(ResourceManager *resMan) : _resMan(resMan), _audioRate(11025),
- _syncResource(NULL), _syncOffset(0), _audioCdStart(0), _initCD(false) {
+ _audioCdStart(0), _initCD(false) {
_mixer = g_system->getMixer();
_wPlayFlag = false;
@@ -56,7 +55,6 @@ AudioPlayer::~AudioPlayer() {
}
void AudioPlayer::stopAllAudio() {
- stopSoundSync();
stopAudio();
if (_audioCdStart > 0)
audioCdStop();
@@ -255,13 +253,7 @@ static void deDPCM16(byte *soundBuf, Common::SeekableReadStream &audioStream, ui
static void deDPCM8Nibble(byte *soundBuf, int32 &s, byte b) {
if (b & 8) {
-#ifdef ENABLE_SCI32
- // SCI2.1 reverses the order of the table values here
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
- s -= tableDPCM8[b & 7];
- else
-#endif
- s -= tableDPCM8[7 - (b & 7)];
+ s -= tableDPCM8[7 - (b & 7)];
} else
s += tableDPCM8[b & 7];
s = CLIP<int32>(s, 0, 255);
@@ -474,43 +466,6 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32
return NULL;
}
-void AudioPlayer::setSoundSync(ResourceId id, reg_t syncObjAddr, SegManager *segMan) {
- _syncResource = _resMan->findResource(id, 1);
- _syncOffset = 0;
-
- if (_syncResource) {
- writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), 0);
- } else {
- warning("setSoundSync: failed to find resource %s", id.toString().c_str());
- // Notify the scripts to stop sound sync
- writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), SIGNAL_OFFSET);
- }
-}
-
-void AudioPlayer::doSoundSync(reg_t syncObjAddr, SegManager *segMan) {
- if (_syncResource && (_syncOffset < _syncResource->size - 1)) {
- int16 syncCue = -1;
- int16 syncTime = (int16)READ_SCI11ENDIAN_UINT16(_syncResource->data + _syncOffset);
-
- _syncOffset += 2;
-
- if ((syncTime != -1) && (_syncOffset < _syncResource->size - 1)) {
- syncCue = (int16)READ_SCI11ENDIAN_UINT16(_syncResource->data + _syncOffset);
- _syncOffset += 2;
- }
-
- writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncTime), syncTime);
- writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), syncCue);
- }
-}
-
-void AudioPlayer::stopSoundSync() {
- if (_syncResource) {
- _resMan->unlockResource(_syncResource);
- _syncResource = NULL;
- }
-}
-
int AudioPlayer::audioCdPlay(int track, int start, int duration) {
if (!_initCD) {
// Initialize CD mode if we haven't already
diff --git a/engines/sci/sound/audio.h b/engines/sci/sound/audio.h
index 4a8b26567d..3d25dcaeef 100644
--- a/engines/sci/sound/audio.h
+++ b/engines/sci/sound/audio.h
@@ -46,12 +46,6 @@ enum AudioCommands {
kSciAudioCD = 10 /* Plays SCI1.1 CD audio */
};
-enum AudioSyncCommands {
- kSciAudioSyncStart = 0,
- kSciAudioSyncNext = 1,
- kSciAudioSyncStop = 2
-};
-
#define AUDIO_VOLUME_MAX 127
class Resource;
@@ -77,10 +71,6 @@ public:
void handleFanmadeSciAudio(reg_t sciAudioObject, SegManager *segMan);
- void setSoundSync(ResourceId id, reg_t syncObjAddr, SegManager *segMan);
- void doSoundSync(reg_t syncObjAddr, SegManager *segMan);
- void stopSoundSync();
-
int audioCdPlay(int track, int start, int duration);
void audioCdStop();
void audioCdUpdate();
@@ -93,8 +83,6 @@ private:
uint16 _audioRate;
Audio::SoundHandle _audioHandle;
Audio::Mixer *_mixer;
- Resource *_syncResource; /**< Used by kDoSync for speech syncing in CD talkie games */
- uint _syncOffset;
uint32 _audioCdStart;
bool _wPlayFlag;
bool _initCD;
diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
new file mode 100644
index 0000000000..ced88a3028
--- /dev/null
+++ b/engines/sci/sound/audio32.cpp
@@ -0,0 +1,997 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/sound/audio32.h"
+#include "audio/audiostream.h" // for SeekableAudioStream
+#include "audio/decoders/raw.h" // for makeRawStream, RawFlags::FLAG_16BITS
+#include "audio/decoders/wave.h" // for makeWAVStream
+#include "audio/rate.h" // for RateConverter, makeRateConverter
+#include "audio/timestamp.h" // for Timestamp
+#include "common/config-manager.h" // for ConfMan
+#include "common/endian.h" // for MKTAG
+#include "common/memstream.h" // for MemoryReadStream
+#include "common/str.h" // for String
+#include "common/stream.h" // for SeekableReadStream
+#include "common/system.h" // for OSystem, g_system
+#include "common/textconsole.h" // for warning
+#include "common/types.h" // for Flag::NO
+#include "engine.h" // for Engine, g_engine
+#include "sci/engine/vm_types.h" // for reg_t, make_reg, NULL_REG
+#include "sci/resource.h" // for ResourceId, ResourceType::kResour...
+#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion
+#include "sci/sound/decoders/sol.h" // for makeSOLStream
+
+namespace Sci {
+
+bool detectSolAudio(Common::SeekableReadStream &stream) {
+ const size_t initialPosition = stream.pos();
+
+// TODO: Resource manager for audio resources reads past the
+// header so even though this is the detection algorithm
+// in SSCI, ScummVM can't use it
+#if 0
+ byte header[6];
+ if (stream.read(header, sizeof(header)) != sizeof(header)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+
+ if (header[0] != 0x8d || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) {
+ return false;
+ }
+
+ return true;
+#else
+ byte header[4];
+ if (stream.read(header, sizeof(header)) != sizeof(header)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+
+ if (READ_BE_UINT32(header) != MKTAG('S', 'O', 'L', 0)) {
+ return false;
+ }
+
+ return true;
+#endif
+}
+
+bool detectWaveAudio(Common::SeekableReadStream &stream) {
+ const size_t initialPosition = stream.pos();
+
+ byte blockHeader[8];
+ if (stream.read(blockHeader, sizeof(blockHeader)) != sizeof(blockHeader)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+ const uint32 headerType = READ_BE_UINT32(blockHeader);
+
+ if (headerType != MKTAG('R', 'I', 'F', 'F')) {
+ return false;
+ }
+
+ return true;
+}
+
+#pragma mark -
+
+Audio32::Audio32(ResourceManager *resMan) :
+ _resMan(resMan),
+ _mixer(g_system->getMixer()),
+ _handle(),
+ _mutex(),
+
+ _numActiveChannels(0),
+ _inAudioThread(false),
+
+ _globalSampleRate(44100),
+ _maxAllowedSampleRate(44100),
+ _globalBitDepth(16),
+ _maxAllowedBitDepth(16),
+ _globalNumOutputChannels(2),
+ _maxAllowedOutputChannels(2),
+ _preload(0),
+
+ _robotAudioPaused(false),
+
+ _pausedAtTick(0),
+ _startedAtTick(0),
+
+ _attenuatedMixing(true),
+
+ _monitoredChannelIndex(-1),
+ _monitoredBuffer(nullptr),
+ _monitoredBufferSize(0),
+ _numMonitoredSamples(0) {
+
+ if (getSciVersion() < SCI_VERSION_3) {
+ _channels.resize(5);
+ } else {
+ _channels.resize(8);
+ }
+
+ _useModifiedAttenuation = false;
+ if (getSciVersion() == SCI_VERSION_2_1_MIDDLE) {
+ switch (g_sci->getGameId()) {
+ case GID_MOTHERGOOSEHIRES:
+ case GID_PQ4:
+ case GID_QFG4:
+ case GID_SQ6:
+ _useModifiedAttenuation = true;
+ default:
+ break;
+ }
+ } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
+ switch (g_sci->getGameId()) {
+ // 1.51 uses the non-standard attenuation, but 2.00b
+ // does not, which is strange.
+ case GID_KQ7:
+ _useModifiedAttenuation = true;
+ default:
+ break;
+ }
+ }
+
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _mixer->pauseHandle(_handle, true);
+}
+
+Audio32::~Audio32() {
+ stop(kAllChannels);
+ _mixer->stopHandle(_handle);
+ free(_monitoredBuffer);
+}
+
+#pragma mark -
+#pragma mark AudioStream implementation
+
+int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) {
+ int samplesToRead = numSamples;
+
+ // The parent rate converter will request N * 2
+ // samples from this `readBuffer` call, because
+ // we tell it that we send stereo output, but
+ // the source stream we're mixing in may be
+ // mono, in which case we need to request half
+ // as many samples from the mono stream and let
+ // the converter double them for stereo output
+ if (!sourceStream->isStereo()) {
+ samplesToRead >>= 1;
+ }
+
+ int samplesWritten = 0;
+
+ do {
+ if (loop && sourceStream->endOfStream()) {
+ sourceStream->rewind();
+ }
+
+ const int loopSamplesWritten = converter->flow(*sourceStream, targetBuffer, samplesToRead, leftVolume, rightVolume);
+
+ if (loopSamplesWritten == 0) {
+ break;
+ }
+
+ samplesToRead -= loopSamplesWritten;
+ samplesWritten += loopSamplesWritten;
+ targetBuffer += loopSamplesWritten << 1;
+ } while (loop && samplesToRead > 0);
+
+ if (!sourceStream->isStereo()) {
+ samplesWritten <<= 1;
+ }
+
+ return samplesWritten;
+}
+
+// In earlier versions of SCI32 engine, audio mixing is
+// split into three different functions.
+//
+// The first function is called from the main game thread in
+// AsyncEventCheck; later versions of SSCI also call it when
+// getting the playback position. This function is
+// responsible for cleaning up finished channels and
+// filling active channel buffers with decompressed audio
+// matching the hardware output audio format so they can
+// just be copied into the main DAC buffer directly later.
+//
+// The second function is called by the audio hardware when
+// the DAC buffer needs to be filled, and by `play` when
+// there is only one active sample (so it can just blow away
+// whatever was already in the DAC buffer). It merges all
+// active channels into the DAC buffer and then updates the
+// offset into the DAC buffer.
+//
+// Finally, a third function is called by the second
+// function, and it actually puts data into the DAC buffer,
+// performing volume, distortion, and balance adjustments.
+//
+// Since we only have one callback from the audio thread,
+// and should be able to do all audio processing in
+// real time, and we have streams, and we do not need to
+// completely fill the audio buffer, the functionality of
+// all these original functions is combined here and
+// simplified.
+int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ // ResourceManager is not thread-safe so we need to
+ // avoid calling into it from the audio thread, but at
+ // the same time we need to be able to clear out any
+ // finished channels on a regular basis
+ _inAudioThread = true;
+
+ // The system mixer should not try to get data when
+ // Audio32 is paused
+ assert(_pausedAtTick == 0 && _numActiveChannels > 0);
+
+ freeUnusedChannels();
+
+ // The caller of `readBuffer` is a rate converter,
+ // which reuses (without clearing) an intermediate
+ // buffer, so we need to zero the intermediate buffer
+ // to prevent mixing into audio data from the last
+ // callback.
+ memset(buffer, 0, numSamples * sizeof(Audio::st_sample_t));
+
+ // This emulates the attenuated mixing mode of SSCI
+ // engine, which reduces the volume of the target
+ // buffer when each new channel is mixed in.
+ // Instead of manipulating the content of the target
+ // buffer when mixing (which would either require
+ // modification of RateConverter or an expensive second
+ // pass against the entire target buffer), we just
+ // scale the volume for each channel in advance, with
+ // the earliest (lowest) channel having the highest
+ // amount of attenuation (lowest volume).
+ uint8 attenuationAmount;
+ uint8 attenuationStepAmount;
+ if (_useModifiedAttenuation) {
+ // channel | divisor
+ // 0 | 0 (>> 0)
+ // 1 | 4 (>> 2)
+ // 2 | 8...
+ attenuationAmount = _numActiveChannels * 2;
+ attenuationStepAmount = 2;
+ } else {
+ // channel | divisor
+ // 0 | 2 (>> 1)
+ // 1 | 4 (>> 2)
+ // 2 | 6...
+ if (_monitoredChannelIndex == -1 && _numActiveChannels > 1) {
+ attenuationAmount = _numActiveChannels + 1;
+ attenuationStepAmount = 1;
+ } else {
+ attenuationAmount = 0;
+ attenuationStepAmount = 0;
+ }
+ }
+
+ int maxSamplesWritten = 0;
+
+ for (int16 channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) {
+ attenuationAmount -= attenuationStepAmount;
+
+ const AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick || (channel.robot && _robotAudioPaused)) {
+ continue;
+ }
+
+ // Channel finished fading and had the
+ // stopChannelOnFade flag set, so no longer exists
+ if (channel.fadeStepsRemaining && processFade(channelIndex)) {
+ --channelIndex;
+ continue;
+ }
+
+ if (channel.robot) {
+ // TODO: Robot audio into output buffer
+ continue;
+ }
+
+ if (channel.vmd) {
+ // TODO: VMD audio into output buffer
+ continue;
+ }
+
+ Audio::st_volume_t leftVolume, rightVolume;
+
+ if (channel.pan == -1 || !isStereo()) {
+ leftVolume = rightVolume = channel.volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ } else {
+ // TODO: This should match the SCI3 algorithm,
+ // which seems to halve the volume of each
+ // channel when centered; is this intended?
+ leftVolume = channel.volume * (100 - channel.pan) / 100 * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ rightVolume = channel.volume * channel.pan / 100 * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ }
+
+ if (_monitoredChannelIndex == -1 && _attenuatedMixing) {
+ leftVolume >>= attenuationAmount;
+ rightVolume >>= attenuationAmount;
+ }
+
+ if (channelIndex == _monitoredChannelIndex) {
+ const size_t bufferSize = numSamples * sizeof(Audio::st_sample_t);
+ if (_monitoredBufferSize < bufferSize) {
+ _monitoredBuffer = (Audio::st_sample_t *)realloc(_monitoredBuffer, bufferSize);
+ _monitoredBufferSize = bufferSize;
+ }
+
+ memset(_monitoredBuffer, 0, _monitoredBufferSize);
+
+ _numMonitoredSamples = writeAudioInternal(channel.stream, channel.converter, _monitoredBuffer, numSamples, leftVolume, rightVolume, channel.loop);
+
+ Audio::st_sample_t *sourceBuffer = _monitoredBuffer;
+ Audio::st_sample_t *targetBuffer = buffer;
+ const Audio::st_sample_t *const end = _monitoredBuffer + _numMonitoredSamples;
+ while (sourceBuffer != end) {
+ Audio::clampedAdd(*targetBuffer++, *sourceBuffer++);
+ }
+
+ if (_numMonitoredSamples > maxSamplesWritten) {
+ maxSamplesWritten = _numMonitoredSamples;
+ }
+ } else if (!channel.stream->endOfStream() || channel.loop) {
+ const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, leftVolume, rightVolume, channel.loop);
+ if (channelSamplesWritten > maxSamplesWritten) {
+ maxSamplesWritten = channelSamplesWritten;
+ }
+ }
+ }
+
+ _inAudioThread = false;
+
+ return maxSamplesWritten;
+}
+
+#pragma mark -
+#pragma mark Channel management
+
+int16 Audio32::findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const {
+ // NOTE: argc/argv are already reduced by one in our engine because
+ // this call is always made from a subop, so no reduction for the
+ // subop is made in this function. SSCI takes extra steps to skip
+ // the subop argument.
+
+ argc -= startIndex;
+ if (argc <= 0) {
+ return kAllChannels;
+ }
+
+ Common::StackLock lock(_mutex);
+
+ if (_numActiveChannels == 0) {
+ return kNoExistingChannel;
+ }
+
+ ResourceId searchId;
+
+ if (argc < 5) {
+ searchId = ResourceId(kResourceTypeAudio, argv[startIndex].toUint16());
+ } else {
+ searchId = ResourceId(
+ kResourceTypeAudio36,
+ argv[startIndex].toUint16(),
+ argv[startIndex + 1].toUint16(),
+ argv[startIndex + 2].toUint16(),
+ argv[startIndex + 3].toUint16(),
+ argv[startIndex + 4].toUint16()
+ );
+ }
+
+ return findChannelById(searchId, soundNode);
+}
+
+int16 Audio32::findChannelById(const ResourceId resourceId, const reg_t soundNode) const {
+ Common::StackLock lock(_mutex);
+
+ if (_numActiveChannels == 0) {
+ return kNoExistingChannel;
+ }
+
+ if (resourceId.getType() == kResourceTypeAudio) {
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ const AudioChannel channel = _channels[i];
+ if (
+ channel.id == resourceId &&
+ (soundNode.isNull() || soundNode == channel.soundNode)
+ ) {
+ return i;
+ }
+ }
+ } else if (resourceId.getType() == kResourceTypeAudio36) {
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ const AudioChannel &candidate = getChannel(i);
+ if (!candidate.robot && candidate.id == resourceId) {
+ return i;
+ }
+ }
+ } else {
+ error("Audio32::findChannelById: Unknown resource type %d", resourceId.getType());
+ }
+
+ return kNoExistingChannel;
+}
+
+void Audio32::freeUnusedChannels() {
+ Common::StackLock lock(_mutex);
+ for (int channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) {
+ const AudioChannel &channel = getChannel(channelIndex);
+ if (channel.stream->endOfStream()) {
+ if (channel.loop) {
+ channel.stream->rewind();
+ } else {
+ stop(channelIndex--);
+ }
+ }
+ }
+
+ if (!_inAudioThread) {
+ unlockResources();
+ }
+}
+
+void Audio32::freeChannel(const int16 channelIndex) {
+ // The original engine did this:
+ // 1. Unlock memory-cached resource, if one existed
+ // 2. Close patched audio file descriptor, if one existed
+ // 3. Free decompression memory buffer, if one existed
+ // 4. Clear monitored memory buffer, if one existed
+ Common::StackLock lock(_mutex);
+ AudioChannel &channel = getChannel(channelIndex);
+
+ // We cannot unlock resources from the audio thread
+ // because ResourceManager is not thread-safe; instead,
+ // we just record that the resource needs unlocking and
+ // unlock it whenever we are on the main thread again
+ if (_inAudioThread) {
+ _resourcesToUnlock.push_back(channel.resource);
+ } else {
+ _resMan->unlockResource(channel.resource);
+ }
+
+ channel.resource = nullptr;
+ delete channel.stream;
+ channel.stream = nullptr;
+ delete channel.resourceStream;
+ channel.resourceStream = nullptr;
+ delete channel.converter;
+ channel.converter = nullptr;
+
+ if (_monitoredChannelIndex == channelIndex) {
+ _monitoredChannelIndex = -1;
+ }
+}
+
+void Audio32::unlockResources() {
+ Common::StackLock lock(_mutex);
+ assert(!_inAudioThread);
+
+ for (UnlockList::const_iterator it = _resourcesToUnlock.begin(); it != _resourcesToUnlock.end(); ++it) {
+ _resMan->unlockResource(*it);
+ }
+ _resourcesToUnlock.clear();
+}
+
+#pragma mark -
+#pragma mark Script compatibility
+
+void Audio32::setSampleRate(uint16 rate) {
+ if (rate > _maxAllowedSampleRate) {
+ rate = _maxAllowedSampleRate;
+ }
+
+ _globalSampleRate = rate;
+}
+
+void Audio32::setBitDepth(uint8 depth) {
+ if (depth > _maxAllowedBitDepth) {
+ depth = _maxAllowedBitDepth;
+ }
+
+ _globalBitDepth = depth;
+}
+
+void Audio32::setNumOutputChannels(int16 numChannels) {
+ if (numChannels > _maxAllowedOutputChannels) {
+ numChannels = _maxAllowedOutputChannels;
+ }
+
+ _globalNumOutputChannels = numChannels;
+}
+
+#pragma mark -
+#pragma mark Playback
+
+uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor) {
+ Common::StackLock lock(_mutex);
+
+ freeUnusedChannels();
+
+ if (channelIndex != kNoExistingChannel) {
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick) {
+ resume(channelIndex);
+ return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000);
+ }
+
+ warning("Tried to resume channel %s that was not paused", channel.id.toString().c_str());
+ return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000);
+ }
+
+ if (_numActiveChannels == _channels.size()) {
+ warning("Audio mixer is full when trying to play %s", resourceId.toString().c_str());
+ return 0;
+ }
+
+ // NOTE: SCI engine itself normally searches in this order:
+ //
+ // For Audio36:
+ //
+ // 1. First, request a FD using Audio36 name and use it as the
+ // source FD for reading the audio resource data.
+ // 2a. If the returned FD is -1, or equals the audio map, or
+ // equals the audio bundle, try to get the offset of the
+ // data from the audio map, using the Audio36 name.
+ //
+ // If the returned offset is -1, this is not a valid resource;
+ // return 0. Otherwise, set the read offset for the FD to the
+ // returned offset.
+ // 2b. Otherwise, use the FD as-is (it is a patch file), with zero
+ // offset, and record it separately so it can be closed later.
+ //
+ // For plain audio:
+ //
+ // 1. First, request an Audio resource from the resource cache. If
+ // one does not exist, make the same request for a Wave resource.
+ // 2a. If an audio resource was discovered, record its memory ID
+ // and clear the streaming FD
+ // 2b. Otherwise, request an Audio FD. If one does not exist, make
+ // the same request for a Wave FD. If neither exist, this is not
+ // a valid resource; return 0. Otherwise, use the returned FD as
+ // the streaming ID and set the memory ID to null.
+ //
+ // Once these steps are complete, the audio engine either has a file
+ // descriptor + offset that it can use to read streamed audio, or it
+ // has a memory ID that it can use to read cached audio.
+ //
+ // Here in ScummVM we just ask the resource manager to give us the
+ // resource and we get a seekable stream.
+
+ // TODO: This should be fixed to use streaming, which means
+ // fixing the resource manager to allow streaming, which means
+ // probably rewriting a bunch of the resource manager.
+ Resource *resource = _resMan->findResource(resourceId, true);
+ if (resource == nullptr) {
+ return 0;
+ }
+
+ channelIndex = _numActiveChannels++;
+
+ AudioChannel &channel = getChannel(channelIndex);
+ channel.id = resourceId;
+ channel.resource = resource;
+ channel.loop = loop;
+ channel.robot = false;
+ channel.vmd = false;
+ channel.lastFadeTick = 0;
+ channel.fadeStepsRemaining = 0;
+ channel.soundNode = soundNode;
+ channel.volume = volume < 0 || volume > kMaxVolume ? (int)kMaxVolume : volume;
+ // TODO: SCI3 introduces stereo audio
+ channel.pan = -1;
+
+ if (monitor) {
+ _monitoredChannelIndex = channelIndex;
+ }
+
+ Common::MemoryReadStream headerStream(resource->_header, resource->_headerSize, DisposeAfterUse::NO);
+ Common::SeekableReadStream *dataStream = channel.resourceStream = resource->makeStream();
+
+ if (detectSolAudio(headerStream)) {
+ channel.stream = makeSOLStream(&headerStream, dataStream, DisposeAfterUse::NO);
+ } else if (detectWaveAudio(*dataStream)) {
+ channel.stream = Audio::makeWAVStream(dataStream, DisposeAfterUse::NO);
+ } else {
+ byte flags = Audio::FLAG_LITTLE_ENDIAN;
+ if (_globalBitDepth == 16) {
+ flags |= Audio::FLAG_16BITS;
+ } else {
+ flags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (_globalNumOutputChannels == 2) {
+ flags |= Audio::FLAG_STEREO;
+ }
+
+ channel.stream = Audio::makeRawStream(dataStream, _globalSampleRate, flags, DisposeAfterUse::NO);
+ }
+
+ channel.converter = Audio::makeRateConverter(channel.stream->getRate(), getRate(), channel.stream->isStereo(), false);
+
+ // NOTE: SCI engine sets up a decompression buffer here for the audio
+ // stream, plus writes information about the sample to the channel to
+ // convert to the correct hardware output format, and allocates the
+ // monitoring buffer to match the bitrate/samplerate/channels of the
+ // original stream. We do not need to do any of these things since we
+ // use audio streams, and allocate and fill the monitoring buffer
+ // when reading audio data from the stream.
+
+ channel.duration = /* round up */ 1 + (channel.stream->getLength().msecs() * 60 / 1000);
+
+ const uint32 now = g_sci->getTickCount();
+ channel.pausedAtTick = autoPlay ? 0 : now;
+ channel.startedAtTick = now;
+
+ if (_numActiveChannels == 1) {
+ _startedAtTick = now;
+ _mixer->pauseHandle(_handle, false);
+ }
+
+ return channel.duration;
+}
+
+bool Audio32::resume(const int16 channelIndex) {
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ Common::StackLock lock(_mutex);
+ const uint32 now = g_sci->getTickCount();
+
+ if (channelIndex == kAllChannels) {
+ // Global pause in SSCI is an extra layer over
+ // individual channel pauses, so only unpause channels
+ // if there was not a global pause in place
+ if (_pausedAtTick == 0) {
+ return false;
+ }
+
+ for (int i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (!channel.pausedAtTick) {
+ channel.startedAtTick += now - _pausedAtTick;
+ }
+ }
+
+ _startedAtTick += now - _pausedAtTick;
+ _pausedAtTick = 0;
+ _mixer->pauseHandle(_handle, false);
+ return true;
+ } else if (channelIndex == kRobotChannel) {
+ for (int i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (channel.robot) {
+ channel.startedAtTick += now - channel.pausedAtTick;
+ channel.pausedAtTick = 0;
+ // TODO: Robot
+ // StartRobot();
+ return true;
+ }
+ }
+ } else {
+ AudioChannel &channel = getChannel(channelIndex);
+ if (channel.pausedAtTick) {
+ channel.startedAtTick += now - channel.pausedAtTick;
+ channel.pausedAtTick = 0;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Audio32::pause(const int16 channelIndex) {
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ Common::StackLock lock(_mutex);
+ const uint32 now = g_sci->getTickCount();
+ bool didPause = false;
+
+ if (channelIndex == kAllChannels) {
+ if (_pausedAtTick == 0) {
+ _pausedAtTick = now;
+ _mixer->pauseHandle(_handle, true);
+ didPause = true;
+ }
+ } else if (channelIndex == kRobotChannel) {
+ _robotAudioPaused = true;
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (channel.robot) {
+ channel.pausedAtTick = now;
+ }
+ }
+
+ // NOTE: The actual engine returns false here regardless of whether
+ // or not channels were paused
+ } else {
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick == 0) {
+ channel.pausedAtTick = now;
+ didPause = true;
+ }
+ }
+
+ return didPause;
+}
+
+int16 Audio32::stop(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ const int16 oldNumChannels = _numActiveChannels;
+
+ if (channelIndex == kNoExistingChannel || oldNumChannels == 0) {
+ return 0;
+ }
+
+ if (channelIndex == kAllChannels) {
+ for (int i = 0; i < oldNumChannels; ++i) {
+ freeChannel(i);
+ }
+ _numActiveChannels = 0;
+ } else {
+ freeChannel(channelIndex);
+ --_numActiveChannels;
+ for (int i = channelIndex; i < oldNumChannels - 1; ++i) {
+ _channels[i] = _channels[i + 1];
+ if (i + 1 == _monitoredChannelIndex) {
+ _monitoredChannelIndex = i;
+ }
+ }
+ }
+
+ // NOTE: SSCI stops the DSP interrupt and frees the
+ // global decompression buffer here if there are no
+ // more active channels
+ if (_numActiveChannels == 0) {
+ _mixer->pauseHandle(_handle, true);
+ }
+
+ return oldNumChannels;
+}
+
+int16 Audio32::getPosition(const int16 channelIndex) const {
+ Common::StackLock lock(_mutex);
+ if (channelIndex == kNoExistingChannel || _numActiveChannels == 0) {
+ return -1;
+ }
+
+ // NOTE: SSCI treats this as an unsigned short except for
+ // when the value is 65535, then it treats it as signed
+ int position = -1;
+ const uint32 now = g_sci->getTickCount();
+
+ // NOTE: The original engine also queried the audio driver to see whether
+ // it thought that there was audio playback occurring via driver opcode 9
+ if (channelIndex == kAllChannels) {
+ if (_pausedAtTick) {
+ position = _pausedAtTick - _startedAtTick;
+ } else {
+ position = now - _startedAtTick;
+ }
+ } else {
+ const AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick) {
+ position = channel.pausedAtTick - channel.startedAtTick;
+ } else if (_pausedAtTick) {
+ position = _pausedAtTick - channel.startedAtTick;
+ } else {
+ position = now - channel.startedAtTick;
+ }
+ }
+
+ return MIN(position, 65534);
+}
+
+void Audio32::setLoop(const int16 channelIndex, const bool loop) {
+ Common::StackLock lock(_mutex);
+
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return;
+ }
+
+ AudioChannel &channel = getChannel(channelIndex);
+ channel.loop = loop;
+}
+
+reg_t Audio32::kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv) {
+ if (argc == 0) {
+ return make_reg(0, _numActiveChannels);
+ }
+
+ const int16 channelIndex = findChannelByArgs(argc, argv, 0, NULL_REG);
+ ResourceId resourceId;
+ bool loop;
+ int16 volume;
+ bool monitor = false;
+ reg_t soundNode = NULL_REG;
+
+ if (argc >= 5) {
+ resourceId = ResourceId(kResourceTypeAudio36, argv[0].toUint16(), argv[1].toUint16(), argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16());
+
+ if (argc < 6 || argv[5].toSint16() == 1) {
+ loop = false;
+ } else {
+ // NOTE: Uses -1 for infinite loop. Presumably the
+ // engine was supposed to allow counter loops at one
+ // point, but ended up only using loop as a boolean.
+ loop = (bool)argv[5].toSint16();
+ }
+
+ if (argc < 7 || argv[6].toSint16() < 0 || argv[6].toSint16() > Audio32::kMaxVolume) {
+ volume = Audio32::kMaxVolume;
+
+ if (argc >= 7) {
+ monitor = true;
+ }
+ } else {
+ volume = argv[6].toSint16();
+ }
+ } else {
+ resourceId = ResourceId(kResourceTypeAudio, argv[0].toUint16());
+
+ if (argc < 2 || argv[1].toSint16() == 1) {
+ loop = false;
+ } else {
+ loop = (bool)argv[1].toSint16();
+ }
+
+ // TODO: SCI3 uses the 0x80 bit as a flag to
+ // indicate "priority channel", but the volume is clamped
+ // in this call to 0x7F so that flag never makes it into
+ // the audio subsystem
+ if (argc < 3 || argv[2].toSint16() < 0 || argv[2].toSint16() > Audio32::kMaxVolume) {
+ volume = Audio32::kMaxVolume;
+
+ if (argc >= 3) {
+ monitor = true;
+ }
+ } else {
+ volume = argv[2].toSint16();
+ }
+
+ soundNode = argc == 4 ? argv[3] : NULL_REG;
+ }
+
+ return make_reg(0, play(channelIndex, resourceId, autoPlay, loop, volume, soundNode, monitor));
+}
+
+#pragma mark -
+#pragma mark Effects
+
+int16 Audio32::getVolume(const int16 channelIndex) const {
+ Common::StackLock lock(_mutex);
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return _mixer->getChannelVolume(_handle) * kMaxVolume / Audio::Mixer::kMaxChannelVolume;
+ }
+
+ return getChannel(channelIndex).volume;
+}
+
+void Audio32::setVolume(const int16 channelIndex, int16 volume) {
+ Common::StackLock lock(_mutex);
+
+ volume = MIN((int16)kMaxVolume, volume);
+ if (channelIndex == kAllChannels) {
+ ConfMan.setInt("sfx_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ ConfMan.setInt("speech_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ _mixer->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ g_engine->syncSoundSettings();
+ } else if (channelIndex != kNoExistingChannel) {
+ getChannel(channelIndex).volume = volume;
+ }
+}
+
+bool Audio32::fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) {
+ Common::StackLock lock(_mutex);
+
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return false;
+ }
+
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.id.getType() != kResourceTypeAudio || channel.volume == targetVolume) {
+ return false;
+ }
+
+ if (steps) {
+ channel.fadeVolume = targetVolume;
+ channel.fadeSpeed = speed;
+ channel.fadeStepsRemaining = steps;
+ channel.stopChannelOnFade = stopAfterFade;
+ channel.lastFadeTick = g_sci->getTickCount();
+ } else {
+ setVolume(channelIndex, targetVolume);
+ }
+
+ return true;
+}
+
+bool Audio32::processFade(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ AudioChannel &channel = getChannel(channelIndex);
+
+ uint32 now = g_sci->getTickCount();
+
+ if (channel.lastFadeTick + channel.fadeSpeed <= now) {
+ --channel.fadeStepsRemaining;
+
+ if (!channel.fadeStepsRemaining) {
+ if (channel.stopChannelOnFade) {
+ stop(channelIndex);
+ return true;
+ } else {
+ setVolume(channelIndex, channel.fadeVolume);
+ }
+ } else {
+ int volume = channel.volume - (channel.volume - channel.fadeVolume) / (channel.fadeStepsRemaining + 1);
+
+ if (volume == channel.fadeVolume) {
+ channel.fadeStepsRemaining = 1;
+ }
+
+ setVolume(channelIndex, volume);
+ channel.lastFadeTick = now;
+ }
+ }
+
+ return false;
+}
+
+#pragma mark -
+#pragma mark Signal monitoring
+
+bool Audio32::hasSignal() const {
+ Common::StackLock lock(_mutex);
+
+ if (_monitoredChannelIndex == -1) {
+ return false;
+ }
+
+ const Audio::st_sample_t *buffer = _monitoredBuffer;
+ const Audio::st_sample_t *const end = _monitoredBuffer + _numMonitoredSamples;
+
+ while (buffer != end) {
+ const Audio::st_sample_t sample = *buffer++;
+ if (sample > 1280 || sample < -1280) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h
new file mode 100644
index 0000000000..416b81d865
--- /dev/null
+++ b/engines/sci/sound/audio32.h
@@ -0,0 +1,591 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_AUDIO32_H
+#define SCI_AUDIO32_H
+#include "audio/audiostream.h" // for AudioStream, SeekableAudioStream (...
+#include "audio/mixer.h" // for Mixer, SoundHandle
+#include "audio/rate.h" // for Audio::st_volume_t, RateConverter
+#include "common/array.h" // for Array
+#include "common/mutex.h" // for StackLock, Mutex
+#include "common/scummsys.h" // for int16, uint8, uint32, uint16
+#include "engines/sci/resource.h" // for ResourceId
+#include "sci/engine/vm_types.h" // for reg_t, NULL_REG
+
+namespace Sci {
+
+/**
+ * An audio channel used by the software SCI mixer.
+ */
+struct AudioChannel {
+ /**
+ * The ID of the resource loaded into this channel.
+ */
+ ResourceId id;
+
+ /**
+ * The resource loaded into this channel.
+ */
+ Resource *resource;
+
+ /**
+ * Data stream containing the raw audio for the channel.
+ */
+ Common::SeekableReadStream *resourceStream;
+
+ /**
+ * The audio stream loaded into this channel.
+ * `SeekableAudioStream` is used here instead of
+ * `RewindableAudioStream` because
+ * `RewindableAudioStream` does not include the
+ * `getLength` function, which is needed to tell the
+ * game engine the duration of audio streams.
+ */
+ Audio::SeekableAudioStream *stream;
+
+ /**
+ * The converter used to transform and merge the input
+ * stream into the mixer's output buffer.
+ */
+ Audio::RateConverter *converter;
+
+ /**
+ * Duration of the channel, in ticks.
+ */
+ uint32 duration;
+
+ /**
+ * The tick when the channel was started.
+ */
+ uint32 startedAtTick;
+
+ /**
+ * The tick when the channel was paused.
+ */
+ uint32 pausedAtTick;
+
+ /**
+ * Whether or not the audio in this channel should loop
+ * infinitely.
+ */
+ bool loop;
+
+ /**
+ * The time the last fade iteration occurred.
+ */
+ uint32 lastFadeTick;
+
+ /**
+ * The target volume of the fade.
+ */
+ int fadeVolume;
+
+ /**
+ * The number of ticks that should elapse between
+ * each change of volume.
+ */
+ int fadeSpeed;
+
+ /**
+ * The number of iterations the fade should take to
+ * complete. If this value is 0, it indicates that the
+ * channel is not fading.
+ */
+ int fadeStepsRemaining;
+
+ /**
+ * Whether or not the channel should be stopped and
+ * freed when the fade is complete.
+ */
+ bool stopChannelOnFade;
+
+ /**
+ * Whether or not this channel contains a Robot
+ * audio block.
+ */
+ bool robot;
+
+ /**
+ * Whether or not this channel contains a VMD audio
+ * track.
+ */
+ bool vmd;
+
+ /**
+ * For digital sound effects, the related VM
+ * Sound::nodePtr object for the sound.
+ */
+ reg_t soundNode;
+
+ /**
+ * The playback volume, from 1 to 127 inclusive.
+ */
+ int volume;
+
+ /**
+ * The amount to pan to the right, from 0 to 100.
+ * 50 is centered, -1 is not panned.
+ */
+ int pan;
+};
+
+/**
+ * Special audio channel indexes used to select a channel
+ * for digital audio playback.
+ */
+enum AudioChannelIndex {
+ kRobotChannel = -3,
+ kNoExistingChannel = -2,
+ kAllChannels = -1
+};
+
+/**
+ * Audio32 acts as a permanent audio stream into the system
+ * mixer and provides digital audio services for the SCI32
+ * engine, since the system mixer does not support all the
+ * features of SCI.
+ */
+class Audio32 : public Audio::AudioStream {
+public:
+ Audio32(ResourceManager *resMan);
+ ~Audio32();
+
+private:
+ ResourceManager *_resMan;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _handle;
+ Common::Mutex _mutex;
+
+ enum {
+ /**
+ * The maximum channel volume.
+ */
+ kMaxVolume = 127
+ };
+
+#pragma mark -
+#pragma mark AudioStream implementation
+public:
+ int readBuffer(Audio::st_sample_t *buffer, const int numSamples);
+ bool isStereo() const { return true; }
+ int getRate() const { return _mixer->getOutputRate(); }
+ bool endOfData() const { return _numActiveChannels == 0; }
+ bool endOfStream() const { return false; }
+
+private:
+ /**
+ * Mixes audio from the given source stream into the
+ * target buffer using the given rate converter.
+ */
+ int writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop);
+
+#pragma mark -
+#pragma mark Channel management
+public:
+ /**
+ * Gets the number of currently active channels.
+ */
+ inline uint8 getNumActiveChannels() const {
+ Common::StackLock lock(_mutex);
+ return _numActiveChannels;
+ }
+
+ /**
+ * Finds a channel that is already configured for the
+ * given audio sample.
+ *
+ * @param startIndex The location of the audio resource
+ * information in the arguments list.
+ */
+ int16 findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const;
+
+ /**
+ * Finds a channel that is already configured for the
+ * given audio sample.
+ */
+ int16 findChannelById(const ResourceId resourceId, const reg_t soundNode = NULL_REG) const;
+
+private:
+ typedef Common::Array<Resource *> UnlockList;
+
+ /**
+ * The audio channels.
+ */
+ Common::Array<AudioChannel> _channels;
+
+ /**
+ * The number of active audio channels in the mixer.
+ * Being active is not the same as playing; active
+ * channels may be paused.
+ */
+ uint8 _numActiveChannels;
+
+ /**
+ * Whether or not we are in the audio thread.
+ *
+ * This flag is used instead of passing a parameter to
+ * `freeUnusedChannels` because a parameter would
+ * require forwarding through the public method `stop`,
+ * and there is not currently any reason for this
+ * implementation detail to be exposed.
+ */
+ bool _inAudioThread;
+
+ /**
+ * The list of resources from freed channels that need
+ * to be unlocked from the main thread.
+ */
+ UnlockList _resourcesToUnlock;
+
+ /**
+ * Gets the audio channel at the given index.
+ */
+ inline AudioChannel &getChannel(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ assert(channelIndex >= 0 && channelIndex < _numActiveChannels);
+ return _channels[channelIndex];
+ }
+
+ /**
+ * Gets the audio channel at the given index.
+ */
+ inline const AudioChannel &getChannel(const int16 channelIndex) const {
+ Common::StackLock lock(_mutex);
+ assert(channelIndex >= 0 && channelIndex < _numActiveChannels);
+ return _channels[channelIndex];
+ }
+
+ /**
+ * Frees all non-looping channels that have reached the
+ * end of their stream.
+ */
+ void freeUnusedChannels();
+
+ /**
+ * Frees resources allocated to the given channel.
+ */
+ void freeChannel(const int16 channelIndex);
+
+ /**
+ * Unlocks all resources that were freed by the audio
+ * thread.
+ */
+ void unlockResources();
+
+#pragma mark -
+#pragma mark Script compatibility
+public:
+ /**
+ * Gets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ inline uint16 getSampleRate() const {
+ return _globalSampleRate;
+ }
+
+ /**
+ * Sets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ void setSampleRate(uint16 rate);
+
+ /**
+ * Gets the (fake) bit depth of the hardware DAC.
+ * For script compatibility only.
+ */
+ inline uint8 getBitDepth() const {
+ return _globalBitDepth;
+ }
+
+ /**
+ * Sets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ void setBitDepth(uint8 depth);
+
+ /**
+ * Gets the (fake) number of output (speaker) channels
+ * of the hardware DAC. For script compatibility only.
+ */
+ inline uint8 getNumOutputChannels() const {
+ return _globalNumOutputChannels;
+ }
+
+ /**
+ * Sets the (fake) number of output (speaker) channels
+ * of the hardware DAC. For script compatibility only.
+ */
+ void setNumOutputChannels(int16 numChannels);
+
+ /**
+ * Gets the (fake) number of preloaded channels.
+ * For script compatibility only.
+ */
+ inline uint8 getPreload() const {
+ return _preload;
+ }
+
+ /**
+ * Sets the (fake) number of preloaded channels.
+ * For script compatibility only.
+ */
+ inline void setPreload(uint8 preload) {
+ _preload = preload;
+ }
+
+private:
+ /**
+ * The hardware DAC sample rate. Stored only for script
+ * compatibility.
+ */
+ uint16 _globalSampleRate;
+
+ /**
+ * The maximum allowed sample rate of the system mixer.
+ * Stored only for script compatibility.
+ */
+ uint16 _maxAllowedSampleRate;
+
+ /**
+ * The hardware DAC bit depth. Stored only for script
+ * compatibility.
+ */
+ uint8 _globalBitDepth;
+
+ /**
+ * The maximum allowed bit depth of the system mixer.
+ * Stored only for script compatibility.
+ */
+ uint8 _maxAllowedBitDepth;
+
+ /**
+ * The hardware DAC output (speaker) channel
+ * configuration. Stored only for script compatibility.
+ */
+ uint8 _globalNumOutputChannels;
+
+ /**
+ * The maximum allowed number of output (speaker)
+ * channels of the system mixer. Stored only for script
+ * compatibility.
+ */
+ uint8 _maxAllowedOutputChannels;
+
+ /**
+ * The number of audio channels that should have their
+ * data preloaded into memory instead of streaming from
+ * disk.
+ * 1 = all channels, 2 = 2nd active channel and above,
+ * etc.
+ * Stored only for script compatibility.
+ */
+ uint8 _preload;
+
+#pragma mark -
+#pragma mark Robot
+public:
+
+private:
+ /**
+ * When true, channels marked as robot audio will not be
+ * played.
+ */
+ bool _robotAudioPaused;
+
+#pragma mark -
+#pragma mark Playback
+public:
+ /**
+ * Starts or resumes playback of an audio channel.
+ */
+ uint16 play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor);
+
+ /**
+ * Resumes playback of a paused audio channel, or of
+ * the entire audio player.
+ */
+ bool resume(const int16 channelIndex);
+ bool resume(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return resume(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Pauses an audio channel, or the entire audio player.
+ */
+ bool pause(const int16 channelIndex);
+ bool pause(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return pause(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Stops and unloads an audio channel, or the entire
+ * audio player.
+ */
+ int16 stop(const int16 channelIndex);
+ int16 stop(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return stop(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Returns the playback position for the given channel
+ * number, in ticks.
+ */
+ int16 getPosition(const int16 channelIndex) const;
+ int16 getPosition(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return getPosition(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Sets whether or not the given channel should loop.
+ */
+ void setLoop(const int16 channelIndex, const bool loop);
+ void setLoop(const ResourceId resourceId, const reg_t soundNode, const bool loop) {
+ Common::StackLock lock(_mutex);
+ setLoop(findChannelById(resourceId, soundNode), loop);
+ }
+
+ reg_t kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv);
+
+private:
+ /**
+ * The tick when audio was globally paused.
+ */
+ uint32 _pausedAtTick;
+
+ /**
+ * The tick when audio was globally started.
+ */
+ uint32 _startedAtTick;
+
+#pragma mark -
+#pragma mark Effects
+public:
+ /**
+ * Gets the volume for a given channel. Passing
+ * `kAllChannels` will get the global volume.
+ */
+ int16 getVolume(const int16 channelIndex) const;
+ int16 getVolume(const ResourceId resourceId, const reg_t soundNode) const {
+ Common::StackLock lock(_mutex);
+ return getVolume(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Sets the volume of an audio channel. Passing
+ * `kAllChannels` will set the global volume.
+ */
+ void setVolume(const int16 channelIndex, int16 volume);
+ void setVolume(const ResourceId resourceId, const reg_t soundNode, const int16 volume) {
+ Common::StackLock lock(_mutex);
+ setVolume(findChannelById(resourceId, soundNode), volume);
+ }
+
+ /**
+ * Initiate an immediate fade of the given channel.
+ */
+ bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade);
+ bool fadeChannel(const ResourceId resourceId, const reg_t soundNode, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) {
+ Common::StackLock lock(_mutex);
+ return fadeChannel(findChannelById(resourceId, soundNode), targetVolume, speed, steps, stopAfterFade);
+ }
+
+ /**
+ * Gets whether attenuated mixing mode is active.
+ */
+ inline bool getAttenuatedMixing() const {
+ return _attenuatedMixing;
+ }
+
+ /**
+ * Sets the attenuated mixing mode.
+ */
+ void setAttenuatedMixing(bool attenuated) {
+ Common::StackLock lock(_mutex);
+ _attenuatedMixing = attenuated;
+ }
+
+private:
+ /**
+ * If true, audio will be mixed by reducing the target
+ * buffer by half every time a new channel is mixed in.
+ * The final channel is not attenuated.
+ */
+ bool _attenuatedMixing;
+
+ /**
+ * When true, a modified attenuation algorithm is used
+ * (`A/4 + B`) instead of standard linear attenuation
+ * (`A/2 + B/2`).
+ */
+ bool _useModifiedAttenuation;
+
+ /**
+ * Processes an audio fade for the given channel.
+ *
+ * @returns true if the fade was completed and the
+ * channel was stopped.
+ */
+ bool processFade(const int16 channelIndex);
+
+#pragma mark -
+#pragma mark Signal monitoring
+public:
+ /**
+ * Returns whether the currently monitored audio channel
+ * contains any signal within the next audio frame.
+ */
+ bool hasSignal() const;
+
+private:
+ /**
+ * The index of the channel being monitored for signal,
+ * or -1 if no channel is monitored. When a channel is
+ * monitored, it also causes the engine to play only the
+ * monitored channel.
+ */
+ int16 _monitoredChannelIndex;
+
+ /**
+ * The data buffer holding decompressed audio data for
+ * the channel that will be monitored for an audio
+ * signal.
+ */
+ Audio::st_sample_t *_monitoredBuffer;
+
+ /**
+ * The size of the buffer, in bytes.
+ */
+ size_t _monitoredBufferSize;
+
+ /**
+ * The number of valid audio samples in the signal
+ * monitoring buffer.
+ */
+ int _numMonitoredSamples;
+};
+
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp
new file mode 100644
index 0000000000..e445403120
--- /dev/null
+++ b/engines/sci/sound/decoders/sol.cpp
@@ -0,0 +1,273 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "common/substream.h"
+#include "common/util.h"
+#include "engines/sci/sci.h"
+#include "engines/sci/sound/decoders/sol.h"
+
+namespace Sci {
+
+// Note that the 16-bit version is also used in coktelvideo.cpp
+static const uint16 tableDPCM16[128] = {
+ 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
+ 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
+ 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
+ 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
+ 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
+ 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
+ 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
+ 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
+ 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
+ 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
+ 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
+ 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
+ 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
+};
+
+static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 };
+
+/**
+ * Decompresses 16-bit DPCM compressed audio. Each byte read
+ * outputs one sample into the decompression buffer.
+ */
+static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, int16 &sample) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = audioStream.readByte();
+ if (delta & 0x80) {
+ sample -= tableDPCM16[delta & 0x7f];
+ } else {
+ sample += tableDPCM16[delta];
+ }
+ sample = CLIP<int16>(sample, -32768, 32767);
+ *out++ = TO_LE_16(sample);
+ }
+}
+
+/**
+ * Decompresses one half of an 8-bit DPCM compressed audio
+ * byte.
+ */
+static void deDPCM8Nibble(int16 *out, uint8 &sample, uint8 delta) {
+ const uint8 lastSample = sample;
+ if (delta & 8) {
+ sample -= tableDPCM8[delta & 7];
+ } else {
+ sample += tableDPCM8[delta & 7];
+ }
+ sample = CLIP<byte>(sample, 0, 255);
+ *out = ((lastSample + sample) << 7) ^ 0x8000;
+}
+
+/**
+ * Decompresses 8-bit DPCM compressed audio. Each byte read
+ * outputs two samples into the decompression buffer.
+ */
+static void deDPCM8(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, uint8 &sample) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = audioStream.readByte();
+ deDPCM8Nibble(out++, sample, delta >> 4);
+ deDPCM8Nibble(out++, sample, delta & 0xf);
+ }
+}
+
+# pragma mark -
+
+template<bool STEREO, bool S16BIT>
+SOLStream<STEREO, S16BIT>::SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const int32 dataOffset, const uint16 sampleRate, const int32 rawDataSize) :
+ _stream(stream, disposeAfterUse),
+ _dataOffset(dataOffset),
+ _sampleRate(sampleRate),
+ // SSCI aligns the size of SOL data to 32 bits
+ _rawDataSize(rawDataSize & ~3) {
+ // TODO: This is not valid for stereo SOL files, which
+ // have interleaved L/R compression so need to store the
+ // carried values for each channel separately. See
+ // 60900.aud from Lighthouse for an example stereo file
+ if (S16BIT) {
+ _dpcmCarry16 = 0;
+ } else {
+ _dpcmCarry8 = 0x80;
+ }
+
+ const uint8 compressionRatio = 2;
+ const uint8 numChannels = STEREO ? 2 : 1;
+ const uint8 bytesPerSample = S16BIT ? 2 : 1;
+ _length = Audio::Timestamp((_rawDataSize * compressionRatio * 1000) / (_sampleRate * numChannels * bytesPerSample), 60);
+ }
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::seek(const Audio::Timestamp &where) {
+ if (where != 0) {
+ // In order to seek in compressed SOL files, all
+ // previous bytes must be known since it uses
+ // differential compression. Therefore, only seeking
+ // to the beginning is supported now (SSCI does not
+ // offer seeking anyway)
+ return false;
+ }
+
+ if (S16BIT) {
+ _dpcmCarry16 = 0;
+ } else {
+ _dpcmCarry8 = 0x80;
+ }
+
+ return _stream->seek(_dataOffset, SEEK_SET);
+}
+
+template <bool STEREO, bool S16BIT>
+Audio::Timestamp SOLStream<STEREO, S16BIT>::getLength() const {
+ return _length;
+}
+
+template <bool STEREO, bool S16BIT>
+int SOLStream<STEREO, S16BIT>::readBuffer(int16 *buffer, const int numSamples) {
+ // Reading an odd number of 8-bit samples will result in a loss of samples
+ // since one byte represents two samples and we do not store the second
+ // nibble in this case; it should never happen in reality
+ assert(S16BIT || (numSamples % 2) == 0);
+
+ const int samplesPerByte = S16BIT ? 1 : 2;
+
+ int32 bytesToRead = numSamples / samplesPerByte;
+ if (_stream->pos() + bytesToRead > _rawDataSize) {
+ bytesToRead = _rawDataSize - _stream->pos();
+ }
+
+ if (S16BIT) {
+ deDPCM16(buffer, *_stream, bytesToRead, _dpcmCarry16);
+ } else {
+ deDPCM8(buffer, *_stream, bytesToRead, _dpcmCarry8);
+ }
+
+ const int samplesRead = bytesToRead * samplesPerByte;
+ return samplesRead;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::isStereo() const {
+ return STEREO;
+}
+
+template <bool STEREO, bool S16BIT>
+int SOLStream<STEREO, S16BIT>::getRate() const {
+ return _sampleRate;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::endOfData() const {
+ return _stream->eos() || _stream->pos() >= _dataOffset + _rawDataSize;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::rewind() {
+ return seek(0);
+}
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+
+ // TODO: Might not be necessary? Makes seeking work, but
+ // not sure if audio is ever actually seeked in SSCI.
+ const int32 initialPosition = stream->pos();
+
+ byte header[6];
+ if (stream->read(header, sizeof(header)) != sizeof(header)) {
+ return nullptr;
+ }
+
+ if (header[0] != 0x8d || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) {
+ return nullptr;
+ }
+
+ const uint8 headerSize = header[1];
+ const uint16 sampleRate = stream->readUint16LE();
+ const byte flags = stream->readByte();
+ const uint32 dataSize = stream->readUint32LE();
+
+ if (flags & kCompressed) {
+ if (flags & kStereo && flags & k16Bit) {
+ return new SOLStream<true, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else if (flags & kStereo) {
+ return new SOLStream<true, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else if (flags & k16Bit) {
+ return new SOLStream<false, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else {
+ return new SOLStream<false, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ }
+ }
+
+ byte rawFlags = Audio::FLAG_LITTLE_ENDIAN;
+ if (flags & k16Bit) {
+ rawFlags |= Audio::FLAG_16BITS;
+ } else {
+ rawFlags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (flags & kStereo) {
+ rawFlags |= Audio::FLAG_STEREO;
+ }
+
+ return Audio::makeRawStream(new Common::SeekableSubReadStream(stream, initialPosition + headerSize, initialPosition + headerSize + dataSize, disposeAfterUse), sampleRate, rawFlags, disposeAfterUse);
+}
+
+// TODO: This needs to be removed when resource manager is fixed
+// to not split audio into two parts
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStream, Common::SeekableReadStream *dataStream, DisposeAfterUse::Flag disposeAfterUse) {
+
+ if (headerStream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) {
+ return nullptr;
+ }
+
+ const uint16 sampleRate = headerStream->readUint16LE();
+ const byte flags = headerStream->readByte();
+ const int32 dataSize = headerStream->readSint32LE();
+
+ if (flags & kCompressed) {
+ if (flags & kStereo && flags & k16Bit) {
+ return new SOLStream<true, true>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else if (flags & kStereo) {
+ return new SOLStream<true, false>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else if (flags & k16Bit) {
+ return new SOLStream<false, true>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else {
+ return new SOLStream<false, false>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ }
+ }
+
+ byte rawFlags = Audio::FLAG_LITTLE_ENDIAN;
+ if (flags & k16Bit) {
+ rawFlags |= Audio::FLAG_16BITS;
+ } else {
+ rawFlags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (flags & kStereo) {
+ rawFlags |= Audio::FLAG_STEREO;
+ }
+
+ return Audio::makeRawStream(dataStream, sampleRate, rawFlags, disposeAfterUse);
+}
+
+}
diff --git a/engines/sci/sound/decoders/sol.h b/engines/sci/sound/decoders/sol.h
new file mode 100644
index 0000000000..1046d0b213
--- /dev/null
+++ b/engines/sci/sound/decoders/sol.h
@@ -0,0 +1,89 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_SOUND_DECODERS_SOL_H
+#define SCI_SOUND_DECODERS_SOL_H
+#include "audio/audiostream.h"
+#include "common/stream.h"
+
+namespace Sci {
+
+enum SOLFlags {
+ kCompressed = 1,
+ k16Bit = 4,
+ kStereo = 16
+};
+
+template <bool STEREO, bool S16BIT>
+class SOLStream : public Audio::SeekableAudioStream {
+private:
+ /**
+ * Read stream containing possibly-compressed SOL audio.
+ */
+ Common::DisposablePtr<Common::SeekableReadStream> _stream;
+
+ /**
+ * Start offset of the audio data in the read stream.
+ */
+ int32 _dataOffset;
+
+ /**
+ * Sample rate of audio data.
+ */
+ uint16 _sampleRate;
+
+ /**
+ * The raw (possibly-compressed) size of audio data in
+ * the stream.
+ */
+ int32 _rawDataSize;
+
+ /**
+ * The last sample from the previous DPCM decode.
+ */
+ union {
+ int16 _dpcmCarry16;
+ uint8 _dpcmCarry8;
+ };
+
+ /**
+ * The calculated length of the stream.
+ */
+ Audio::Timestamp _length;
+
+ virtual bool seek(const Audio::Timestamp &where) override;
+ virtual Audio::Timestamp getLength() const override;
+ virtual int readBuffer(int16 *buffer, const int numSamples) override;
+ virtual bool isStereo() const override;
+ virtual int getRate() const override;
+ virtual bool endOfData() const override;
+ virtual bool rewind() override;
+
+public:
+ SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const int32 dataOffset, const uint16 sampleRate, const int32 rawDataSize);
+};
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStream, Common::SeekableReadStream *dataStream, DisposeAfterUse::Flag disposeAfterUse);
+}
+#endif
diff --git a/engines/sci/sound/drivers/amigamac.cpp b/engines/sci/sound/drivers/amigamac.cpp
index 0f93b19e7c..031fd32164 100644
--- a/engines/sci/sound/drivers/amigamac.cpp
+++ b/engines/sci/sound/drivers/amigamac.cpp
@@ -773,6 +773,7 @@ bool MidiDriver_AmigaMac::loadInstrumentsSCI0(Common::File &file) {
if (id < 0 || id > 255) {
warning("Amiga/Mac driver: Error: instrument ID out of bounds");
+ delete instrument;
return false;
}
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index 5a11ac386a..3f34ecc2f8 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -212,6 +212,13 @@ void SciMusic::clearPlayList() {
void SciMusic::pauseAll(bool pause) {
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
+#ifdef ENABLE_SCI32
+ // The entire DAC will have been paused by the caller;
+ // do not pause the individual samples too
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && (*i)->isSample) {
+ continue;
+ }
+#endif
soundToggle(*i, pause);
}
}
@@ -472,7 +479,16 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
}
}
- if (pSnd->pStreamAud) {
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+
+ g_sci->_audio32->play(kNoExistingChannel, ResourceId(kResourceTypeAudio, pSnd->resourceId), true, pSnd->loop != 0 && pSnd->loop != 1, pSnd->volume, pSnd->soundObj, false);
+
+ return;
+ } else
+#endif
if (!_pMixer->isSoundHandleActive(pSnd->hCurrentAud)) {
if ((_currentlyPlayingSample) && (_pMixer->isSoundHandleActive(_currentlyPlayingSample->hCurrentAud))) {
// Another sample is already playing, we have to stop that one
@@ -550,10 +566,18 @@ void SciMusic::soundStop(MusicEntry *pSnd) {
pSnd->status = kSoundStopped;
if (_soundVersion <= SCI_VERSION_0_LATE)
pSnd->isQueued = false;
- if (pSnd->pStreamAud) {
- if (_currentlyPlayingSample == pSnd)
- _currentlyPlayingSample = NULL;
- _pMixer->stopHandle(pSnd->hCurrentAud);
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+#endif
+ if (_currentlyPlayingSample == pSnd)
+ _currentlyPlayingSample = NULL;
+ _pMixer->stopHandle(pSnd->hCurrentAud);
+#ifdef ENABLE_SCI32
+ }
+#endif
}
if (pSnd->pMidiParser) {
@@ -572,9 +596,12 @@ void SciMusic::soundStop(MusicEntry *pSnd) {
void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) {
assert(volume <= MUSIC_VOLUME_MAX);
- if (pSnd->pStreamAud) {
- // we simply ignore volume changes for samples, because sierra sci also
- // doesn't support volume for samples via kDoSound
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->setVolume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj, volume);
+ }
+#endif
} else if (pSnd->pMidiParser) {
Common::StackLock lock(_mutex);
pSnd->pMidiParser->mainThreadBegin();
@@ -614,12 +641,20 @@ void SciMusic::soundKill(MusicEntry *pSnd) {
_mutex.unlock();
- if (pSnd->pStreamAud) {
- if (_currentlyPlayingSample == pSnd) {
- // Forget about this sound, in case it was currently playing
- _currentlyPlayingSample = NULL;
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+#endif
+ if (_currentlyPlayingSample == pSnd) {
+ // Forget about this sound, in case it was currently playing
+ _currentlyPlayingSample = NULL;
+ }
+ _pMixer->stopHandle(pSnd->hCurrentAud);
+#ifdef ENABLE_SCI32
}
- _pMixer->stopHandle(pSnd->hCurrentAud);
+#endif
delete pSnd->pStreamAud;
pSnd->pStreamAud = NULL;
delete pSnd->pLoopStream;
@@ -685,6 +720,18 @@ void SciMusic::soundResume(MusicEntry *pSnd) {
}
void SciMusic::soundToggle(MusicEntry *pSnd, bool pause) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && pSnd->isSample) {
+ if (pause) {
+ g_sci->_audio32->pause(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+ g_sci->_audio32->resume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ }
+
+ return;
+ }
+#endif
+
if (pause)
soundPause(pSnd);
else
@@ -813,6 +860,7 @@ MusicEntry::MusicEntry() {
pStreamAud = 0;
pLoopStream = 0;
pMidiParser = 0;
+ isSample = false;
for (int i = 0; i < 16; ++i) {
_usedChannels[i] = 0xFF;
diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h
index 047f63b3b7..3a6de81c49 100644
--- a/engines/sci/sound/music.h
+++ b/engines/sci/sound/music.h
@@ -31,6 +31,9 @@
#include "sci/sci.h"
#include "sci/resource.h"
#include "sci/sound/drivers/mididriver.h"
+#ifdef ENABLE_SCI32
+#include "sci/sound/audio32.h"
+#endif
namespace Audio {
class LoopingAudioStream;
@@ -123,6 +126,7 @@ public:
Audio::RewindableAudioStream *pStreamAud;
Audio::LoopingAudioStream *pLoopStream;
Audio::SoundHandle hCurrentAud;
+ bool isSample;
public:
MusicEntry();
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
index fe33ea1feb..c5d8dda56b 100644
--- a/engines/sci/sound/soundcmd.cpp
+++ b/engines/sci/sound/soundcmd.cpp
@@ -23,6 +23,7 @@
#include "common/config-manager.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
+#include "sci/resource.h"
#include "sci/sound/audio.h"
#include "sci/sound/music.h"
#include "sci/sound/soundcmd.h"
@@ -97,12 +98,21 @@ void SoundCommandParser::initSoundResource(MusicEntry *newSound) {
// user wants the digital version.
if (_useDigitalSFX || !newSound->soundRes) {
int sampleLen;
- newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen);
- newSound->soundType = Audio::Mixer::kSFXSoundType;
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ newSound->isSample = g_sci->getResMan()->testResource(ResourceId(kResourceTypeAudio, newSound->resourceId));
+ } else {
+#endif
+ newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen);
+ newSound->soundType = Audio::Mixer::kSFXSoundType;
+ newSound->isSample = newSound->pStreamAud != nullptr;
+#ifdef ENABLE_SCI32
+ }
+#endif
}
}
- if (!newSound->pStreamAud && newSound->soundRes)
+ if (!newSound->isSample && newSound->soundRes)
_music->soundInitSnd(newSound);
}
@@ -118,6 +128,7 @@ void SoundCommandParser::processInitSound(reg_t obj) {
newSound->resourceId = resourceId;
newSound->soundObj = obj;
newSound->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
+ newSound->overridePriority = false;
if (_soundVersion <= SCI_VERSION_0_LATE)
newSound->priority = readSelectorValue(_segMan, obj, SELECTOR(priority));
else
@@ -133,7 +144,7 @@ void SoundCommandParser::processInitSound(reg_t obj) {
_music->pushBackSlot(newSound);
- if (newSound->soundRes || newSound->pStreamAud) {
+ if (newSound->soundRes || newSound->isSample) {
// Notify the engine
if (_soundVersion <= SCI_VERSION_0_LATE)
writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundInitialized);
@@ -214,17 +225,6 @@ void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) {
musicSlot->fadeStep = 0;
}
-reg_t SoundCommandParser::kDoSoundRestore(int argc, reg_t *argv, reg_t acc) {
- // Called after loading, to restore the playlist
- // We don't really use or need this
- return acc;
-}
-
-reg_t SoundCommandParser::kDoSoundDummy(int argc, reg_t *argv, reg_t acc) {
- warning("cmdDummy invoked"); // not supposed to occur
- return acc;
-}
-
reg_t SoundCommandParser::kDoSoundDispose(int argc, reg_t *argv, reg_t acc) {
debugC(kDebugLevelSound, "kDoSound(dispose): %04x:%04x", PRINT_REG(argv[0]));
processDisposeSound(argv[0]);
@@ -313,10 +313,22 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
}
reg_t obj = argv[0];
- uint16 value = argc > 1 ? argv[1].toUint16() : 0;
- if (!obj.getSegment()) { // pause the whole playlist
- _music->pauseAll(value);
- } else { // pause a playlist slot
+ const bool shouldPause = argc > 1 ? argv[1].toUint16() : false;
+ if (
+ (_soundVersion < SCI_VERSION_2_1_EARLY && !obj.getSegment()) ||
+ (_soundVersion >= SCI_VERSION_2_1_EARLY && obj.isNull())
+ ) {
+ _music->pauseAll(shouldPause);
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ if (shouldPause) {
+ g_sci->_audio32->pause(kAllChannels);
+ } else {
+ g_sci->_audio32->resume(kAllChannels);
+ }
+ }
+#endif
+ } else {
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
// This happens quite frequently
@@ -324,7 +336,23 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
return acc;
}
- _music->soundToggle(musicSlot, value);
+#ifdef ENABLE_SCI32
+ // NOTE: The original engine also expected a global
+ // "kernel call" flag to be true in order to perform
+ // this action, but the architecture of the ScummVM
+ // implementation is so different that it doesn't
+ // matter here
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ const int16 channelIndex = g_sci->_audio32->findChannelById(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj);
+
+ if (shouldPause) {
+ g_sci->_audio32->pause(channelIndex);
+ } else {
+ g_sci->_audio32->resume(channelIndex);
+ }
+ } else
+#endif
+ _music->soundToggle(musicSlot, shouldPause);
}
return acc;
}
@@ -354,7 +382,11 @@ reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc)
int vol = CLIP<int16>(argv[0].toSint16(), 0, MUSIC_MASTERVOLUME_MAX);
vol = vol * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
ConfMan.setInt("music_volume", vol);
- ConfMan.setInt("sfx_volume", vol);
+ // In SCI32, digital audio volume is controlled separately by
+ // kDoAudioVolume
+ if (_soundVersion < SCI_VERSION_2_1_EARLY) {
+ ConfMan.setInt("sfx_volume", vol);
+ }
g_engine->syncSoundSettings();
}
return acc;
@@ -377,6 +409,13 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
int volume = musicSlot->volume;
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ g_sci->_audio32->fadeChannel(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, argv[2].toSint16(), argv[3].toSint16(), argv[4].toSint16(), (bool)argv[5].toSint16());
+ return acc;
+ }
+#endif
+
// If sound is not playing currently, set signal directly
if (musicSlot->status != kSoundPlaying) {
debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x fading requested, but sound is currently not playing", PRINT_REG(obj));
@@ -465,7 +504,18 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {
return;
}
- if (musicSlot->pStreamAud) {
+ if (musicSlot->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ const int position = g_sci->_audio32->getPosition(g_sci->_audio32->findChannelById(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj));
+
+ if (position == -1) {
+ processStopSound(musicSlot->soundObj, true);
+ }
+
+ return;
+ }
+#endif
// Update digital sound effect slots
uint currentLoopCounter = 0;
@@ -668,6 +718,12 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {
value = CLIP<int>(value, 0, MUSIC_VOLUME_MAX);
+#ifdef ENABLE_SCI32
+ // SSCI unconditionally sets volume if it is digital audio
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ _music->soundSetVolume(musicSlot, value);
+ } else
+#endif
if (musicSlot->volume != value) {
musicSlot->volume = value;
_music->soundSetVolume(musicSlot, value);
@@ -726,6 +782,15 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
}
return acc;
}
+
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ if (value != -1) {
+ value = 1;
+ }
+ }
+#endif
+
if (value == -1) {
musicSlot->loop = 0xFFFF;
} else {
@@ -733,6 +798,13 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
}
writeSelectorValue(_segMan, obj, SELECTOR(loop), musicSlot->loop);
+
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ g_sci->_audio32->setLoop(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, value == -1);
+ }
+#endif
+
return acc;
}
diff --git a/engines/sci/sound/sync.cpp b/engines/sci/sound/sync.cpp
new file mode 100644
index 0000000000..4e75dab725
--- /dev/null
+++ b/engines/sci/sound/sync.cpp
@@ -0,0 +1,76 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/engine/kernel.h"
+#include "sci/util.h"
+#include "sync.h"
+
+namespace Sci {
+
+Sync::Sync(ResourceManager *resMan, SegManager *segMan) :
+ _resMan(resMan),
+ _segMan(segMan),
+ _resource(nullptr),
+ _offset(0) {}
+
+Sync::~Sync() {
+ stop();
+}
+
+void Sync::start(const ResourceId id, const reg_t syncObjAddr) {
+ _resource = _resMan->findResource(id, true);
+ _offset = 0;
+
+ if (_resource) {
+ writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), 0);
+ } else {
+ warning("Sync::start: failed to find resource %s", id.toString().c_str());
+ // Notify the scripts to stop sound sync
+ writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), SIGNAL_OFFSET);
+ }
+}
+
+void Sync::next(const reg_t syncObjAddr) {
+ if (_resource && (_offset < _resource->size - 1)) {
+ int16 syncCue = -1;
+ int16 syncTime = (int16)READ_SCI11ENDIAN_UINT16(_resource->data + _offset);
+
+ _offset += 2;
+
+ if ((syncTime != -1) && (_offset < _resource->size - 1)) {
+ syncCue = (int16)READ_SCI11ENDIAN_UINT16(_resource->data + _offset);
+ _offset += 2;
+ }
+
+ writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncTime), syncTime);
+ writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), syncCue);
+ }
+}
+
+void Sync::stop() {
+ if (_resource) {
+ _resMan->unlockResource(_resource);
+ _resource = nullptr;
+ }
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/paint.cpp b/engines/sci/sound/sync.h
index 482b81aff1..4b9e2d1b3c 100644
--- a/engines/sci/graphics/paint.cpp
+++ b/engines/sci/sound/sync.h
@@ -20,25 +20,42 @@
*
*/
-#include "graphics/primitives.h"
+#ifndef SCI_SOUND_SYNC_H
+#define SCI_SOUND_SYNC_H
-#include "sci/sci.h"
-#include "sci/engine/state.h"
#include "sci/engine/selector.h"
-#include "sci/graphics/paint.h"
+#include "sci/engine/vm_types.h"
namespace Sci {
-GfxPaint::GfxPaint() {
-}
+enum AudioSyncCommands {
+ kSciAudioSyncStart = 0,
+ kSciAudioSyncNext = 1,
+ kSciAudioSyncStop = 2
+};
-GfxPaint::~GfxPaint() {
-}
+class Resource;
+class ResourceManager;
+class SegManager;
-void GfxPaint::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) {
-}
+/**
+ * Sync class, kDoSync and relevant functions for SCI games.
+ * Provides AV synchronization for animations.
+ */
+class Sync {
+ SegManager *_segMan;
+ ResourceManager *_resMan;
+ Resource *_resource;
+ uint _offset;
+
+public:
+ Sync(ResourceManager *resMan, SegManager *segMan);
+ ~Sync();
-void GfxPaint::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) {
-}
+ void start(const ResourceId id, const reg_t syncObjAddr);
+ void next(const reg_t syncObjAddr);
+ void stop();
+};
} // End of namespace Sci
+#endif
diff --git a/engines/scumm/he/cup_player_he.cpp b/engines/scumm/he/cup_player_he.cpp
index c409863804..3a7ad49c2f 100644
--- a/engines/scumm/he/cup_player_he.cpp
+++ b/engines/scumm/he/cup_player_he.cpp
@@ -408,6 +408,9 @@ void CUP_Player::handleSRLE(Common::SeekableReadStream &dataStream, uint32 dataS
static void decodeLZSS(uint8 *dst, const uint8 *src1, const uint8 *src2, const uint8 *src3) {
uint8 wnd[4096];
int index = 1;
+
+ memset(wnd, 0, sizeof(wnd));
+
while (1) {
int code = *src1++;
for (int b = 0; b < 8; ++b) {
diff --git a/engines/scumm/he/intern_he.h b/engines/scumm/he/intern_he.h
index 7f7babc604..c6abac3ecc 100644
--- a/engines/scumm/he/intern_he.h
+++ b/engines/scumm/he/intern_he.h
@@ -121,13 +121,24 @@ class ScummEngine_v70he : public ScummEngine_v60he {
friend class ResExtractor;
protected:
+ enum HESndFlags {
+ HE_SND_LOOP = 1,
+ HE_SND_APPEND = 2,
+ HE_SND_SOFT_SOUND = 4,
+ HE_SND_QUICK_START = 8,
+ HE_SND_OFFSET = 16,
+ HE_SND_VOL = 32,
+ HE_SND_FREQUENCY = 64,
+ HE_SND_PAN = 128
+ };
+
ResExtractor *_resExtractor;
byte *_heV7DiskOffsets;
byte *_heV7RoomOffsets;
uint32 *_heV7RoomIntOffsets;
- int32 _heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags, _heSndSoundFreq;
+ int32 _heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags, _heSndSoundFreq, _heSndPan, _heSndVol;
int _numStoredFlObjects;
ObjectData *_storedFlObjects;
@@ -168,7 +179,7 @@ protected:
virtual void setDefaultCursor();
/* HE version 70 script opcodes */
- void o70_startSound();
+ void o70_soundOps();
void o70_pickupObject();
void o70_getActorRoom();
void o70_resourceRoutines();
@@ -622,7 +633,7 @@ protected:
void o100_redimArray();
void o100_roomOps();
void o100_setSystemMessage();
- void o100_startSound();
+ void o100_soundOps();
void o100_setSpriteInfo();
void o100_startScript();
void o100_systemOps();
diff --git a/engines/scumm/he/moonbase/ai_defenseunit.cpp b/engines/scumm/he/moonbase/ai_defenseunit.cpp
index 37ce303bc5..ab61297603 100644
--- a/engines/scumm/he/moonbase/ai_defenseunit.cpp
+++ b/engines/scumm/he/moonbase/ai_defenseunit.cpp
@@ -80,14 +80,14 @@ Common::Point *AntiAirUnit::createTargetPos(int index, int distance, int weaponT
targetPos->y = getPosY();
} else {
ratio = MAX(0, (getRadius() / distance));
- targetPos->x = getPosX() - ratio * (getPosX() - sourceX);
- targetPos->y = getPosY() - ratio * (getPosY() - sourceY);
+ targetPos->x = (int16)(getPosX() - ratio * (getPosX() - sourceX));
+ targetPos->y = (int16)(getPosY() - ratio * (getPosY() - sourceY));
}
break;
case ITEM_EMP:
- if (getRadius() + 215 > distance) { //emp radius
+ if (getRadius() + 215 > distance) { // Emp radius
double x1 = static_cast<double>(sourceX);
double y1 = static_cast<double>(sourceY);
double x2 = static_cast<double>(getPosX());
@@ -96,17 +96,17 @@ Common::Point *AntiAirUnit::createTargetPos(int index, int distance, int weaponT
double r2 = static_cast<double>(getRadius() + 3);
double d = static_cast<double>(distance);
- //formulae for calculating one point of intersection of two circles
+ // Formulae for calculating one point of intersection of two circles
float root = sqrt((((r1 + r2) * (r1 + r2)) - (d * d)) * ((d * d) - ((r2 - r1) * (r2 - r1))));
- int x = ((x1 + x2) / 2) + ((x2 - x1) * (r1 * r1 - r2 * r2)) / (2 * d * d) + ((y2 - y1) / (2 * d * d)) * root;
- int y = ((y1 + y2) / 2) + ((y2 - y1) * (r1 * r1 - r2 * r2)) / (2 * d * d) - ((x2 - x1) / (2 * d * d)) * root;
+ int x = (int)(((x1 + x2) / 2) + ((x2 - x1) * (r1 * r1 - r2 * r2)) / (2 * d * d) + ((y2 - y1) / (2 * d * d)) * root);
+ int y = (int)(((y1 + y2) / 2) + ((y2 - y1) * (r1 * r1 - r2 * r2)) / (2 * d * d) - ((x2 - x1) / (2 * d * d)) * root);
targetPos->x = x;
targetPos->y = y;
} else {
ratio = 1 - (getRadius() / static_cast<float>(distance - 20));
- targetPos->x = sourceX + ratio * (getPosX() - sourceX);
- targetPos->y = sourceY + ratio * (getPosY() - sourceY);
+ targetPos->x = (int16)(sourceX + ratio * (getPosX() - sourceX));
+ targetPos->y = (int16)(sourceY + ratio * (getPosY() - sourceY));
}
break;
@@ -188,7 +188,7 @@ Common::Point *ShieldUnit::createTargetPos(int index, int distance, int weaponTy
break;
case ITEM_EMP:
- if (getRadius() + 215 > distance) { //emp radius
+ if (getRadius() + 215 > distance) { // Emp radius
double x1 = static_cast<double>(sourceX);
double y1 = static_cast<double>(sourceY);
double x2 = static_cast<double>(getPosX());
@@ -197,17 +197,17 @@ Common::Point *ShieldUnit::createTargetPos(int index, int distance, int weaponTy
double r2 = static_cast<double>(getRadius() + 10);
double d = static_cast<double>(distance);
- //formulae for calculating one point of intersection of two circles
+ // Formulae for calculating one point of intersection of two circles
float root = sqrt((((r1 + r2) * (r1 + r2)) - (d * d)) * ((d * d) - ((r2 - r1) * (r2 - r1))));
- int x = ((x1 + x2) / 2) + ((x2 - x1) * (r1 * r1 - r2 * r2)) / (2 * d * d) + ((y2 - y1) / (2 * d * d)) * root;
- int y = ((y1 + y2) / 2) + ((y2 - y1) * (r1 * r1 - r2 * r2)) / (2 * d * d) - ((x2 - x1) / (2 * d * d)) * root;
+ int x = (int)(((x1 + x2) / 2) + ((x2 - x1) * (r1 * r1 - r2 * r2)) / (2 * d * d) + ((y2 - y1) / (2 * d * d)) * root);
+ int y = (int)(((y1 + y2) / 2) + ((y2 - y1) * (r1 * r1 - r2 * r2)) / (2 * d * d) - ((x2 - x1) / (2 * d * d)) * root);
targetPos->x = x;
targetPos->y = y;
} else {
ratio = 1 - (getRadius() / static_cast<float>(distance - 20));
- targetPos->x = sourceX + ratio * (getPosX() - sourceX);
- targetPos->y = sourceY + ratio * (getPosY() - sourceY);
+ targetPos->x = (int16)(sourceX + ratio * (getPosX() - sourceX));
+ targetPos->y = (int16)(sourceY + ratio * (getPosY() - sourceY));
}
if (distance < getRadius()) {
@@ -282,8 +282,8 @@ Common::Point *MineUnit::createTargetPos(int index, int distance, int weaponType
case ITEM_EMP:
ratio = 1 - (getRadius() / static_cast<float>(distance - 20));
- targetPos->x = sourceX + ratio * (getPosX() - sourceX);
- targetPos->y = sourceY + ratio * (getPosY() - sourceY);
+ targetPos->x = (int16)(sourceX + ratio * (getPosX() - sourceX));
+ targetPos->y = (int16)(sourceY + ratio * (getPosY() - sourceY));
break;
default:
diff --git a/engines/scumm/he/moonbase/ai_main.cpp b/engines/scumm/he/moonbase/ai_main.cpp
index 7ca4fbd933..98a577bdba 100644
--- a/engines/scumm/he/moonbase/ai_main.cpp
+++ b/engines/scumm/he/moonbase/ai_main.cpp
@@ -606,7 +606,7 @@ int AI::masterControlProgram(const int paramCount, const int32 *params) {
targetX = getHubX(closestHub);
targetY = getHubY(closestHub);
- delete launchAction;
+ delete[] launchAction;
launchAction = NULL;
_aiState = STATE_DEFEND_TARGET;
delete myTree;
@@ -647,7 +647,7 @@ int AI::masterControlProgram(const int paramCount, const int32 *params) {
}
} else {
index++;
- delete launchAction;
+ delete[] launchAction;
launchAction = NULL;
}
} else {
@@ -667,7 +667,7 @@ int AI::masterControlProgram(const int paramCount, const int32 *params) {
_aiState = STATE_INIT_ACQUIRE_TARGET;
} else {
index++;
- delete launchAction;
+ delete[] launchAction;
launchAction = NULL;
}
}
@@ -695,7 +695,7 @@ int AI::masterControlProgram(const int paramCount, const int32 *params) {
}
} else {
index++;
- delete launchAction;
+ delete[] launchAction;
launchAction = NULL;
}
}
@@ -817,8 +817,7 @@ int AI::masterControlProgram(const int paramCount, const int32 *params) {
{
// ANGLE setting
- int angleAdjustment = 0;
- angleAdjustment = _vm->_rnd.getRandomNumber(_aiType[getCurrentPlayer()]->getAngleVariation() * AI_VAR_BASE_ANGLE) * 3.6;
+ int angleAdjustment = (int)(_vm->_rnd.getRandomNumber(_aiType[getCurrentPlayer()]->getAngleVariation() * AI_VAR_BASE_ANGLE) * 3.6);
//pos or neg choice
angleAdjustment *= ((_vm->_rnd.getRandomNumber(1) * 2) - 1);
angleAdjustment *= randomAttenuation;
@@ -2111,7 +2110,7 @@ int *AI::energizeTarget(int &targetX, int &targetY, int index) {
break;
}
- testDist = ((((n - attempt) / n) * .5) + .5) * (getDistance(getHubX(nextUnit), getHubY(nextUnit), targetX, targetY) / .8);
+ testDist = (((((double)n - (double)attempt) / n) * .5) + .5) * (getDistance(getHubX(nextUnit), getHubY(nextUnit), targetX, targetY) / .8);
xPos = getHubX(nextUnit) + testDist * cos(degToRad(testAngle));
yPos = getHubY(nextUnit) + testDist * sin(degToRad(testAngle));
}
diff --git a/engines/scumm/he/moonbase/ai_targetacquisition.cpp b/engines/scumm/he/moonbase/ai_targetacquisition.cpp
index 938a02dba3..313ea7a411 100644
--- a/engines/scumm/he/moonbase/ai_targetacquisition.cpp
+++ b/engines/scumm/he/moonbase/ai_targetacquisition.cpp
@@ -226,7 +226,7 @@ IContainedObject *Sortie::createChildObj(int index, int &completionFlag) {
if (!_ai->_vm->_rnd.getRandomNumber(4))
currentWeapon->setTypeID(ITEM_MINE);
- (*i)->setDamage(thisDamage);
+ (*i)->setDamage((int)thisDamage);
// Apply emp effect
if (currentWeapon->getTypeID() == ITEM_EMP) {
@@ -278,7 +278,8 @@ float Sortie::calcH() {
}
int Sortie::checkSuccess() {
- if (!_enemyDefenses.size()) return SUCCESS;
+ if (!_enemyDefenses.size())
+ return SUCCESS;
int targetX = getTargetPosX();
int targetY = getTargetPosY();
@@ -319,6 +320,15 @@ void Sortie::printEnemyDefenses() {
}
}
+Defender::Defender(AI *ai) : _ai(ai) {
+ _sourceX = _sourceY = 0;
+ _targetX = _targetY = 0;
+ _sourceUnit = 0;
+ _power = 0;
+ _angle = 0;
+ _unit = 0;
+}
+
int Defender::calculateDefenseUnitPosition(int targetX, int targetY, int index) {
int currentPlayer = _ai->getCurrentPlayer();
@@ -431,8 +441,8 @@ int Defender::calculateDefenseUnitPosition(int targetX, int targetY, int index)
int randAngle = directAngleToHub + _ai->_vm->_rnd.getRandomNumber(179) - 90;
int randDist = _ai->_vm->_rnd.getRandomNumber(109) + 40;
- int x = targetX + randDist * cos(_ai->degToRad(randAngle));
- int y = targetY + randDist * sin(_ai->degToRad(randAngle));
+ int x = (int)(targetX + randDist * cos(_ai->degToRad(randAngle)));
+ int y = (int)(targetY + randDist * sin(_ai->degToRad(randAngle)));
int powAngle = _ai->getPowerAngleFromPoint(hubX, hubY, x, y, 20);
@@ -496,8 +506,8 @@ int Defender::calculateDefenseUnitPosition(int targetX, int targetY, int index)
int xDist = xCoord - x;
int yDist = yCoord - y;
- x = xCoord + (terrainSquareSize * 1.414 * (xDist / (abs(xDist) + 1)));
- y = yCoord + (terrainSquareSize * 1.414 * (yDist / (abs(yDist) + 1)));
+ x = (int)(xCoord + (terrainSquareSize * 1.414 * (xDist / (abs(xDist) + 1))));
+ y = (int)(yCoord + (terrainSquareSize * 1.414 * (yDist / (abs(yDist) + 1))));
setTargetX(x);
setTargetY(y);
diff --git a/engines/scumm/he/moonbase/ai_targetacquisition.h b/engines/scumm/he/moonbase/ai_targetacquisition.h
index 9afe0f50ab..5e6cfed8bc 100644
--- a/engines/scumm/he/moonbase/ai_targetacquisition.h
+++ b/engines/scumm/he/moonbase/ai_targetacquisition.h
@@ -112,7 +112,7 @@ private:
AI *_ai;
public:
- Defender(AI *ai) : _ai(ai) {}
+ Defender(AI *ai);
void setSourceX(int sourceX) { _sourceX = sourceX; }
void setSourceY(int sourceY) { _sourceY = sourceY; }
void setTargetX(int targetX) { _targetX = targetX; }
diff --git a/engines/scumm/he/moonbase/ai_traveller.cpp b/engines/scumm/he/moonbase/ai_traveller.cpp
index 8294ea32b5..d6eea67b41 100644
--- a/engines/scumm/he/moonbase/ai_traveller.cpp
+++ b/engines/scumm/he/moonbase/ai_traveller.cpp
@@ -46,6 +46,8 @@ Traveller::Traveller(AI *ai) : _ai(ai) {
_waterSourceY = 0;
_waterDestX = 0;
_waterDestY = 0;
+
+ _posX = _posY = 0;
}
Traveller::Traveller(int originX, int originY, AI *ai) : _ai(ai) {
@@ -143,10 +145,10 @@ IContainedObject *Traveller::createChildObj(int index, int &completionFlag) {
if (directDist > _maxDist + 120)
maxPower = _ai->getMaxPower();
else
- maxPower = (static_cast<float>(directDist) / static_cast<float>(_maxDist + 120)) * _ai->getMaxPower();
+ maxPower = (int)((static_cast<float>(directDist) / static_cast<float>(_maxDist + 120)) * _ai->getMaxPower());
maxPower -= 70;
- power = maxPower * (1 - ((index % NUM_POWER_STEPS) * SIZE_POWER_STEP));
+ power = (int)(maxPower * (1 - ((index % NUM_POWER_STEPS) * SIZE_POWER_STEP)));
}
retTraveller->setAngleTo(angle);
@@ -188,8 +190,8 @@ IContainedObject *Traveller::createChildObj(int index, int &completionFlag) {
float pwr = _ai->getMinPower() * .3;
float cosine = cos((static_cast<float>(angle) / 360) * (2 * M_PI));
float sine = sin((static_cast<float>(angle) / 360) * (2 * M_PI));
- int xParam = xCoord + (pwr * cosine);
- int yParam = yCoord + (pwr * sine);
+ int xParam = (int)(xCoord + (pwr * cosine));
+ int yParam = (int)(yCoord + (pwr * sine));
if (xParam < 0)
xParam += _ai->getMaxX();
@@ -233,8 +235,8 @@ IContainedObject *Traveller::createChildObj(int index, int &completionFlag) {
int xDist = xCoord - _posX;
int yDist = yCoord - _posY;
- retTraveller->setPosX(xCoord + (terrainSquareSize * 1.414 * (xDist / (abs(xDist) + 1))));
- retTraveller->setPosY(yCoord + (terrainSquareSize * 1.414 * (yDist / (abs(yDist) + 1))));
+ retTraveller->setPosX((int)(xCoord + (terrainSquareSize * 1.414 * (xDist / (abs(xDist) + 1)))));
+ retTraveller->setPosY((int)(yCoord + (terrainSquareSize * 1.414 * (yDist / (abs(yDist) + 1)))));
int closestHub = _ai->getClosestUnit(retTraveller->getPosX(), retTraveller->getPosY(), _ai->getMaxX(), _ai->getCurrentPlayer(), 1, BUILDING_MAIN_BASE, 1, 110);
diff --git a/engines/scumm/he/moonbase/ai_tree.cpp b/engines/scumm/he/moonbase/ai_tree.cpp
index e3098a7b24..d18536812b 100644
--- a/engines/scumm/he/moonbase/ai_tree.cpp
+++ b/engines/scumm/he/moonbase/ai_tree.cpp
@@ -29,7 +29,12 @@
namespace Scumm {
static int compareTreeNodes(const void *a, const void *b) {
- return ((const TreeNode *)a)->value - ((const TreeNode *)b)->value;
+ if (((const TreeNode *)a)->value < ((const TreeNode *)b)->value)
+ return -1;
+ else if (((const TreeNode *)a)->value > ((const TreeNode *)b)->value)
+ return 1;
+ else
+ return 0;
}
Tree::Tree(AI *ai) : _ai(ai) {
diff --git a/engines/scumm/he/moonbase/ai_types.h b/engines/scumm/he/moonbase/ai_types.h
index e2de87d653..bb16a737e9 100644
--- a/engines/scumm/he/moonbase/ai_types.h
+++ b/engines/scumm/he/moonbase/ai_types.h
@@ -72,7 +72,7 @@ public:
AIEntity(int id);
~AIEntity() {
if (_nameString) {
- delete _nameString;
+ delete[] _nameString;
_nameString = 0;
}
}
diff --git a/engines/scumm/he/moonbase/distortion.cpp b/engines/scumm/he/moonbase/distortion.cpp
new file mode 100644
index 0000000000..6b637d0565
--- /dev/null
+++ b/engines/scumm/he/moonbase/distortion.cpp
@@ -0,0 +1,218 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "scumm/he/intern_he.h"
+#include "engines/scumm/he/moonbase/moonbase.h"
+
+namespace Scumm {
+
+enum {
+ kBptHeaderSize = 8,
+
+ kReflectionClipped = 0,
+ kNotClipped = 1,
+ kSpecializedNotClipped = 2
+};
+
+static void blitDistortionCore(
+ Graphics::Surface *dstBitmap,
+ const int x, const int y,
+ const Graphics::Surface *distortionBitmap,
+ const Common::Rect *optionalclipRectPtr,
+ int transferOp,
+ const Graphics::Surface *srcBitmap,
+ Common::Rect *srcClipRect
+) {
+ Common::Rect clipRect(dstBitmap->w, dstBitmap->h);
+
+ if (optionalclipRectPtr)
+ if (!clipRect.intersects(*optionalclipRectPtr))
+ return;
+
+ clipRect.clip(*optionalclipRectPtr);
+
+ Common::Rect distortionRect(distortionBitmap->w, distortionBitmap->h);
+ Common::Rect dstRect(x, y, x + distortionRect.width(), y + distortionRect.height());
+
+ if (!dstRect.intersects(clipRect))
+ return;
+
+ dstRect.clip(clipRect);
+
+ distortionRect.moveTo(dstRect.left - x, dstRect.top - y);
+
+ const byte *distortionPtr = (const byte *)distortionBitmap->getBasePtr(distortionRect.left, distortionRect.top);
+ byte *dstPtr = (byte *)dstBitmap->getBasePtr(dstRect.left, dstRect.top);
+ int cw = dstRect.width();
+ int ch = dstRect.height();
+ int idx = dstRect.left;
+ int dy = dstRect.top;
+
+ int baseX, baseY;
+ const byte *srcData = (const byte *)srcBitmap->getBasePtr(0, 0);
+ int srcPitch = srcBitmap->pitch;
+
+ switch (transferOp) {
+ case kReflectionClipped:
+ case kNotClipped:
+ baseX = -(0x1f / 2); // Half range
+ baseY = -(0x1f / 2);
+ break;
+
+ case kSpecializedNotClipped:
+ default:
+ baseX = 0;
+ baseY = 0;
+ }
+
+ while (--ch >= 0) {
+ uint16 *d = (uint16 *)dstPtr;
+ const uint16 *is = (const uint16 *)distortionPtr;
+ int dx = idx;
+
+ for (int i = cw; --i >= 0;) {
+ uint16 p = READ_LE_UINT16(is);
+ int sx = baseX + dx + ((p >> 5) & 0x1f); // G color
+ int sy = baseY + dy + (p & 0x1f); // B color;
+
+ if (transferOp == kReflectionClipped) {
+ if (sx < srcClipRect->left)
+ sx -= (srcClipRect->left - sx);
+
+ if (sx > srcClipRect->right)
+ sx -= (sx - srcClipRect->right);
+
+ sx = MAX<int>(srcClipRect->left, MIN<int>(sx, srcClipRect->right));
+
+ if (sy < srcClipRect->top)
+ sy -= (srcClipRect->top - sy);
+
+ if (sy > srcClipRect->bottom)
+ sy -= (sy - srcClipRect->bottom);
+
+ sy = MAX<int>(srcClipRect->top, MIN<int>(sy, srcClipRect->bottom));
+ }
+
+ *d = *((const uint16 *)(srcData + sy * srcPitch + sx * 2));
+
+ ++d;
+ ++is;
+ ++dx;
+ }
+
+ dstPtr += dstBitmap->pitch;
+ distortionPtr += distortionBitmap->pitch;
+
+ ++dy;
+ }
+}
+
+void Moonbase::blitDistortion(byte *bufferData, const int bufferWidth, const int bufferHeight, const int bufferPitch,
+ const Common::Rect *optionalClippingRect, byte *dataStream, const int x, const int y, byte *altSourceBuffer) {
+ byte *sourcePixels = (altSourceBuffer) ? altSourceBuffer : bufferData;
+ Common::Rect dstLimitsRect(bufferWidth, bufferHeight);
+ Common::Rect clippedDstRect = dstLimitsRect;
+
+ if (optionalClippingRect) {
+ if (!dstLimitsRect.intersects(*optionalClippingRect))
+ return;
+ dstLimitsRect.clip(*optionalClippingRect);
+ } else {
+ clippedDstRect = dstLimitsRect;
+ }
+
+ int w = READ_LE_UINT16(dataStream + kBptHeaderSize + 0);
+ int h = READ_LE_UINT16(dataStream + kBptHeaderSize + 2);
+ Common::Rect srcLimitsRect(w, h);
+ Common::Rect clippedSrcRect = srcLimitsRect;
+ Common::Rect dstOperation(x, y, x + clippedSrcRect.width(), y + clippedSrcRect.height());
+
+ if (!clippedDstRect.intersects(dstOperation))
+ return;
+
+ clippedDstRect.clip(dstOperation);
+
+ int subBlockCount = READ_LE_UINT16(dataStream + kBptHeaderSize + 4);
+ byte *subBlockStream = dataStream + kBptHeaderSize + READ_LE_UINT32(dataStream + 4);
+ int cx1 = clippedDstRect.left;
+ int cy1 = clippedDstRect.top;
+ int cx2 = clippedDstRect.right - 1;
+ int cy2 = clippedDstRect.bottom - 1;
+
+ for (int i = 0; i < subBlockCount; i++) {
+ byte *blockData = subBlockStream;
+ uint32 blockSize = READ_LE_UINT32(blockData); blockData += 4;
+ subBlockStream += blockSize;
+ int xOffset = READ_LE_UINT16(blockData); blockData += 2;
+ int yOffset = READ_LE_UINT16(blockData); blockData += 2;
+ int width = READ_LE_UINT16(blockData); blockData += 2;
+ int height = READ_LE_UINT16(blockData); blockData += 2;
+ int l_reach = READ_LE_UINT16(blockData); blockData += 2;
+ int r_reach = READ_LE_UINT16(blockData); blockData += 2;
+ int t_reach = READ_LE_UINT16(blockData); blockData += 2;
+ int b_reach = READ_LE_UINT16(blockData); blockData += 2;
+ int distortionPitch = ((width * 2 + 7) / 8); // 2 for 555
+
+ if (width == 0 && height == 0)
+ continue;
+
+ Graphics::Surface dstBitmap;
+ dstBitmap.init(bufferWidth, bufferHeight, bufferPitch, bufferData, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
+
+ Graphics::Surface srcBitmap;
+ srcBitmap.init(bufferWidth, bufferHeight, bufferPitch, sourcePixels, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
+
+ Graphics::Surface distortionBitmap;
+ distortionBitmap.init(width, height, distortionPitch, blockData, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
+
+ Common::Rect srcClipRect(cx1, cy1, cx2, cy2);
+ Common::Rect dstClipRect(cx1, cy1, cx2, cy2);
+
+ int src_x = (x + xOffset);
+ int src_y = (y + yOffset);
+
+ Common::Rect srcReach((src_x - l_reach), (src_y - t_reach), (src_x + r_reach), (src_y + b_reach));
+ Common::Rect srcLimits(srcBitmap.w, srcBitmap.h);
+
+ if (!srcLimits.intersects(srcClipRect))
+ return;
+
+ srcLimits.clip(srcClipRect);
+
+ if (!srcReach.intersects(srcLimits))
+ return;
+
+ srcReach.clip(srcLimits);
+
+ if (srcLimits.contains(srcReach)) {
+ if (srcBitmap.pitch == 1280) {
+ blitDistortionCore(&dstBitmap, src_x, src_y, &distortionBitmap, &dstClipRect, kSpecializedNotClipped, &srcBitmap, 0);
+ } else {
+ blitDistortionCore(&dstBitmap, src_x, src_y, &distortionBitmap, &dstClipRect, kNotClipped, &srcBitmap, 0);
+ }
+ } else {
+ blitDistortionCore(&dstBitmap, src_x, src_y, &distortionBitmap, &dstClipRect, kReflectionClipped, &srcBitmap, &srcLimits);
+ }
+ }
+}
+
+}
diff --git a/engines/scumm/he/moonbase/moonbase.h b/engines/scumm/he/moonbase/moonbase.h
index 243d53a11d..71c03cb007 100644
--- a/engines/scumm/he/moonbase/moonbase.h
+++ b/engines/scumm/he/moonbase/moonbase.h
@@ -42,6 +42,8 @@ public:
void blitT14WizImage(uint8 *dst, int dstw, int dsth, int dstPitch, const Common::Rect *clipBox,
uint8 *wizd, int srcx, int srcy, int rawROP, int paramROP);
+ void blitDistortion(byte *bufferData, const int bufferWidth, const int bufferHeight, const int bufferPitch,
+ const Common::Rect *optionalClippingRect, byte *dataStream, const int x, const int y, byte *altSourceBuffer);
// FOW Stuff
bool isFOW(int resNum, int state, uint32 conditionBits) {
@@ -98,7 +100,7 @@ private:
bool _fowBlackMode;
- int _fowRenderTable[32768];
+ int32 _fowRenderTable[32768];
Common::PEResources _exe;
Common::String _fileName;
diff --git a/engines/scumm/he/moonbase/moonbase_fow.cpp b/engines/scumm/he/moonbase/moonbase_fow.cpp
index 48c2219926..0837d9eea3 100644
--- a/engines/scumm/he/moonbase/moonbase_fow.cpp
+++ b/engines/scumm/he/moonbase/moonbase_fow.cpp
@@ -59,7 +59,7 @@ void Moonbase::initFOW() {
_fowBlackMode = true;
- memset(_fowRenderTable, 0, 32768);
+ memset(_fowRenderTable, 0, sizeof(_fowRenderTable));
}
void Moonbase::releaseFOWResources() {
@@ -114,8 +114,14 @@ bool Moonbase::setFOWImage(int image) {
delete stream;
}
- if (!_fowImage && image > 0)
- _fowImage = _vm->getResourceAddress(rtImage, image);
+ if (!_fowImage && image > 0) {
+ int sz = _vm->getResourceSize(rtImage, image);
+ _fowImage = (uint8 *)malloc(sz);
+
+ // We have to copy it, otherwise the resource manager
+ // will kill it earlier or later. Matches original.
+ memcpy(_fowImage, _vm->getResourceAddress(rtImage, image), sz);
+ }
if (!_fowImage)
return false;
@@ -158,7 +164,7 @@ enum FOWElement {
};
int Moonbase::readFOWVisibilityArray(int array, int y, int x) {
- if (readFromArray(array, y, x) > 0)
+ if (readFromArray(array, x, y) > 0)
return FOW_EMPTY;
return FOW_SOLID;
@@ -169,6 +175,8 @@ void Moonbase::setFOWInfo(int fowInfoArray, int downDim, int acrossDim, int view
if (!_fowImage)
return;
+ memset(_fowRenderTable, 0, sizeof(_fowRenderTable));
+
_fowDrawX = clipX1;
_fowDrawY = clipY1;
@@ -210,16 +218,16 @@ void Moonbase::setFOWInfo(int fowInfoArray, int downDim, int acrossDim, int view
int dataOffset = (_fowVw * 3);
int dataOffset2 = (dataOffset * 2);
- int *pOutterRenderTableA = _fowRenderTable;
- int *pOutterRenderTableB = pOutterRenderTableA + dataOffset;
+ int32 *pOutterRenderTableA = _fowRenderTable;
+ int32 *pOutterRenderTableB = pOutterRenderTableA + dataOffset;
for (int ay = 0; ay < _fowVh; ay++) {
int l = il;
int c = ic;
int r = ir;
- int *pRenderTableA = pOutterRenderTableA;
- int *pRenderTableB = pOutterRenderTableB;
+ int32 *pRenderTableA = pOutterRenderTableA;
+ int32 *pRenderTableB = pOutterRenderTableB;
pOutterRenderTableA += dataOffset2;
pOutterRenderTableB += dataOffset2;
@@ -228,7 +236,7 @@ void Moonbase::setFOWInfo(int fowInfoArray, int downDim, int acrossDim, int view
int visibility = readFOWVisibilityArray(fowInfoArray, m, c);
if (visibility == FOW_EMPTY) {
- int bits = 0;
+ uint32 bits = 0;
if (readFOWVisibilityArray(fowInfoArray, t, l) != 0) bits |= FF_T_L;
if (readFOWVisibilityArray(fowInfoArray, t, c) != 0) bits |= FF_T;
@@ -333,7 +341,7 @@ static void blackRect_16bpp(uint8 *destSurface, int dstPitch, int dstw, int dsth
int h = y2 - y1;
int w = ((x2 - x1) + 1) * 2;
- while ( --h >= 0 ) {
+ while (--h >= 0) {
memset(dst, 0, w);
dst += dstPitch;
}
@@ -343,7 +351,7 @@ void Moonbase::renderFOW(uint8 *destSurface, int dstPitch, int dstType, int dstw
if (!_fowImage)
return;
- const int *pOutterRenderTable = _fowRenderTable;
+ const int32 *pOutterRenderTable = _fowRenderTable;
int ixPos = ((_fowVtx1 * _fowTileW) - _fowMvx) + _fowDrawX;
int yPos = ((_fowVty1 * _fowTileH) - _fowMvy) + _fowDrawY;
int dataOffset = _fowVw * 3;
@@ -355,7 +363,7 @@ void Moonbase::renderFOW(uint8 *destSurface, int dstPitch, int dstType, int dstw
int real_yPos = yPos;
for (int i = 0; i < 2; i++) {
- const int *pRenderTable = pOutterRenderTable;
+ const int32 *pRenderTable = pOutterRenderTable;
pOutterRenderTable += dataOffset;
int xPos = ixPos;
diff --git a/engines/scumm/he/script_v100he.cpp b/engines/scumm/he/script_v100he.cpp
index 2e7b4c4bf5..714f431188 100644
--- a/engines/scumm/he/script_v100he.cpp
+++ b/engines/scumm/he/script_v100he.cpp
@@ -184,7 +184,7 @@ void ScummEngine_v100he::setupOpcodes() {
OPCODE(0x74, o6_delay);
OPCODE(0x75, o6_delayMinutes);
OPCODE(0x76, o6_delaySeconds);
- OPCODE(0x77, o100_startSound);
+ OPCODE(0x77, o100_soundOps);
/* 78 */
OPCODE(0x78, o80_sourceDebug);
OPCODE(0x79, o100_setSpriteInfo);
@@ -643,6 +643,11 @@ void ScummEngine_v100he::o100_arrayOps() {
dim2end = pop();
dim2start = pop();
+ debug(0, "Complex: %d = %d[%d to %d][%d to %d] %c %d[%d to %d][%d to %d]", array,
+ array1, a1_dim1start, a1_dim2end, a1_dim1start, a1_dim2end,
+ " +-&|^"[type],
+ array2, a2_dim1start, a2_dim2end, a2_dim1start, a2_dim2end);
+
int a12_num = a1_dim2end - a1_dim2start + 1;
int a11_num = a1_dim1end - a1_dim1start + 1;
int a22_num = a2_dim2end - a2_dim2start + 1;
@@ -689,8 +694,6 @@ void ScummEngine_v100he::o100_arrayOps() {
writeArray(array, dim2start, dim1, res);
}
}
-
- warning("STUB: o100_arrayOps: case 132 type %d", type);
break;
}
case 133: // SO_RANGE_ARRAY_ASSIGNMENT
@@ -1739,69 +1742,69 @@ void ScummEngine_v100he::o100_setSystemMessage() {
}
}
-void ScummEngine_v100he::o100_startSound() {
+void ScummEngine_v100he::o100_soundOps() {
byte filename[260];
int var, value;
byte subOp = fetchScriptByte();
switch (subOp) {
- case 6:
- _heSndFlags |= 16;
+ case 6: // SO_AT
+ _heSndFlags |= HE_SND_OFFSET;
_heSndOffset = pop();
break;
- case 47:
+ case 47: // SO_LOAD
copyScriptString(filename, sizeof(filename));
_heSndSoundId = pop();
if (_heSndSoundId)
debug(0, "Load sound %d from file %s\n", _heSndSoundId, filename);
break;
- case 55:
- _heSndFlags |= 8;
+ case 55: // SO_NOW
+ _heSndFlags |= HE_SND_QUICK_START;
break;
- case 83:
+ case 83: // SO_VARIABLE
value = pop();
var = pop();
_heSndSoundId = pop();
((SoundHE *)_sound)->setSoundVar(_heSndSoundId, var, value);
break;
- case 92:
- _sound->addSoundToQueue(_heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags);
+ case 92: // SO_END
+ _sound->addSoundToQueue(_heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags, _heSndSoundFreq, _heSndPan, _heSndVol);
break;
- case 128:
- _heSndFlags |= 2;
+ case 128: // SO_SOUND_ADD
+ _heSndFlags |= HE_SND_APPEND;
break;
- case 129:
+ case 129: // SO_SOUND_CHANNEL
_heSndChannel = pop();
break;
- case 130:
- _heSndFlags |= 64;
- pop();
+ case 130: // SO_SOUND_FREQUENCY
+ _heSndFlags |= HE_SND_FREQUENCY;
+ _heSndSoundFreq = pop();
break;
- case 131:
- _heSndFlags |= 1;
+ case 131: // SO_SOUND_LOOPING
+ _heSndFlags |= HE_SND_LOOP;
break;
- case 132: // Music
- case 134: // Sound
+ case 132: // SO_SOUND_MODIFY
+ case 134: // SO_SOUND_START
_heSndSoundId = pop();
_heSndOffset = 0;
_heSndSoundFreq = 11025;
_heSndChannel = VAR(VAR_SOUND_CHANNEL);
_heSndFlags = 0;
break;
- case 133:
- _heSndFlags |= 128;
- pop();
+ case 133: // SO_SOUND_PAN
+ _heSndFlags |= HE_SND_PAN;
+ _heSndPan = pop();
break;
- case 135:
- _heSndFlags |= 4;
+ case 135: // SO_SOUND_SOFT
+ _heSndFlags |= HE_SND_SOFT_SOUND;
break;
- case 136:
- _heSndFlags |= 32;
- pop();
+ case 136: // SO_SOUND_VOLUME
+ _heSndFlags |= HE_SND_VOL;
+ _heSndVol = pop();
break;
default:
- error("o100_startSound invalid case %d", subOp);
+ error("o100_soundOps invalid case %d", subOp);
}
}
@@ -2572,62 +2575,62 @@ void ScummEngine_v100he::o100_getWizData() {
byte subOp = fetchScriptByte();
switch (subOp) {
- case 20:
+ case 20: // SO_COLOR
y = pop();
x = pop();
state = pop();
resId = pop();
push(_wiz->getWizPixelColor(resId, state, x, y));
break;
- case 26:
+ case 26: // SO_COUNT
resId = pop();
push(_wiz->getWizImageStates(resId));
break;
- case 33:
+ case 33: // SO_FIND
y = pop();
x = pop();
state = pop();
resId = pop();
push(_wiz->isWizPixelNonTransparent(resId, state, x, y, 0));
break;
- case 39:
+ case 39: // SO_HEIGHT
state = pop();
resId = pop();
_wiz->getWizImageDim(resId, state, w, h);
push(h);
break;
- case 54:
+ case 54: // SO_NEW_GENERAL_PROPERTY
type = pop();
state = pop();
resId = pop();
push(_wiz->getWizImageData(resId, state, type));
break;
- case 84:
+ case 84: // SO_WIDTH
state = pop();
resId = pop();
_wiz->getWizImageDim(resId, state, w, h);
push(w);
break;
- case 85:
+ case 85: // SO_XPOS
state = pop();
resId = pop();
_wiz->getWizImageSpot(resId, state, x, y);
push(x);
break;
- case 86:
+ case 86: // SO_YPOS
state = pop();
resId = pop();
_wiz->getWizImageSpot(resId, state, x, y);
push(y);
break;
- case 131:
+ case 131: // SO_FONT_START
pop();
copyScriptString(filename, sizeof(filename));
pop();
push(0);
debug(0, "o100_getWizData() case 111 unhandled");
break;
- case 132:
+ case 132: // SO_HISTOGRAM
h = pop();
w = pop();
y = pop();
diff --git a/engines/scumm/he/script_v70he.cpp b/engines/scumm/he/script_v70he.cpp
index b91943c685..0bdeb3211e 100644
--- a/engines/scumm/he/script_v70he.cpp
+++ b/engines/scumm/he/script_v70he.cpp
@@ -39,7 +39,7 @@ namespace Scumm {
void ScummEngine_v70he::setupOpcodes() {
ScummEngine_v60he::setupOpcodes();
- OPCODE(0x74, o70_startSound);
+ OPCODE(0x74, o70_soundOps);
OPCODE(0x84, o70_pickupObject);
OPCODE(0x8c, o70_getActorRoom);
OPCODE(0x9b, o70_resourceRoutines);
@@ -52,60 +52,60 @@ void ScummEngine_v70he::setupOpcodes() {
OPCODE(0xfa, o70_setSystemMessage);
}
-void ScummEngine_v70he::o70_startSound() {
+void ScummEngine_v70he::o70_soundOps() {
int var, value;
byte subOp = fetchScriptByte();
switch (subOp) {
- case 9:
- _heSndFlags |= 4;
+ case 9: // SO_SOUND_SOFT?
+ _heSndFlags |= HE_SND_SOFT_SOUND;
break;
- case 23:
+ case 23: // SO_VARIABLE
value = pop();
var = pop();
_heSndSoundId = pop();
((SoundHE *)_sound)->setSoundVar(_heSndSoundId, var, value);
break;
- case 25:
+ case 25: // SO_SOUND_VOLUME
value = pop();
_heSndSoundId = pop();
- _sound->addSoundToQueue(_heSndSoundId, 0, 0, 8);
+ _sound->addSoundToQueue(_heSndSoundId, 0, 0, HE_SND_VOL, 0, 0, value);
break;
- case 56:
- _heSndFlags |= 16;
+ case 56: // SO_NOW
+ _heSndFlags |= HE_SND_QUICK_START;
break;
- case 164:
- _heSndFlags |= 2;
+ case 164: // SO_SOUND_ADD
+ _heSndFlags |= HE_SND_APPEND;
break;
case 222:
// WORKAROUND: For errors in room script 240 (room 4) of maze
break;
- case 224:
+ case 224: // SO_SOUND_FREQUENCY
_heSndSoundFreq = pop();
break;
- case 230:
+ case 230: // SO_SOUND_CHANNEL
_heSndChannel = pop();
break;
- case 231:
+ case 231: // SO_AT
_heSndOffset = pop();
break;
- case 232:
+ case 232: // SO_SOUND_START
_heSndSoundId = pop();
_heSndOffset = 0;
_heSndSoundFreq = 11025;
_heSndChannel = VAR(VAR_SOUND_CHANNEL);
break;
- case 245:
- _heSndFlags |= 1;
+ case 245: // SO_SOUND_LOOPING
+ _heSndFlags |= HE_SND_LOOP;
break;
- case 255:
- _sound->addSoundToQueue(_heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags);
+ case 255: // SO_END
+ _sound->addSoundToQueue(_heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags, _heSndSoundFreq);
_heSndFlags = 0;
break;
default:
- error("o70_startSound invalid case %d", subOp);
+ error("o70_soundOps invalid case %d", subOp);
}
}
diff --git a/engines/scumm/he/sound_he.cpp b/engines/scumm/he/sound_he.cpp
index 8670116c68..9da3641064 100644
--- a/engines/scumm/he/sound_he.cpp
+++ b/engines/scumm/he/sound_he.cpp
@@ -59,31 +59,29 @@ SoundHE::~SoundHE() {
delete[] _heSoundChannels;
}
-void SoundHE::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags) {
+void SoundHE::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {
if (_vm->VAR_LAST_SOUND != 0xFF)
_vm->VAR(_vm->VAR_LAST_SOUND) = sound;
- if ((_vm->_game.heversion <= 99 && (heFlags & 16)) || (_vm->_game.heversion >= 100 && (heFlags & 8))) {
- playHESound(sound, heOffset, heChannel, heFlags);
- return;
+ if (heFlags & 8) {
+ playHESound(sound, heOffset, heChannel, heFlags, heFreq, hePan, heVol);
} else {
-
- Sound::addSoundToQueue(sound, heOffset, heChannel, heFlags);
+ Sound::addSoundToQueue(sound, heOffset, heChannel, heFlags, heFreq, hePan, heVol);
}
}
-void SoundHE::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags) {
+void SoundHE::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {
int i = _soundQue2Pos;
while (i--) {
if (_soundQue2[i].sound == sound && !(heFlags & 2))
return;
}
- Sound::addSoundToQueue2(sound, heOffset, heChannel, heFlags);
+ Sound::addSoundToQueue2(sound, heOffset, heChannel, heFlags, heFreq, hePan, heVol);
}
void SoundHE::processSoundQueues() {
- int snd, heOffset, heChannel, heFlags;
+ int snd, heOffset, heChannel, heFlags, heFreq, hePan, heVol;
if (_vm->_game.heversion >= 72) {
for (int i = 0; i <_soundQue2Pos; i++) {
@@ -91,8 +89,11 @@ void SoundHE::processSoundQueues() {
heOffset = _soundQue2[i].offset;
heChannel = _soundQue2[i].channel;
heFlags = _soundQue2[i].flags;
+ heFreq = _soundQue2[_soundQue2Pos].freq;
+ hePan = _soundQue2[_soundQue2Pos].pan;
+ heVol = _soundQue2[_soundQue2Pos].vol;
if (snd)
- playHESound(snd, heOffset, heChannel, heFlags);
+ playHESound(snd, heOffset, heChannel, heFlags, heFreq, hePan, heVol);
}
_soundQue2Pos = 0;
} else {
@@ -102,8 +103,11 @@ void SoundHE::processSoundQueues() {
heOffset = _soundQue2[_soundQue2Pos].offset;
heChannel = _soundQue2[_soundQue2Pos].channel;
heFlags = _soundQue2[_soundQue2Pos].flags;
+ heFreq = _soundQue2[_soundQue2Pos].freq;
+ hePan = _soundQue2[_soundQue2Pos].pan;
+ heVol = _soundQue2[_soundQue2Pos].vol;
if (snd)
- playHESound(snd, heOffset, heChannel, heFlags);
+ playHESound(snd, heOffset, heChannel, heFlags, heFreq, hePan, heVol);
}
}
@@ -527,7 +531,7 @@ byte *findSoundTag(uint32 tag, byte *ptr) {
return NULL;
}
-void SoundHE::playHESound(int soundID, int heOffset, int heChannel, int heFlags) {
+void SoundHE::playHESound(int soundID, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {
Audio::RewindableAudioStream *stream = 0;
byte *ptr, *spoolPtr;
int size = -1;
diff --git a/engines/scumm/he/sound_he.h b/engines/scumm/he/sound_he.h
index e0324d0753..d5a2817a0f 100644
--- a/engines/scumm/he/sound_he.h
+++ b/engines/scumm/he/sound_he.h
@@ -61,8 +61,8 @@ public:
SoundHE(ScummEngine *parent, Audio::Mixer *mixer);
~SoundHE();
- virtual void addSoundToQueue(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0);
- virtual void addSoundToQueue2(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0);
+ virtual void addSoundToQueue(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0, int heFreq = 0, int hePan = 0, int heVol = 0);
+ virtual void addSoundToQueue2(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0, int heFreq = 0, int hePan = 0, int heVol = 0);
virtual int isSoundRunning(int sound) const;
virtual void stopSound(int sound);
@@ -75,7 +75,7 @@ public:
int getSoundPos(int sound);
int getSoundVar(int sound, int var);
void setSoundVar(int sound, int var, int val);
- void playHESound(int soundID, int heOffset, int heChannel, int heFlags);
+ void playHESound(int soundID, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol);
void processSoundCode();
void processSoundOpcodes(int sound, byte *codePtr, int *soundVars);
void setOverrideFreq(int freq);
diff --git a/engines/scumm/he/wiz_he.cpp b/engines/scumm/he/wiz_he.cpp
index 428960f673..9339318d19 100644
--- a/engines/scumm/he/wiz_he.cpp
+++ b/engines/scumm/he/wiz_he.cpp
@@ -1748,7 +1748,7 @@ void Wiz::copyCompositeWizImage(uint8 *dst, uint8 *wizPtr, uint8 *compositeInfoB
drawFlags = flags;
}
- uint srcw1, srch1;
+ uint srcw1 = 0, srch1 = 0;
if (drawFlags & (kWIFFlipX | kWIFFlipY)) {
uint8 *wizh = _vm->findWrappedBlock(MKTAG('W','I','Z','H'), wizPtr, subState, 0);
assert(wizh);
@@ -1816,11 +1816,10 @@ void Wiz::copy555WizImage(uint8 *dst, uint8 *wizd, int dstPitch, int dstType,
if (compID == 0x12340102) {
((ScummEngine_v100he *)_vm)->_moonbase->blitT14WizImage(dst, dstw, dsth, dstPitch, clipBox, wizd, srcx, srcy, rawROP, paramROP);
} else if (compID == 0x12340802) {
- warning("Distorion codec");
+ ((ScummEngine_v100he *)_vm)->_moonbase->blitDistortion(dst, dstw, dsth, dstPitch, clipBox, wizd, srcx, srcy, 0);
} else if (compID == 0x12340902) {
error("Unsupported Distortion");
}
-
}
#endif
@@ -2790,6 +2789,14 @@ int Wiz::isWizPixelNonTransparent(uint8 *data, int state, int x, int y, int flag
int c = READ_LE_UINT32(wizh + 0x0);
int w = READ_LE_UINT32(wizh + 0x4);
int h = READ_LE_UINT32(wizh + 0x8);
+
+ if (_vm->_game.id == GID_MOONBASE) {
+ uint16 color = 0xffff;
+ drawWizImageEx((byte *)&color, data, 0, 2, kDstMemory, 1, 1, -x, -y, w, h, state, 0, 0, 0, 0, 2, 0, 0);
+
+ return color != 0xffff;
+ }
+
uint8 *wizd = _vm->findWrappedBlock(MKTAG('W','I','Z','D'), data, state, 0);
assert(wizd);
if (x >= 0 && x < w && y >= 0 && y < h) {
@@ -2841,6 +2848,13 @@ uint16 Wiz::getWizPixelColor(int resNum, int state, int x, int y) {
int c = READ_LE_UINT32(wizh + 0x0);
int w = READ_LE_UINT32(wizh + 0x4);
int h = READ_LE_UINT32(wizh + 0x8);
+
+ if (_vm->_game.id == GID_MOONBASE) {
+ drawWizImageEx((byte *)&color, data, 0, 2, kDstMemory, 1, 1, -x, -y, w, h, state, 0, 0, 0, 0, 2, 0, 0);
+
+ return color;
+ }
+
uint8 *wizd = _vm->findWrappedBlock(MKTAG('W','I','Z','D'), data, state, 0);
assert(wizd);
switch (c) {
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 04611ba1b1..85ff1aa4cd 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -147,6 +147,7 @@ MODULE_OBJS += \
he/moonbase/ai_tree.o \
he/moonbase/ai_types.o \
he/moonbase/ai_weapon.o \
+ he/moonbase/distortion.o \
he/moonbase/moonbase.o \
he/moonbase/moonbase_fow.o
endif
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index d5727f2a7c..3b7dea194b 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -805,6 +805,8 @@ ScummEngine_v70he::ScummEngine_v70he(OSystem *syst, const DetectorResult &dr)
_heSndChannel = 0;
_heSndFlags = 0;
_heSndSoundFreq = 0;
+ _heSndPan = 0;
+ _heSndVol = 0;
_numStoredFlObjects = 0;
_storedFlObjects = (ObjectData *)calloc(100, sizeof(ObjectData));
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index 33b7c3108d..a62092f493 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -110,7 +110,7 @@ Sound::~Sound() {
delete _talkChannelHandle;
}
-void Sound::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags) {
+void Sound::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {
if (_vm->VAR_LAST_SOUND != 0xFF)
_vm->VAR(_vm->VAR_LAST_SOUND) = sound;
_lastSound = sound;
@@ -119,15 +119,18 @@ void Sound::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags)
if (sound <= _vm->_numSounds)
_vm->ensureResourceLoaded(rtSound, sound);
- addSoundToQueue2(sound, heOffset, heChannel, heFlags);
+ addSoundToQueue2(sound, heOffset, heChannel, heFlags, heFreq, hePan, heVol);
}
-void Sound::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags) {
+void Sound::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {
assert(_soundQue2Pos < ARRAYSIZE(_soundQue2));
_soundQue2[_soundQue2Pos].sound = sound;
_soundQue2[_soundQue2Pos].offset = heOffset;
_soundQue2[_soundQue2Pos].channel = heChannel;
_soundQue2[_soundQue2Pos].flags = heFlags;
+ _soundQue2[_soundQue2Pos].freq = heFreq;
+ _soundQue2[_soundQue2Pos].pan = hePan;
+ _soundQue2[_soundQue2Pos].vol = heVol;
_soundQue2Pos++;
}
@@ -806,6 +809,9 @@ void Sound::stopSound(int sound) {
_soundQue2[i].offset = 0;
_soundQue2[i].channel = 0;
_soundQue2[i].flags = 0;
+ _soundQue2[i].freq = 0;
+ _soundQue2[i].pan = 0;
+ _soundQue2[i].vol = 0;
}
}
}
diff --git a/engines/scumm/sound.h b/engines/scumm/sound.h
index 7fdb16371c..bc1e88f76b 100644
--- a/engines/scumm/sound.h
+++ b/engines/scumm/sound.h
@@ -67,6 +67,9 @@ protected:
int32 offset;
int16 channel;
int16 flags;
+ int16 freq;
+ int16 pan;
+ int16 vol;
} _soundQue2[10];
Common::String _sfxFilename;
@@ -101,8 +104,8 @@ public:
public:
Sound(ScummEngine *parent, Audio::Mixer *mixer);
virtual ~Sound();
- virtual void addSoundToQueue(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0);
- virtual void addSoundToQueue2(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0);
+ virtual void addSoundToQueue(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0, int heFreq = 0, int hePan = 0, int heVol = 0);
+ virtual void addSoundToQueue2(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0, int heFreq = 0, int hePan = 0, int heVol = 0);
void processSound();
void playSound(int soundID);
diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp
index c52c30191a..da4aec6994 100644
--- a/engines/sherlock/music.cpp
+++ b/engines/sherlock/music.cpp
@@ -226,6 +226,7 @@ Music::Music(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
_midiOption = false;
_midiMusicData = nullptr;
_musicVolume = ConfMan.hasKey("music_volume") ? ConfMan.getInt("music_volume") : 255;
+ _musicOn = false;
if (IS_3DO) {
// 3DO - uses digital samples for music
diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp
index 644c0c74c9..a05351b170 100644
--- a/engines/sherlock/objects.cpp
+++ b/engines/sherlock/objects.cpp
@@ -636,7 +636,9 @@ void Sprite::clear() {
_altImages = nullptr;
_altSeq = 0;
_centerWalk = 0;
- Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr);
+
+ for (int i = 0; i < 8; i++)
+ _stopFrames[i] = nullptr;
}
void Sprite::setImageFrame() {
diff --git a/engines/sherlock/saveload.cpp b/engines/sherlock/saveload.cpp
index f32552c7ea..45a4eedb10 100644
--- a/engines/sherlock/saveload.cpp
+++ b/engines/sherlock/saveload.cpp
@@ -89,7 +89,7 @@ SaveStateList SaveManager::getSavegameList(const Common::String &target) {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames;
Common::String saveDesc;
- Common::String pattern = Common::String::format("%s.0??", target.c_str());
+ Common::String pattern = Common::String::format("%s.0##", target.c_str());
SherlockSavegameHeader header;
filenames = saveFileMan->listSavefiles(pattern);
diff --git a/engines/sherlock/scalpel/scalpel_user_interface.h b/engines/sherlock/scalpel/scalpel_user_interface.h
index c0b8672a87..cc3aafff65 100644
--- a/engines/sherlock/scalpel/scalpel_user_interface.h
+++ b/engines/sherlock/scalpel/scalpel_user_interface.h
@@ -58,7 +58,7 @@ class Settings;
class ScalpelUserInterface: public UserInterface {
friend class Settings;
- friend class Talk;
+ friend class Sherlock::Talk;
private:
char _keyPress;
int _lookHelp;
diff --git a/engines/sherlock/surface.h b/engines/sherlock/surface.h
index 807fbeb1d1..401d9b648d 100644
--- a/engines/sherlock/surface.h
+++ b/engines/sherlock/surface.h
@@ -121,7 +121,7 @@ protected:
virtual void addDirtyRect(const Common::Rect &r) {}
public:
Surface() : BaseSurface() {}
- Surface(int w, int h) : BaseSurface(w, h) {}
+ Surface(int width, int height) : BaseSurface(width, height) {}
};
} // End of namespace Sherlock
diff --git a/engines/sword1/sound.cpp b/engines/sword1/sound.cpp
index a37f7d250d..720ed8afc8 100644
--- a/engines/sword1/sound.cpp
+++ b/engines/sword1/sound.cpp
@@ -124,22 +124,29 @@ void Sound::checkSpeechFileEndianness() {
uint32 index = _cowHeader[locIndex + (localNo * 2) - 1];
if (sampleSize) {
uint32 size;
+ bool leOk = false, beOk = false;
// Compute average of difference between two consecutive samples for both BE and LE
_bigEndianSpeech = false;
- int16 *data = uncompressSpeech(index + _cowHeaderSize, sampleSize, &size);
+ int16 *data = uncompressSpeech(index + _cowHeaderSize, sampleSize, &size, &leOk);
uint32 maxSamples = size > 2000 ? 2000 : size;
double le_diff = endiannessHeuristicValue(data, size, maxSamples);
delete[] data;
_bigEndianSpeech = true;
- data = uncompressSpeech(index + _cowHeaderSize, sampleSize, &size);
+ data = uncompressSpeech(index + _cowHeaderSize, sampleSize, &size, &beOk);
double be_diff = endiannessHeuristicValue(data, size, maxSamples);
delete [] data;
// Set the big endian flag
- _bigEndianSpeech = (be_diff < le_diff);
+ if (leOk && !beOk)
+ _bigEndianSpeech = false;
+ else if (beOk && !leOk)
+ _bigEndianSpeech = true;
+ else
+ _bigEndianSpeech = (be_diff < le_diff);
if (_bigEndianSpeech)
debug(6, "Mac version: using big endian speech file");
else
debug(6, "Mac version: using little endian speech file");
+ debug(8, "Speech decompression memory check: big endian = %s, little endian = %s", beOk ? "good" : "bad", leOk ? "good" : "bad");
debug(8, "Speech endianness heuristic: average = %f for BE and %f for LE (%d samples)", be_diff, le_diff, maxSamples);
}
}
@@ -445,7 +452,7 @@ bool Sound::startSpeech(uint16 roomNo, uint16 localNo) {
return false;
}
-int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size) {
+int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size, bool* ok) {
uint8 *fBuf = (uint8 *)malloc(cSize);
_cowFile.seek(index);
_cowFile.read(fBuf, cSize);
@@ -455,6 +462,8 @@ int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size) {
headerPos++;
if (headerPos < 100) {
+ if (ok != 0)
+ *ok = true;
int32 resSize;
int16 *srcData;
uint32 srcPos;
@@ -507,8 +516,11 @@ int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size) {
srcPos++;
if (length < 0) {
length = -length;
- if (length > samplesLeft)
+ if (length > samplesLeft) {
length = samplesLeft;
+ if (ok != 0)
+ *ok = false;
+ }
int16 value;
if (_bigEndianSpeech) {
value = (int16)SWAP_BYTES_16(*((uint16 *)(srcData + srcPos)));
@@ -519,8 +531,11 @@ int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size) {
dstData[dstPos++] = value;
srcPos++;
} else {
- if (length > samplesLeft)
+ if (length > samplesLeft) {
length = samplesLeft;
+ if (ok != 0)
+ *ok = false;
+ }
if (_bigEndianSpeech) {
for (uint16 cnt = 0; cnt < (uint16)length; cnt++)
dstData[dstPos++] = (int16)SWAP_BYTES_16(*((uint16 *)(srcData + (srcPos++))));
@@ -534,6 +549,8 @@ int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size) {
}
if (samplesLeft > 0) {
memset(dstData + dstPos, 0, samplesLeft * 2);
+ if (ok != 0)
+ *ok = false;
}
if (_cowMode == CowDemo) // demo has wave output size embedded in the compressed data
*(uint32 *)dstData = 0;
@@ -542,6 +559,8 @@ int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size) {
calcWaveVolume(dstData, resSize);
return dstData;
} else {
+ if (ok != 0)
+ *ok = false;
free(fBuf);
warning("Sound::uncompressSpeech(): DATA tag not found in wave header");
*size = 0;
diff --git a/engines/sword1/sound.h b/engines/sword1/sound.h
index e65e797934..18ee91dff2 100644
--- a/engines/sword1/sound.h
+++ b/engines/sword1/sound.h
@@ -110,7 +110,7 @@ private:
void initCowSystem();
uint32 getSampleId(int32 fxNo);
- int16 *uncompressSpeech(uint32 index, uint32 cSize, uint32 *size);
+ int16 *uncompressSpeech(uint32 index, uint32 cSize, uint32 *size, bool* ok = 0);
void calcWaveVolume(int16 *data, uint32 length);
bool _waveVolume[WAVE_VOL_TAB_LENGTH];
uint16 _waveVolPos;
diff --git a/engines/sword25/detection.cpp b/engines/sword25/detection.cpp
index 4ca565c972..c5f55b5a26 100644
--- a/engines/sword25/detection.cpp
+++ b/engines/sword25/detection.cpp
@@ -91,7 +91,7 @@ const ExtraGuiOptions Sword25MetaEngine::getExtraGuiOptions(const Common::String
SaveStateList Sword25MetaEngine::listSaves(const char *target) const {
Common::String pattern = target;
- pattern = pattern + ".???";
+ pattern = pattern + ".###";
SaveStateList saveList;
Sword25::PersistenceService ps;
diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp
index 32d5abb865..dc7ca67cfe 100644
--- a/engines/tinsel/music.cpp
+++ b/engines/tinsel/music.cpp
@@ -401,7 +401,7 @@ MidiMusicPlayer::MidiMusicPlayer(TinselEngine *vm) {
if (milesAudioEnabled) {
// Discworld 1 (DOS) uses Miles Audio 3
// use our own Miles Audio drivers
- //
+ //
// It seems that there are multiple versions of Discworld 1
//
// Version 1:
@@ -639,10 +639,14 @@ PCMMusicPlayer::PCMMusicPlayer() {
_dimmed = false;
_dimmedTinsel = false;
_dimIteration = 0;
+ _dimmedVolume = 0;
+ _dimPosition = 0;
_fadeOutVolume = 0;
_fadeOutIteration = 0;
+ _hScript = _hSegment = 0;
+
_end = true;
_vm->_mixer->playStream(Audio::Mixer::kMusicSoundType,
diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp
index 918894cf6f..495049d1a2 100644
--- a/engines/tinsel/palette.cpp
+++ b/engines/tinsel/palette.cpp
@@ -132,6 +132,8 @@ void PalettesToVideoDAC() {
VIDEO_DAC_Q *pDACtail = g_vidDACdata; // set tail pointer
byte pal[768];
+ memset(pal, 0, sizeof(pal));
+
// while Q is not empty
while (g_pDAChead != pDACtail) {
const PALETTE *pPalette; // pointer to hardware palette
diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp
index ef3127233d..a4f5bc8261 100644
--- a/engines/tinsel/play.cpp
+++ b/engines/tinsel/play.cpp
@@ -159,6 +159,9 @@ static int RegisterSoundReel(SCNHANDLE hFilm, int column, int actorCol) {
}
}
+ if (i == MAX_SOUNDREELS)
+ error("Out of sound reels in RegisterSoundReel()");
+
g_soundReelNumbers[i]++;
return i;
}
diff --git a/engines/tinsel/saveload.cpp b/engines/tinsel/saveload.cpp
index 226cbb51c0..c8fe5f898c 100644
--- a/engines/tinsel/saveload.cpp
+++ b/engines/tinsel/saveload.cpp
@@ -594,8 +594,8 @@ static void DoSave() {
hdr.id = SAVEGAME_ID;
hdr.size = SAVEGAME_HEADER_SIZE;
hdr.ver = CURRENT_VER;
- memcpy(hdr.desc, g_SaveSceneDesc, SG_DESC_LEN);
- hdr.desc[SG_DESC_LEN - 1] = 0;
+ memset(hdr.desc, 0, SG_DESC_LEN);
+ Common::strlcpy(hdr.desc, g_SaveSceneDesc, SG_DESC_LEN);
g_system->getTimeAndDate(hdr.dateTime);
hdr.scnFlag = _vm->getFeatures() & GF_SCNFILES;
hdr.language = _vm->_config->_language;
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index 1b733a08ba..44e81494f7 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -823,6 +823,9 @@ TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc)
_console(0), _sound(0), _midiMusic(0), _pcmMusic(0), _bmv(0) {
_vm = this;
+ _gameId = 0;
+ _driver = NULL;
+
_config = new Config(this);
// Register debug flags
diff --git a/engines/toltecs/animation.cpp b/engines/toltecs/animation.cpp
index 1f776bbc02..5278b25ba8 100644
--- a/engines/toltecs/animation.cpp
+++ b/engines/toltecs/animation.cpp
@@ -29,6 +29,18 @@ namespace Toltecs {
AnimationPlayer::AnimationPlayer(ToltecsEngine *vm) : _vm(vm) {
_animBuffer = new byte[262144];
+ memset(_animBuffer, 0, 262144);
+
+ _resIndex = 0;
+ _width = _height = 0;
+ _frameNumber = 0;
+ _frameCount = 0;
+ _keepFrameCounter = 0;
+ _curFrameSize = _nextFrameSize = 0;
+ _nextFrameOffset = 0;
+ _firstNextFrameSize = 0;
+ _firstNextFrameOffset = 0;
+ _firstCurFrameSize = 0;
}
AnimationPlayer::~AnimationPlayer() {
diff --git a/engines/toltecs/menu.cpp b/engines/toltecs/menu.cpp
index a58771433e..5fc0599c2a 100644
--- a/engines/toltecs/menu.cpp
+++ b/engines/toltecs/menu.cpp
@@ -37,6 +37,16 @@
namespace Toltecs {
MenuSystem::MenuSystem(ToltecsEngine *vm) : _vm(vm) {
+ _background = nullptr;
+ _running = false;
+ _currMenuID = _newMenuID = kMenuIdNone;
+ _currItemID = kItemIdNone;
+ _top = 0;
+ _savegameListTopIndex = 0;
+ _editingDescription = false;
+ _editingDescriptionID = kItemIdNone;
+ _editingDescriptionItem = nullptr;
+ _needRedraw = false;
}
MenuSystem::~MenuSystem() {
diff --git a/engines/toltecs/segmap.cpp b/engines/toltecs/segmap.cpp
index b27e0c8e11..d50e110485 100644
--- a/engines/toltecs/segmap.cpp
+++ b/engines/toltecs/segmap.cpp
@@ -27,6 +27,11 @@
namespace Toltecs {
SegmentMap::SegmentMap(ToltecsEngine *vm) : _vm(vm) {
+ _maskRectData = NULL;
+ memset(_deadEndPathRects, 0, sizeof(_closedPathRects));
+ _closedPathRectsCount = 0;
+ _deadEndPathRectsCount = 0;
+ _pathNodesCount = 0;
}
SegmentMap::~SegmentMap() {
diff --git a/engines/toltecs/segmap.h b/engines/toltecs/segmap.h
index c1ad293162..03bd858217 100644
--- a/engines/toltecs/segmap.h
+++ b/engines/toltecs/segmap.h
@@ -80,6 +80,8 @@ public: // for debugging purposes
struct PathPoint {
int16 y, x;
+
+ PathPoint() : x(0), y(0) {}
};
typedef Common::Array<SegmapMaskRect> SegmapMaskRectArray;
diff --git a/engines/tony/font.cpp b/engines/tony/font.cpp
index 850aff17be..fb40bf5c67 100644
--- a/engines/tony/font.cpp
+++ b/engines/tony/font.cpp
@@ -921,6 +921,12 @@ RMDialogChoice::RMDialogChoice() {
_curAdded = 0;
_bShow = false;
+
+ _curSelection = 0;
+ _numChoices = 0;
+
+ _drawedStrings = NULL;
+ _ptDrawStrings = NULL;
}
RMDialogChoice::~RMDialogChoice() {
diff --git a/engines/tony/game.cpp b/engines/tony/game.cpp
index 0a2c62330b..7b9cb4c6ca 100644
--- a/engines/tony/game.cpp
+++ b/engines/tony/game.cpp
@@ -319,6 +319,11 @@ RMOptionScreen::RMOptionScreen() {
_fadeTime = 0;
_nEditPos = 0;
_nLastState = MENUGAME;
+
+ _bExit = false;
+ _bLoadMenuOnly = false;
+ _bNoLoadSave = false;
+ _bAlterGfx = false;
}
RMOptionScreen::~RMOptionScreen() {
@@ -1145,7 +1150,7 @@ void RMOptionScreen::doFrame(CORO_PARAM, RMInput *input) {
// Turn on edit mode
_bEditSaveName = true;
_nEditPos = _ctx->i;
- strcpy(_editName, _curThumbName[_ctx->i].c_str());
+ Common::strlcpy(_editName, _curThumbName[_ctx->i].c_str(), sizeof(_editName));
_ctx->bRefresh = true;
}
diff --git a/engines/tony/gfxcore.h b/engines/tony/gfxcore.h
index c9081506d7..1a0738e5b7 100644
--- a/engines/tony/gfxcore.h
+++ b/engines/tony/gfxcore.h
@@ -442,6 +442,7 @@ private:
OTList();
OTList(RMGfxPrimitive *pr) {
_prim = pr;
+ _next = NULL;
}
};
diff --git a/engines/tony/mpal/mpalutils.cpp b/engines/tony/mpal/mpalutils.cpp
index 84c8a68919..d52e7332cc 100644
--- a/engines/tony/mpal/mpalutils.cpp
+++ b/engines/tony/mpal/mpalutils.cpp
@@ -37,6 +37,8 @@ namespace MPAL {
* @param resId MPAL resource to open
*/
RMRes::RMRes(uint32 resID) {
+ _buf = NULL;
+
_h = g_vm->_resUpdate.queryResource(resID);
if (_h == NULL)
_h = mpalQueryResource(resID);
diff --git a/engines/tony/sound.cpp b/engines/tony/sound.cpp
index aa86750ad5..fed51dacf4 100644
--- a/engines/tony/sound.cpp
+++ b/engines/tony/sound.cpp
@@ -161,6 +161,7 @@ FPSfx::FPSfx(bool soundOn) {
_loopStream = 0;
_rewindableStream = 0;
_paused = false;
+ _loop = 0;
g_vm->_activeSfx.push_back(this);
}
diff --git a/engines/tony/tony.cpp b/engines/tony/tony.cpp
index d8fa45cb5d..c51f449aa1 100644
--- a/engines/tony/tony.cpp
+++ b/engines/tony/tony.cpp
@@ -83,6 +83,8 @@ TonyEngine::TonyEngine(OSystem *syst, const TonyGameDescription *gameDesc) : Eng
_bTimeFreezed = false;
_nTimeFreezed = 0;
_vdbCodec = FPCODEC_UNKNOWN;
+
+ memset(_funcList, 0, sizeof(_funcList));
}
TonyEngine::~TonyEngine() {
diff --git a/engines/toon/toon.cpp b/engines/toon/toon.cpp
index 169e2eff7b..837339402c 100644
--- a/engines/toon/toon.cpp
+++ b/engines/toon/toon.cpp
@@ -708,7 +708,7 @@ bool ToonEngine::showOptions() {
entries[2].activeFrame = entries[2].animation->_numFrames - 1;
if (!_showConversationText) {
- entries[4].activeFrame = 4;
+ entries[4].activeFrame = 4;
} else if (_useAlternativeFont) {
entries[4].activeFrame = 8;
} else {
@@ -797,19 +797,19 @@ bool ToonEngine::showOptions() {
// handle sliders
if (clickingOn == OPTIONMENUHOTSPOT_VOLUMEMUSICSLIDER) {
entries[clickingOnSprite].activeFrame = ratioX * (entries[clickingOnSprite].animation->_numFrames) / 256;
- int vol = entries[clickingOnSprite].activeFrame * 256 / entries[clickingOnSprite].animation->_numFrames;
+ int vol = entries[clickingOnSprite].activeFrame * 256 / entries[clickingOnSprite].animation->_numFrames;
_audioManager->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, vol);
}
if (clickingOn == OPTIONMENUHOTSPOT_VOLUMEVOICESLIDER) {
entries[clickingOnSprite].activeFrame = ratioX * (entries[clickingOnSprite].animation->_numFrames) / 256;
- int vol = entries[clickingOnSprite].activeFrame * 256 / entries[clickingOnSprite].animation->_numFrames;
+ int vol = entries[clickingOnSprite].activeFrame * 256 / entries[clickingOnSprite].animation->_numFrames;
_audioManager->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, vol);
}
if (clickingOn == OPTIONMENUHOTSPOT_VOLUMESFXSLIDER) {
entries[clickingOnSprite].activeFrame = ratioX * (entries[clickingOnSprite].animation->_numFrames) / 256;
- int vol = entries[clickingOnSprite].activeFrame * 256 / entries[clickingOnSprite].animation->_numFrames;
+ int vol = entries[clickingOnSprite].activeFrame * 256 / entries[clickingOnSprite].animation->_numFrames;
_audioManager->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, vol);
}
@@ -935,10 +935,12 @@ bool ToonEngine::showOptions() {
_gameState->_inMenu = false;
_firstFrame = true;
_gameState->_currentScrollValue = oldScrollValue;
-
+
restorePalette();
dirtyAllScreen();
+ delete optionPicture;
+
return exitGame;
}
@@ -1296,6 +1298,7 @@ ToonEngine::ToonEngine(OSystem *syst, const ADGameDescription *gameDescription)
_scriptState[i].running = false;
}
_currentScriptRegion = 0;
+ _currentFont = nullptr;
}
ToonEngine::~ToonEngine() {
diff --git a/engines/tsage/detection.cpp b/engines/tsage/detection.cpp
index fe555f2fdb..584ad87742 100644
--- a/engines/tsage/detection.cpp
+++ b/engines/tsage/detection.cpp
@@ -83,7 +83,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "(c) Tsunami Media";
+ return "(C) Tsunami Media";
}
virtual bool hasFeature(MetaEngineFeature f) const {
diff --git a/engines/voyeur/detection.cpp b/engines/voyeur/detection.cpp
index 9e5320aac8..7b9fa6722e 100644
--- a/engines/voyeur/detection.cpp
+++ b/engines/voyeur/detection.cpp
@@ -75,7 +75,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "Voyeur (c) Philips P.O.V. Entertainment Group";
+ return "Voyeur (C) Philips P.O.V. Entertainment Group";
}
virtual bool hasFeature(MetaEngineFeature f) const;
diff --git a/engines/wage/design.cpp b/engines/wage/design.cpp
index eda28df159..f2a8aba812 100644
--- a/engines/wage/design.cpp
+++ b/engines/wage/design.cpp
@@ -422,7 +422,7 @@ void Design::drawBitmap(Graphics::ManagedSurface *surface, Common::SeekableReadS
int x2 = in.readSint16BE();
int w = x2 - x1;
int h = y2 - y1;
- Graphics::ManagedSurface tmp;
+ Graphics::Surface tmp;
tmp.create(w, h, Graphics::PixelFormat::createFormatCLUT8());
@@ -477,7 +477,7 @@ void Design::drawBitmap(Graphics::ManagedSurface *surface, Common::SeekableReadS
if (_boundsCalculationMode)
return;
- FloodFill ff(&tmp, kColorWhite, kColorGreen);
+ Graphics::FloodFill ff(&tmp, kColorWhite, kColorGreen);
for (int yy = 0; yy < h; yy++) {
ff.addSeed(0, yy);
ff.addSeed(w - 1, yy);
@@ -541,54 +541,4 @@ void Design::drawVLine(Graphics::ManagedSurface *surface, int x, int y1, int y2,
Graphics::drawVLine(x, y1, y2, color, drawPixel, &pd);
}
-FloodFill::FloodFill(Graphics::ManagedSurface *surface, byte color1, byte color2) {
- _surface = surface;
- _color1 = color1;
- _color2 = color2;
- _w = surface->w;
- _h = surface->h;
-
- _visited = (byte *)calloc(_w * _h, 1);
-}
-
-FloodFill::~FloodFill() {
- while(!_queue.empty()) {
- Common::Point *p = _queue.front();
-
- delete p;
- _queue.pop_front();
- }
-
- free(_visited);
-}
-
-void FloodFill::addSeed(int x, int y) {
- byte *p;
-
- if (x >= 0 && x < _w && y >= 0 && y < _h) {
- if (!_visited[y * _w + x] && *(p = (byte *)_surface->getBasePtr(x, y)) == _color1) {
- _visited[y * _w + x] = 1;
- *p = _color2;
-
- Common::Point *pt = new Common::Point(x, y);
-
- _queue.push_back(pt);
- }
- }
-}
-
-void FloodFill::fill() {
- while (!_queue.empty()) {
- Common::Point *p = _queue.front();
- _queue.pop_front();
- addSeed(p->x , p->y - 1);
- addSeed(p->x - 1, p->y );
- addSeed(p->x , p->y + 1);
- addSeed(p->x + 1, p->y );
-
- delete p;
- }
-}
-
-
} // End of namespace Wage
diff --git a/engines/wage/design.h b/engines/wage/design.h
index 9b0231ca96..86225c9224 100644
--- a/engines/wage/design.h
+++ b/engines/wage/design.h
@@ -100,21 +100,6 @@ private:
void drawBitmap(Graphics::ManagedSurface *surface, Common::SeekableReadStream &in);
};
-class FloodFill {
-public:
- FloodFill(Graphics::ManagedSurface *surface, byte color1, byte color2);
- ~FloodFill();
- void addSeed(int x, int y);
- void fill();
-
-private:
- Common::List<Common::Point *> _queue;
- Graphics::ManagedSurface *_surface;
- byte _color1, _color2;
- byte *_visited;
- int _w, _h;
-};
-
} // End of namespace Wage
#endif
diff --git a/engines/wage/detection.cpp b/engines/wage/detection.cpp
index 1b418b5aa8..8b8a6399a8 100644
--- a/engines/wage/detection.cpp
+++ b/engines/wage/detection.cpp
@@ -101,7 +101,7 @@ SaveStateList WageMetaEngine::listSaves(const char *target) const {
Common::StringArray filenames;
char saveDesc[31];
Common::String pattern = target;
- pattern += ".???";
+ pattern += ".###";
filenames = saveFileMan->listSavefiles(pattern);
diff --git a/engines/wintermute/detection.cpp b/engines/wintermute/detection.cpp
index f77eb5c64d..4e8eab505f 100644
--- a/engines/wintermute/detection.cpp
+++ b/engines/wintermute/detection.cpp
@@ -85,7 +85,7 @@ public:
}
virtual const char *getOriginalCopyright() const {
- return "Copyright (c) 2011 Jan Nedoma";
+ return "Copyright (C) 2011 Jan Nedoma";
}
virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const {