/* 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 3 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, see . * */ #include "dgds/globals.h" #include "dgds/dgds.h" #include "dgds/scene.h" #include "dgds/game_palettes.h" #include "dgds/sound.h" #include "dgds/includes.h" namespace Dgds { typedef ReadOnlyGlobal ROI16Global; typedef ReadWriteGlobal RWI16Global; //////////////////////////////// // TODO: Move this to Scene?? class GameIsInteractiveGlobal : public Global { public: GameIsInteractiveGlobal(uint16 num, int16 *ptr) : Global(num), _ptr(ptr), _isSetOff(false) {} int16 get() override { SDSScene *scene = DgdsEngine::getInstance()->getScene(); bool nonInteractive = _isSetOff || scene->getDragItem() || scene->hasVisibleOrOpeningDialog(); *_ptr = !nonInteractive; return *_ptr; } int16 set(int16 val) override { _isSetOff = (val == 0); return get(); } void setRaw(int16 val) override { } private: int16 *_ptr; bool _isSetOff; }; Globals::Globals(Clock &clock) : _lastOpcode1SceneChangeNum(0), _sceneOp12SceneNum(0), _currentSelectedItem(0), _gameMinsToAddOnUse(0), _gameMinsToAddOnPickUp(0), _gameMinsToAddOnLook(0), _gameMinsToAddOnDrop(0), _gameMinsToAddOnObjInteraction(0), _gameIsInteractiveGlobal(0), _sceneOpcode15FromScene(0), _sceneOpcode15ToScene(0) { _globals.push_back(clock.getGameMinsAddedGlobal(1)); _globals.push_back(clock.getGameTicksUpGlobal(0x64)); _globals.push_back(clock.getGameTicksDownGlobal(0x63)); _globals.push_back(new ROI16Global(0x62, &_lastOpcode1SceneChangeNum)); _globals.push_back(new RWI16Global(0x61, &_sceneOp12SceneNum)); _globals.push_back(new RWI16Global(0x60, &_currentSelectedItem)); _globals.push_back(clock.getDaysGlobal(0x5F)); _globals.push_back(clock.getHoursGlobal(0x5E)); _globals.push_back(clock.getMinsGlobal(0x5D)); _globals.push_back(new RWI16Global(0x5C, &_gameMinsToAddOnUse)); _globals.push_back(new RWI16Global(0x5B, &_gameMinsToAddOnPickUp)); _globals.push_back(new RWI16Global(0x5A, &_gameMinsToAddOnLook)); _globals.push_back(new RWI16Global(0x59, &_gameMinsToAddOnDrop)); _globals.push_back(new RWI16Global(0x58, &_gameMinsToAddOnObjInteraction)); _globals.push_back(new GameIsInteractiveGlobal(0x57, &_gameIsInteractiveGlobal)); _globals.push_back(clock.getDays2Global(0x56)); _globals.push_back(new RWI16Global(0x55, &_sceneOpcode15FromScene)); _globals.push_back(new RWI16Global(0x54, &_sceneOpcode15ToScene)); } Globals::~Globals() { for (auto &g : _globals) delete g; } int16 Globals::getGlobal(uint16 num) { for (auto &global : _globals) { if (global->getNum() == num) return global->get(); } if (num == 333) { // Bug in HoC (scene 21)? warning("getGlobal: requested global 333"); return 0; } // This happens in a couple of places in RotD if (num) warning("getGlobal: requested non-existing global %d", num); // Bug in HoC? //warning("getGlobal: requested global 0"); return 0; } int16 Globals::setGlobal(uint16 num, int16 val) { //debug(1, "setGlobal %d -> %d", num, val); for (auto &global : _globals) { if (global->getNum() == num) return global->set(val); } // This happens eg looking at the Fisto box in RotD warning("setGlobal: requested non-existing global %d", num); return 0; } Common::Error Globals::syncState(Common::Serializer &s) { s.syncAsSint16LE(_lastOpcode1SceneChangeNum); s.syncAsSint16LE(_sceneOp12SceneNum); s.syncAsSint16LE(_currentSelectedItem); s.syncAsSint16LE(_gameMinsToAddOnUse); s.syncAsSint16LE(_gameMinsToAddOnPickUp); s.syncAsSint16LE(_gameMinsToAddOnLook); s.syncAsSint16LE(_gameMinsToAddOnDrop); s.syncAsSint16LE(_gameMinsToAddOnObjInteraction); s.syncAsSint16LE(_gameIsInteractiveGlobal); s.syncAsSint16LE(_sceneOpcode15FromScene); s.syncAsSint16LE(_sceneOpcode15ToScene); return Common::kNoError; } //////////////////////////////// class DetailLevelROGlobal : public Global { public: DetailLevelROGlobal(uint16 num) : Global(num) {} int16 get() override { return DgdsEngine::getInstance()->getDetailLevel(); } int16 set(int16 val) override { return DgdsEngine::getInstance()->getDetailLevel(); } void setRaw(int16 val) override { } }; //////////////////////////////// static byte dragonDataTableOffsets[] = {0x9, 0x29, 0xF, 0x15, 0x5C, 0x19, 0x28, 0x1F}; static byte dragonDataTable[] = { 0x4, 0x8, 0x16, 0xE, 0x8, 0xE, 0x17, 0x1C, 0x8, 0x2, 0x18, 0x8, 0x10, 0x10, 0x17, 0x15, 0x16, 0x18, 0x3, 0x20, 0x1E, 0x24, 0x32, 0x30, 0xE, 0x8, 0x20, 0x3, 0x13, 0xE, 0x14, 0x12, 0x8, 0x10, 0x1E, 0x13, 0x3, 0x8, 0x12, 0x22, 0xE, 0x10, 0x24, 0xE, 0x8, 0x3, 0xB, 0x20, 0x17, 0x17, 0x32, 0x14, 0x12, 0xB, 0x4, 0x24, 0x1C, 0x15, 0x30, 0x12, 0x22, 0x20, 0x24, 0x4 }; DragonDataTable::DragonDataTable() : _row(0), _col(0), _divBy4(0), _output(0) {} int DragonDataTable::getOffsetForVal(uint16 val) const { for (int i = 0; i < ARRAYSIZE(dragonDataTableOffsets); i++) { if (dragonDataTableOffsets[i] == val) return i; } return 0; } uint16 DragonDataTable::getValueFromTable() { int row = getOffsetForVal(_row); int col = getOffsetForVal(_col); _output = dragonDataTable[row * 8 + col]; if (_divBy4) _output /= 4; if (_output == 0) _output = 1; return _output; } class DragonDataTableGlobal : public Global { public: DragonDataTableGlobal(uint16 num, DragonDataTable &table) : Global(num), _table(table) {} int16 get() override { return _table.getValueFromTable(); } int16 set(int16 val) override { return _table.getValueFromTable(); } void setRaw(int16 val) override { } private: DragonDataTable &_table; }; //////////////////////////////// DragonGlobals::DragonGlobals(Clock &clock) : Globals(clock), _sceneOpcode100Var(0), _arcadeState(0), _opcode106EndMinutes(0) { _globals.push_back(new RWI16Global(0x20, &_sceneOpcode100Var)); _globals.push_back(new RWI16Global(0x21, &_arcadeState)); _globals.push_back(new RWI16Global(0x22, &_opcode106EndMinutes)); _globals.push_back(new RWI16Global(0x23, &_table._row)); _globals.push_back(new RWI16Global(0x24, &_table._col)); _globals.push_back(new RWI16Global(0x25, &_table._divBy4)); _globals.push_back(new DragonDataTableGlobal(0x26, _table)); _globals.push_back(new DetailLevelROGlobal(0x27)); } Common::Error DragonGlobals::syncState(Common::Serializer &s) { Globals::syncState(s); s.syncAsSint16LE(_sceneOpcode100Var); s.syncAsSint16LE(_arcadeState); s.syncAsSint16LE(_opcode106EndMinutes); s.syncAsSint16LE(_table._row); s.syncAsSint16LE(_table._col); s.syncAsSint16LE(_table._divBy4); s.syncAsSint16LE(_table._output); return Common::kNoError; } class HocCharacterGlobal : public RWI16Global { public: HocCharacterGlobal(uint16 num, int16 *val) : RWI16Global(num, val) {} int16 set(int16 val) override { DgdsEngine *engine = DgdsEngine::getInstance(); bool buttonVisible = engine->isInvButtonVisible(); if (buttonVisible) engine->getScene()->removeInvButtonFromHotAreaList(); RWI16Global::set(val); if (buttonVisible) engine->getScene()->addInvButtonToHotAreaList(); return get(); } }; class HocDifficultyGlobal : public Global { public: HocDifficultyGlobal(uint16 num) : Global(num) {} int16 get() override { return DgdsEngine::getInstance()->getDifficulty(); } int16 set(int16 val) override { DgdsEngine::getInstance()->setDifficulty(val); return DgdsEngine::getInstance()->getDetailLevel(); } void setRaw(int16 val) override { DgdsEngine::getInstance()->setDifficulty(val); } }; class HocPalFadeGlobal : public RWI16Global { public: HocPalFadeGlobal(uint16 num, int16 *val) : RWI16Global(num, val), _lastWas0x41(false) {} int16 set(int16 val) override { const int16 oldVal = RWI16Global::get(); if (val < 0x40) { if (oldVal == val) return val; const int step = (oldVal < val) ? 1 : -1; for (int16 fade = oldVal; val + step != fade; fade = fade + step) { int16 ncols; int16 colno; if (!_lastWas0x41) { ncols = 0xc0; colno = 0x40; } else { ncols = 0xe0; colno = 0x20; } DgdsEngine::getInstance()->getGamePals()->setFade(colno, ncols, 0, fade * 4); g_system->updateScreen(); g_system->delayMillis(5); } } else { _lastWas0x41 = (val == 0x41); val = 0x3f; } return RWI16Global::set(val); } private: bool _lastWas0x41; }; HocGlobals::HocGlobals(Clock &clock) : Globals(clock), _unk55(0), _partnerDlgFileNum(0), _partnerDlgDlgNum(0), _currentCharacter2(0), _currentCharacter(0), _tankFinished(0), _nativeGameState(0), _tankState(0), _unk47(0), _unk46(0), _palFade(0x3f), _sheckels(0), _shellBet(0), _shellPea(0), _trainState(0), _startScene(3), _introState(0) { _globals.push_back(new DetailLevelROGlobal(0x53)); _globals.push_back(new HocDifficultyGlobal(0x52)); _globals.push_back(new RWI16Global(0x37, &_unk55)); // TODO: Special update function FUN_1407_080d, sound init related.. sound bank? _globals.push_back(new RWI16Global(0x36, &_partnerDlgFileNum)); _globals.push_back(new RWI16Global(0x35, &_partnerDlgDlgNum)); _globals.push_back(new HocCharacterGlobal(0x34, &_currentCharacter)); _globals.push_back(new HocCharacterGlobal(0x33, &_currentCharacter2)); _globals.push_back(new RWI16Global(0x32, &_tankFinished)); _globals.push_back(new RWI16Global(0x31, &_nativeGameState)); _globals.push_back(new RWI16Global(0x30, &_tankState)); _globals.push_back(new RWI16Global(0x2F, &_unk47)); // tank related.. cows? _globals.push_back(new RWI16Global(0x2E, &_unk46)); // tank related.. start point? _globals.push_back(new HocPalFadeGlobal(0x2D, &_palFade)); _globals.push_back(new RWI16Global(0x2C, &_sheckels)); // used as currency in Istanbul _globals.push_back(new RWI16Global(0x2B, &_shellBet)); _globals.push_back(new RWI16Global(0x2A, &_shellPea)); _globals.push_back(new RWI16Global(0x29, &_trainState)); _globals.push_back(new RWI16Global(0x28, &_startScene)); _globals.push_back(new ROI16Global(0x27, &_introState)); } Common::Error HocGlobals::syncState(Common::Serializer &s) { Globals::syncState(s); s.syncAsSint16LE(_introState); s.syncAsSint16LE(_startScene); s.syncAsSint16LE(_trainState); s.syncAsSint16LE(_shellPea); s.syncAsSint16LE(_shellBet); s.syncAsSint16LE(_sheckels); s.syncAsSint16LE(_palFade); s.syncAsSint16LE(_unk46); s.syncAsSint16LE(_unk47); s.syncAsSint16LE(_tankState); s.syncAsSint16LE(_nativeGameState); s.syncAsSint16LE(_tankFinished); s.syncAsSint16LE(_currentCharacter); s.syncAsSint16LE(_currentCharacter2); s.syncAsSint16LE(_partnerDlgDlgNum); s.syncAsSint16LE(_partnerDlgFileNum); s.syncAsSint16LE(_unk55); // This was a duplicate of difficulty level (replaced by HocDifficultyGlobal). // Sync a 16-bit int for backward compatibility. s.skip(2); return Common::kNoError; } static const int FADE_STARTCOL = 0x40; static const int FADE_NUMCOLS = 0xC0; class PaletteFadeGlobal : public RWI16Global { public: PaletteFadeGlobal(uint16 num, int16 *val) : RWI16Global(num, val) {} int16 set(int16 val) override { val = CLIP(val, (int16)0, (int16)255); int16 lastVal = get(); const int FADESTEP = 4; if (lastVal != val) { int step = (val > lastVal) ? FADESTEP : -FADESTEP; int currentLevel = lastVal / FADESTEP; int targetLevel = val / FADESTEP; while (currentLevel != targetLevel) { lastVal += step; currentLevel = lastVal / FADESTEP; DgdsEngine::getInstance()->getGamePals()->setFade(FADE_STARTCOL, FADE_NUMCOLS, 0, currentLevel); } RWI16Global::set(val); } return get(); } }; class WillyTroubleGlobal : public RWI16Global { public: WillyTroubleGlobal(uint16 num, int16 *val) : RWI16Global(num, val) {} int16 set(int16 val) override { int16 oldVal = get(); if (val != oldVal) { // Draw the trouble meter changing. DgdsEngine *engine = DgdsEngine::getInstance(); val = CLIP(val, (int16)0, (int16)10); Image img(engine->getResourceManager(), engine->getDecompressor()); img.loadBitmap("METER.BMP"); uint16 soundNum = (oldVal < val) ? 0x386 : 0x387; engine->_soundPlayer->playSFX(soundNum); Graphics::ManagedSurface &compBuf = engine->_compositionBuffer; const Common::Rect screenRect(SCREEN_WIDTH, SCREEN_HEIGHT); int16 sign = val > oldVal ? 1 : -1; int16 prevBarSize = oldVal * 8; int16 newBarSize = val * 8; // Animate the trouble bar going up or down compBuf.fillRect(Common::Rect(Common::Point(0x80, 0x96), 0x47, 4), 16); for (int sz = prevBarSize; sz != newBarSize; sz += sign) { RequestData::drawCorners(&compBuf, 0, 0x71, 0x25, 0x5e, 0x7e); compBuf.fillRect(Common::Rect(Common::Point(0x80, 0x3a), 0x47, 0x50), 0); if (sz) compBuf.fillRect(Common::Rect(Common::Point(0x80, 0x8a - sz), 0x47, sz), 0x22); img.drawBitmap(0, 0x80, 0x29, screenRect, compBuf); // // TODO: Timing is a bit hackily approximate here. // Measure real game timing more accurately. Also probably // should pump messages to make the mouse responsive. // g_system->copyRectToScreen(compBuf.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); g_system->updateScreen(); g_system->delayMillis(100); } g_system->delayMillis(900); engine->_soundPlayer->stopSfxByNum(soundNum); return RWI16Global::set(val); } return oldVal; } }; WillyGlobals::WillyGlobals(Clock &clock) : Globals(clock), _trouble(4), _money(0), _invDrawTimeSkipButtons(0), _hideMouseCursor(0), _unk74(0), _unk75(300), _palFade(255), _droppedItemNum(0), _characterStance(0), _characterPos(0), _unk81(3), _unk82(1) { _globals.push_back(new DetailLevelROGlobal(0x53)); _globals.push_back(new RWI16Global(0x52, &_unk82)); // Maybe text speed? _globals.push_back(new RWI16Global(0x51, &_unk81)); // Maybe difficulty? _globals.push_back(new RWI16Global(0x50, &_characterPos)); // ads variable 0 - character position? _globals.push_back(new RWI16Global(0x4F, &_characterStance)); // ads varaible 1 - character stance? _globals.push_back(new RWI16Global(0x4E, &_droppedItemNum)); _globals.push_back(new RWI16Global(0x4D, &_palFade)); _globals.push_back(new PaletteFadeGlobal(0x4C, &_palFade)); _globals.push_back(new RWI16Global(0x4B, &_unk75)); _globals.push_back(new RWI16Global(0x4A, &_unk74)); _globals.push_back(new RWI16Global(0x05, &_hideMouseCursor)); _globals.push_back(new RWI16Global(0x04, &_invDrawTimeSkipButtons)); _globals.push_back(new RWI16Global(0x03, &_money)); _globals.push_back(new WillyTroubleGlobal(0x02, &_trouble)); } Common::Error WillyGlobals::syncState(Common::Serializer &s) { Globals::syncState(s); s.syncAsSint16LE(_trouble); s.syncAsSint16LE(_money); s.syncAsSint16LE(_invDrawTimeSkipButtons); s.syncAsSint16LE(_hideMouseCursor); s.syncAsSint16LE(_unk74); s.syncAsSint16LE(_unk75); s.syncAsSint16LE(_palFade); s.syncAsSint16LE(_droppedItemNum); s.syncAsSint16LE(_characterStance); s.syncAsSint16LE(_characterPos); s.syncAsSint16LE(_unk81); s.syncAsSint16LE(_unk82); return Common::kNoError; } } // end namespace Dgds