diff options
Diffstat (limited to 'engines')
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 §or) 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 §or) 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 ¤tPalette = 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 ¤tPalette = 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 ¤tPalette = 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 { |