diff --git a/CHANGES.txt b/CHANGES.txt index 69723bb..de31d51 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,10 @@ +* release 0.4.9 + - added option to match original inventory items order + - added zoom from DOS version + - added Sega CD tracks playback based on stb_vorbis (OGG 22khz) + - fixed piege opcode 0x57 + - fixed repeating sounds volume + * release 0.4.8 - added detection for DOS version with .ABA files - added Macintosh credits diff --git a/Makefile b/Makefile index 61ed283..18fb80d 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ SDL_CFLAGS := `sdl2-config --cflags` SDL_LIBS := `sdl2-config --libs` MODPLUG_LIBS := -lmodplug -TREMOR_LIBS := -lvorbisidec -logg +TREMOR_LIBS := #-lvorbisidec -logg ZLIB_LIBS := -lz -CXXFLAGS += -Wall -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_TREMOR -DUSE_ZLIB +CXXFLAGS += -Wall -Wpedantic -MMD $(SDL_CFLAGS) -DUSE_MODPLUG -DUSE_STB_VORBIS -DUSE_ZLIB SRCS = collision.cpp cpc_player.cpp cutscene.cpp decode_mac.cpp file.cpp fs.cpp game.cpp graphics.cpp main.cpp \ menu.cpp mixer.cpp mod_player.cpp ogg_player.cpp piege.cpp protection.cpp resource.cpp resource_aba.cpp \ diff --git a/README.txt b/README.txt index 32aeb4c..4d09469 100644 --- a/README.txt +++ b/README.txt @@ -1,6 +1,6 @@ REminiscence README -Release version: 0.4.8 +Release version: 0.4.9 ------------------------------------------------------------------------------- @@ -59,25 +59,20 @@ The widescreen option accepts two modes : In-game hotkeys : - Arrow Keys move Conrad - Enter use the current inventory object - Shift talk / use / run / shoot - Escape display the options - Backspace display the inventory - Alt Enter toggle windowed/fullscreen mode - Alt + and - increase or decrease game screen scaler factor - Alt S write screenshot as .tga - Ctrl S save game state - Ctrl L load game state - Ctrl R rewind game state buffer (requires --autosave) - Ctrl + and - change game state slot - Function Keys change game screen scaler - -Debug hotkeys : - - Ctrl F toggle fast mode - Ctrl I Conrad 'infinite' life - Ctrl B toggle display of updated dirty blocks + Arrow Keys move Conrad + Enter use the current inventory object + Shift talk / use / run / shoot + Escape display options + Backspace / Tab display inventory / skip cutscene + Alt Enter toggle windowed / fullscreen mode + Alt + and - increase or decrease game screen scaler factor + Alt S take screenshot + Ctrl G toggle auto zoom (DOS version only) + Ctrl S save game state + Ctrl L load game state + Ctrl R rewind game state buffer (requires --autosave) + Ctrl + and - change game state slot + Function Keys change game screen scaler Credits: diff --git a/collision.cpp b/collision.cpp index e2a7d96..aabd86d 100644 --- a/collision.cpp +++ b/collision.cpp @@ -42,13 +42,13 @@ void Game::col_clearState() { void Game::col_preparePiegeState(LivePGE *pge) { debug(DBG_COL, "Game::col_preparePiegeState() pge_num=%ld", pge - &_pgeLive[0]); CollisionSlot *ct_slot1, *ct_slot2; - if (pge->init_PGE->unk1C == 0) { + if (pge->init_PGE->collision_data_len == 0) { pge->collision_slot = 0xFF; return; } int i = 0; ct_slot1 = 0; - for (int c = 0; c < pge->init_PGE->unk1C; ++c) { + for (int c = 0; c < pge->init_PGE->collision_data_len; ++c) { ct_slot2 = _col_curSlot; if (ct_slot2 + 1 > &_col_slots[255]) return; @@ -231,7 +231,7 @@ int16_t Game::col_detectHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callba if (pge_room < 0 || pge_room >= 0x40) { return 0; } - int16_t thr = pge->init_PGE->counter_values[0]; + int16_t thr = pge->init_PGE->data[0]; if (thr > 0) { pos_dx = -1; pos_dy = -1; @@ -452,9 +452,9 @@ int Game::col_detectGunHit(LivePGE *pge, int16_t arg2, int16_t arg4, col_Callbac if (pge_room < 0 || pge_room >= 0x40) return 0; int16_t thr, pos_dx, pos_dy; if (argC == -1) { - thr = pge->init_PGE->counter_values[0]; + thr = pge->init_PGE->data[0]; } else { - thr = pge->init_PGE->counter_values[3]; + thr = pge->init_PGE->data[3]; } if (thr > 0) { pos_dx = -1; diff --git a/cpc_player.cpp b/cpc_player.cpp index aa84934..fd54c5a 100644 --- a/cpc_player.cpp +++ b/cpc_player.cpp @@ -108,6 +108,7 @@ int8_t CpcPlayer::readSampleData() { // rewind _f.seek(_restartPos); nextChunk(); + _sampleL = _sampleR = 0; } } const int8_t data = _f.readByte(); diff --git a/cutscene.cpp b/cutscene.cpp index 24c50a5..8aeeda8 100644 --- a/cutscene.cpp +++ b/cutscene.cpp @@ -147,16 +147,12 @@ uint16_t Cutscene::findTextSeparators(const uint8_t *p, int len) { void Cutscene::drawText(int16_t x, int16_t y, const uint8_t *p, uint16_t color, uint8_t *page, int textJustify) { debug(DBG_CUT, "Cutscene::drawText(x=%d, y=%d, c=%d, justify=%d)", x, y, color, textJustify); int len = 0; - if (_res->isMac()) { - if (p == _textBuf) { - while (p[len] != 0xA) { - ++len; - } - } else { - len = *p++; - } + if (p != _textBuf && _res->isMac()) { + len = *p++; } else { - len = strlen((const char *)p); + while (p[len] != 0xA && p[len]) { + ++len; + } } Video::drawCharFunc dcf = _vid->_drawChar; const uint8_t *fnt = (_res->_lang == LANG_JP) ? Video::_font8Jp : _res->_fnt; @@ -202,71 +198,69 @@ void Cutscene::clearBackPage() { } void Cutscene::drawCreditsText() { - if (_creditsSequence) { - if (_creditsKeepText) { - if (_creditsSlowText) { + if (_creditsKeepText) { + if (_creditsSlowText) { + return; + } + _creditsKeepText = false; + } + if (_creditsTextCounter <= 0) { + uint8_t code; + const bool isMac = _res->isMac(); + if (isMac && _creditsTextLen <= 0) { + const uint8_t *p = _res->getCreditsString(_creditsTextIndex++); + if (!p) { return; } - _creditsKeepText = false; + _creditsTextCounter = 60; + _creditsTextPosX = p[0]; + _creditsTextPosY = p[1]; + _creditsTextLen = p[2]; + _textCurPtr = p + 2; + code = 0; + } else { + code = *_textCurPtr; } - if (_creditsTextCounter <= 0) { - uint8_t code; - const bool isMac = _res->isMac(); - if (isMac && _creditsTextLen <= 0) { - const uint8_t *p = _res->getCreditsString(_creditsTextIndex++); - if (!p) { - return; - } - _creditsTextCounter = 60; - _creditsTextPosX = p[0]; - _creditsTextPosY = p[1]; - _creditsTextLen = p[2]; - _textCurPtr = p + 2; - code = 0; - } else { - code = *_textCurPtr; + if (code == 0x7D && isMac) { + ++_textCurPtr; + code = *_textCurPtr++; + _creditsTextLen -= 2; + assert(code > 0x30); + for (int i = 0; i < (code - 0x30); ++i) { + *_textCurBuf++ = ' '; } - if (code == 0x7D && isMac) { - ++_textCurPtr; - code = *_textCurPtr++; - _creditsTextLen -= 2; - assert(code > 0x30); - for (int i = 0; i < (code - 0x30); ++i) { - *_textCurBuf++ = ' '; - } - *_textCurBuf = 0xA; - } else if (code == 0xFF) { - _textBuf[0] = 0xA; - } else if (code == 0xFE) { - ++_textCurPtr; - _creditsTextCounter = *_textCurPtr++; - } else if (code == 1) { - ++_textCurPtr; - _creditsTextPosX = *_textCurPtr++; - _creditsTextPosY = *_textCurPtr++; - } else if (code == 0) { - _textCurBuf = _textBuf; - _textBuf[0] = 0xA; - ++_textCurPtr; - if (_creditsSlowText) { - _creditsKeepText = true; - } - } else { - *_textCurBuf++ = code; - *_textCurBuf = 0xA; - ++_textCurPtr; - if (isMac) { - --_creditsTextLen; - if (_creditsTextLen == 0) { - _creditsTextCounter = 600; - } - } + *_textCurBuf = 0xA; + } else if (code == 0xFF) { + _textBuf[0] = 0xA; + } else if (code == 0xFE) { + ++_textCurPtr; + _creditsTextCounter = *_textCurPtr++; + } else if (code == 1) { + ++_textCurPtr; + _creditsTextPosX = *_textCurPtr++; + _creditsTextPosY = *_textCurPtr++; + } else if (code == 0) { + _textCurBuf = _textBuf; + _textBuf[0] = 0xA; + ++_textCurPtr; + if (_creditsSlowText) { + _creditsKeepText = true; } } else { - _creditsTextCounter -= 10; + *_textCurBuf++ = code; + *_textCurBuf = 0xA; + ++_textCurPtr; + if (isMac) { + --_creditsTextLen; + if (_creditsTextLen == 0) { + _creditsTextCounter = 600; + } + } } - drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _backPage, kTextJustifyLeft); + } else { + _creditsTextCounter -= 10; } + drawText((_creditsTextPosX - 1) * 8, _creditsTextPosY * 8, _textBuf, 0xEF, _backPage, kTextJustifyLeft); } void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) { @@ -307,8 +301,16 @@ void Cutscene::drawProtectionShape(uint8_t shapeNum, int16_t zoom) { void Cutscene::op_markCurPos() { debug(DBG_CUT, "Cutscene::op_markCurPos()"); _cmdPtrBak = _cmdPtr; - drawCreditsText(); _frameDelay = 5; + if (!_creditsSequence) { + if (_id == kCineDebut) { + _frameDelay = 7; + } else if (_id == kCineChute) { + _frameDelay = 6; + } + } else { + drawCreditsText(); + } updateScreen(); clearBackPage(); _creditsSlowText = false; diff --git a/cutscene.h b/cutscene.h index 8c6caf7..a9fab45 100644 --- a/cutscene.h +++ b/cutscene.h @@ -30,6 +30,8 @@ struct Cutscene { }; enum { + kCineDebut = 0, + kCineChute = 47, kCineMemo = 48, kCineVoyage = 52, kCineEspions = 57 diff --git a/game.cpp b/game.cpp index 7b39025..5f13110 100644 --- a/game.cpp +++ b/game.cpp @@ -13,7 +13,7 @@ #include "systemstub.h" #include "util.h" -Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave) +Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, uint32_t cheats) : _cut(&_res, stub, &_vid), _menu(&_res, stub, &_vid), _mix(fs, stub), _res(fs, ver, lang), _seq(stub, &_mix), _vid(&_res, stub, widescreenMode), _stub(stub), _fs(fs), _savePath(savePath) { @@ -26,6 +26,7 @@ Game::Game(SystemStub *stub, FileSystem *fs, const char *savePath, int level, Re _autoSave = autoSave; _rewindPtr = -1; _rewindLen = 0; + _cheats = cheats; } void Game::run() { @@ -56,7 +57,7 @@ void Game::run() { break; } - if (!g_options.bypass_protection && !g_options.use_words_protection && !_res.isMac()) { + if (!g_options.bypass_protection && !g_options.use_words_protection && (_res.isAmiga() || _res.isDOS())) { while (!handleProtectionScreenShape()) { if (_stub->_pi.quit) { return; @@ -126,12 +127,11 @@ void Game::run() { _skillLevel = kSkillNormal; _currentLevel = _demoInputs[_demoBin].level; _randSeed = 0; - _mix.stopMusic(); - break; + } else { + _demoBin = -1; + _skillLevel = _menu._skill; + _currentLevel = _menu._level; } - _demoBin = -1; - _skillLevel = _menu._skill; - _currentLevel = _menu._level; _mix.stopMusic(); break; case kResourceTypeAmiga: @@ -171,7 +171,6 @@ void Game::run() { if (_demoBin != -1 && _inp_demPos >= _res._demLen) { debug(DBG_DEMO, "End of demo"); // exit level - _demoBin = -1; _endLoop = true; } } @@ -360,15 +359,17 @@ void Game::resetGameState() { _animBuffers._curPos[3] = 0xFF; _currentRoom = _res._pgeInit[0].init_room; _cut._deathCutsceneId = 0xFFFF; - _pge_opTempVar2 = 0xFFFF; _deathCutsceneCounter = 0; _saveStateCompleted = false; _loadMap = true; pge_resetMessages(); _blinkingConradCounter = 0; _pge_processOBJ = false; - _pge_opTempVar1 = 0; + _pge_opGunVar = 0; _textToDisplay = 0xFFFF; + _pge_zoomPiegeNum = 0; + _pge_zoomCounter = 0; + _pge_zoomX = _pge_zoomY = 0; } void Game::mainLoop() { @@ -417,7 +418,11 @@ void Game::mainLoop() { _currentLevel = oldLevel; } changeLevel(); - _pge_opTempVar1 = 0; + _pge_opGunVar = 0; + return; + } + if (_currentLevel == 3 && _cut._id == 50) { + // do not draw next room when boarding taxi return; } if (_loadMap) { @@ -431,6 +436,9 @@ void Game::mainLoop() { _vid.fullRefresh(); } } + if (_res.isDOS() && (_stub->_pi.dbgMask & PlayerInput::DF_AUTOZOOM) != 0) { + pge_updateZoom(); + } prepareAnims(); drawAnims(); drawCurrentInventoryItem(); @@ -481,9 +489,7 @@ void Game::playCutscene(int id) { _cut._id = id; } if (_cut._id != 0xFFFF) { - if (_stub->hasWidescreen()) { - _stub->enableWidescreen(false); - } + ToggleWidescreenStack tws(_stub, false); _mix.stopMusic(); if (_res._hasSeqData) { int num = 0; @@ -532,22 +538,20 @@ void Game::playCutscene(int id) { } else { _cut._id = 0xFFFF; } + _mix.stopMusic(); return; } } } - if (_cut._id != 0x4A) { - _mix.playMusic(Cutscene::_musicTable[_cut._id]); - } + _mix.playMusic(Cutscene::_musicTable[_cut._id]); _cut.play(); if (id == 0xD && !_cut._interrupted) { - const bool extendedIntroduction = (_res._type == kResourceTypeDOS || _res._type == kResourceTypeMac); - if (extendedIntroduction) { - _cut._id = 0x4A; + if (!_res.isAmiga()) { + _cut._id = 0x4A; // second part of the introduction cutscene _cut.play(); } } - if (_res._type == kResourceTypeMac && !(id == 0x48 || id == 0x49)) { // continue or score screens + if (_res.isMac() && !(id == 0x48 || id == 0x49)) { // continue or score screens // restore palette entries modified by the cutscene player (0xC and 0xD) Color palette[32]; _res.MAC_copyClut16(palette, 0, 0x37); @@ -561,9 +565,6 @@ void Game::playCutscene(int id) { _cut.playCredits(); } _mix.stopMusic(); - if (_stub->hasWidescreen()) { - _stub->enableWidescreen(true); - } } } @@ -639,10 +640,10 @@ void Game::showFinalScore() { } bool Game::handleConfigPanel() { - const int x = 7; - const int y = 10; - const int w = 17; - const int h = 12; + static const int x = 7; + static const int y = 10; + static const int w = 17; + static const int h = 12; _vid._charShadowColor = 0xE2; _vid._charFrontColor = 0xEE; @@ -1027,7 +1028,7 @@ void Game::drawString(const uint8_t *p, int x, int y, uint8_t color, bool hcente } void Game::prepareAnims() { - if (!(_currentRoom & 0x80) && _currentRoom < 0x40) { + if (_currentRoom < 0x40) { int8_t pge_room; LivePGE *pge = _pge_liveTable1[_currentRoom]; while (pge) { @@ -1540,6 +1541,10 @@ bool Game::hasLevelMap(int level, int room) const { return false; } +static bool isMetro(int level, int room) { + return level == 1 && (room == 0 || room == 13 || room == 38 || room == 51); +} + void Game::loadLevelMap() { debug(DBG_GAME, "Game::loadLevelMap() room=%d", _currentRoom); bool widescreenUpdated = false; @@ -1574,14 +1579,14 @@ void Game::loadLevelMap() { case kResourceTypeDOS: if (_stub->hasWidescreen() && _widescreenMode == kWidescreenAdjacentRooms) { const int leftRoom = _res._ctData[CT_LEFT_ROOM + _currentRoom]; - if (leftRoom > 0 && hasLevelMap(_currentLevel, leftRoom)) { + if (leftRoom >= 0 && hasLevelMap(_currentLevel, leftRoom) && !isMetro(_currentLevel, leftRoom)) { _vid.PC_decodeMap(_currentLevel, leftRoom); _stub->copyWidescreenLeft(Video::GAMESCREEN_W, Video::GAMESCREEN_H, _vid._backLayer); } else { _stub->copyWidescreenLeft(Video::GAMESCREEN_W, Video::GAMESCREEN_H, 0); } const int rightRoom = _res._ctData[CT_RIGHT_ROOM + _currentRoom]; - if (rightRoom > 0 && hasLevelMap(_currentLevel, rightRoom)) { + if (rightRoom >= 0 && hasLevelMap(_currentLevel, rightRoom) && !isMetro(_currentLevel, rightRoom)) { _vid.PC_decodeMap(_currentLevel, rightRoom); _stub->copyWidescreenRight(Video::GAMESCREEN_W, Video::GAMESCREEN_H, _vid._backLayer); } else { @@ -1594,14 +1599,14 @@ void Game::loadLevelMap() { case kResourceTypeMac: if (_stub->hasWidescreen() && _widescreenMode == kWidescreenAdjacentRooms) { const int leftRoom = _res._ctData[CT_LEFT_ROOM + _currentRoom]; - if (leftRoom > 0 && hasLevelMap(_currentLevel, leftRoom)) { + if (leftRoom >= 0 && hasLevelMap(_currentLevel, leftRoom)) { _vid.MAC_decodeMap(_currentLevel, leftRoom); _stub->copyWidescreenLeft(_vid._w, _vid._h, _vid._backLayer); } else { _stub->copyWidescreenLeft(_vid._w, _vid._h, 0); } const int rightRoom = _res._ctData[CT_RIGHT_ROOM + _currentRoom]; - if (rightRoom > 0 && hasLevelMap(_currentLevel, rightRoom)) { + if (rightRoom >= 0 && hasLevelMap(_currentLevel, rightRoom)) { _vid.MAC_decodeMap(_currentLevel, rightRoom); _stub->copyWidescreenRight(_vid._w, _vid._h, _vid._backLayer); } else { @@ -1910,36 +1915,63 @@ void Game::handleInventory() { } icon_x_pos += 32; } - if (current_line != 0) { - drawIcon(78, 120, 176, 0xA); // down arrow - } - if (current_line != num_lines - 1) { + if (current_line != (g_options.order_inventory_original ? 0 : (num_lines - 1))) { drawIcon(77, 120, 143, 0xA); // up arrow } + if (current_line != (g_options.order_inventory_original ? (num_lines - 1) : 0)) { + drawIcon(78, 120, 176, 0xA); // down arrow + } } else { char buf[50]; snprintf(buf, sizeof(buf), "SCORE %08u", _score); _vid.drawString(buf, (114 - strlen(buf) * Video::CHAR_W) / 2 + 72, 158, 0xE5); snprintf(buf, sizeof(buf), "%s:%s", _res.getMenuString(LocaleData::LI_06_LEVEL), _res.getMenuString(LocaleData::LI_13_EASY + _skillLevel)); _vid.drawString(buf, (114 - strlen(buf) * Video::CHAR_W) / 2 + 72, 166, 0xE5); + if (0) { // if the protection screen code was not properly cracked... + static const uint8_t kCrackerText[17] = { + 0x19, 0x08, 0x1B, 0x19, 0x11, 0x1F, 0x08, 0x67, 0x18, + 0x16, 0x1B, 0x13, 0x08, 0x1F, 0x1B, 0x0F, 0x5A + }; + for (int i = 0; i < 17; ++i) { + buf[i] = kCrackerText[i] ^ 0x5A; + } + _vid.drawString(buf, 65, 193, 0xE4); + } } _vid.updateScreen(); _stub->sleep(80); inp_update(); - if (_stub->_pi.dirMask & PlayerInput::DIR_UP) { - _stub->_pi.dirMask &= ~PlayerInput::DIR_UP; - if (current_line < num_lines - 1) { - ++current_line; - current_item = current_line * 4; + if (g_options.order_inventory_original) { + if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) { + _stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN; + if (current_line < num_lines - 1) { + ++current_line; + current_item = current_line * 4; + } } - } - if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) { - _stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN; - if (current_line > 0) { - --current_line; - current_item = current_line * 4; + if (_stub->_pi.dirMask & PlayerInput::DIR_UP) { + _stub->_pi.dirMask &= ~PlayerInput::DIR_UP; + if (current_line > 0) { + --current_line; + current_item = current_line * 4; + } + } + } else { + if (_stub->_pi.dirMask & PlayerInput::DIR_UP) { + _stub->_pi.dirMask &= ~PlayerInput::DIR_UP; + if (current_line < num_lines - 1) { + ++current_line; + current_item = current_line * 4; + } + } + if (_stub->_pi.dirMask & PlayerInput::DIR_DOWN) { + _stub->_pi.dirMask &= ~PlayerInput::DIR_DOWN; + if (current_line > 0) { + --current_line; + current_item = current_line * 4; + } } } if (_stub->_pi.dirMask & PlayerInput::DIR_LEFT) { @@ -1990,6 +2022,9 @@ void Game::makeGameStateName(uint8_t slot, char *buf) { sprintf(buf, "rs-level%d-%02d.state", _currentLevel + 1, slot); } +// 3: persist _pge_opGunVar +static const int kSaveVersion = 3; + static const uint32_t TAG_FBSV = 0x46425356; bool Game::saveGameState(uint8_t slot) { @@ -2005,7 +2040,7 @@ bool Game::saveGameState(uint8_t slot) { } else { // header f.writeUint32BE(TAG_FBSV); - f.writeUint16BE(2); + f.writeUint16BE(kSaveVersion); char buf[32]; memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "level=%d room=%d", _currentLevel + 1, _currentRoom); @@ -2037,15 +2072,15 @@ bool Game::loadGameState(uint8_t slot) { if (id != TAG_FBSV) { warning("Bad save state format"); } else { - uint16_t ver = f.readUint16BE(); - if (ver != 2) { + const uint16_t version = f.readUint16BE(); + if (version < 2) { warning("Invalid save state version"); } else { // header char buf[32]; f.read(buf, sizeof(buf)); // contents - loadState(&f); + loadState(&f, version); if (f.ioErr()) { warning("I/O error when loading game state"); } else { @@ -2083,7 +2118,7 @@ void Game::saveState(File *f) { f->writeByte(pge->collision_slot); f->writeByte(pge->next_inventory_PGE); f->writeByte(pge->current_inventory_PGE); - f->writeByte(pge->unkF); + f->writeByte(pge->ref_inventory_PGE); f->writeUint16BE(pge->anim_number); f->writeByte(pge->flags); f->writeByte(pge->index); @@ -2114,9 +2149,10 @@ void Game::saveState(File *f) { f->writeByte(cs2->data_size); f->write(cs2->data_buf, 0x10); } + f->writeUint16BE(_pge_opGunVar); } -void Game::loadState(File *f) { +void Game::loadState(File *f, int version) { uint16_t i; uint32_t off; _skillLevel = f->readByte(); @@ -2147,7 +2183,7 @@ void Game::loadState(File *f) { pge->collision_slot = f->readByte(); pge->next_inventory_PGE = f->readByte(); pge->current_inventory_PGE = f->readByte(); - pge->unkF = f->readByte(); + pge->ref_inventory_PGE = f->readByte(); pge->anim_number = f->readUint16BE(); pge->flags = f->readByte(); pge->index = f->readByte(); @@ -2193,6 +2229,9 @@ void Game::loadState(File *f) { } } resetGameState(); + if (version >= 3) { + _pge_opGunVar = f->readUint16BE(); + } } void Game::clearStateRewind() { @@ -2234,7 +2273,7 @@ bool Game::loadStateRewind() { } File &f = _rewindBuffer[ptr]; f.seek(0); - loadState(&f); + loadState(&f, kSaveVersion); if (_rewindLen > 0) { --_rewindLen; } diff --git a/game.h b/game.h index c95ec15..c526c3f 100644 --- a/game.h +++ b/game.h @@ -19,6 +19,12 @@ struct File; struct FileSystem; struct SystemStub; +enum { + kCheatOneHitKill = 1 << 0, + kCheatNoHit = 1 << 1, + kCheatLifeCounter = 1 << 2 +}; + struct Game { typedef int (Game::*pge_OpcodeProc)(ObjectOpcodeArgs *args); typedef int (Game::*pge_ZOrderCallback)(LivePGE *, LivePGE *, uint8_t, uint8_t); @@ -71,6 +77,7 @@ struct Game { const char *_savePath; File _rewindBuffer[kRewindSize]; int _rewindPtr, _rewindLen; + uint32_t _cheats; const uint8_t *_stringsTable; const char **_textsTable; @@ -102,7 +109,7 @@ struct Game { bool _autoSave; uint32_t _saveTimestamp; - Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave); + Game(SystemStub *, FileSystem *, const char *savePath, int level, ResourceType ver, Language lang, WidescreenMode widescreenMode, bool autoSave, uint32_t cheats); void run(); void displayTitleScreenAmiga(); @@ -155,10 +162,12 @@ struct Game { bool _pge_currentPiegeFacingDir; // (false == left) bool _pge_processOBJ; uint8_t _pge_inpKeysMask; - uint16_t _pge_opTempVar1; - uint16_t _pge_opTempVar2; + uint16_t _pge_opGunVar; uint16_t _pge_compareVar1; uint16_t _pge_compareVar2; + uint8_t _pge_zoomPiegeNum; + uint8_t _pge_zoomCounter; + int _pge_zoomX, _pge_zoomY; void pge_resetMessages(); void pge_clearMessages(uint8_t pge_index); @@ -248,7 +257,7 @@ struct Game { int pge_o_unk0x47(ObjectOpcodeArgs *args); int pge_o_unk0x48(ObjectOpcodeArgs *args); int pge_o_unk0x49(ObjectOpcodeArgs *args); - int pge_o_unk0x4A(ObjectOpcodeArgs *args); + int pge_op_killInventoryPiege(ObjectOpcodeArgs *args); int pge_op_killPiege(ObjectOpcodeArgs *args); int pge_op_isInCurrentRoom(ObjectOpcodeArgs *args); int pge_op_isNotInCurrentRoom(ObjectOpcodeArgs *args); @@ -264,7 +273,7 @@ struct Game { int pge_op_setLifeCounter(ObjectOpcodeArgs *args); int pge_op_decLifeCounter(ObjectOpcodeArgs *args); int pge_op_playCutscene(ObjectOpcodeArgs *args); - int pge_op_isTempVar2Set(ObjectOpcodeArgs *args); + int pge_op_compareUnkVar(ObjectOpcodeArgs *args); int pge_op_playDeathCutscene(ObjectOpcodeArgs *args); int pge_o_unk0x5D(ObjectOpcodeArgs *args); int pge_o_unk0x5E(ObjectOpcodeArgs *args); @@ -310,14 +319,14 @@ struct Game { int pge_o_unk0x86(ObjectOpcodeArgs *args); int pge_op_playSoundGroup(ObjectOpcodeArgs *args); int pge_op_adjustPos(ObjectOpcodeArgs *args); - int pge_op_setTempVar1(ObjectOpcodeArgs *args); - int pge_op_isTempVar1Set(ObjectOpcodeArgs *args); + int pge_op_setGunVar(ObjectOpcodeArgs *args); + int pge_op_compareGunVar(ObjectOpcodeArgs *args); int pge_setCurrentInventoryObject(LivePGE *pge); void pge_updateInventory(LivePGE *pge1, LivePGE *pge2); void pge_reorderInventory(LivePGE *pge); - LivePGE *pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge); + LivePGE *pge_getPreviousInventoryItem(LivePGE *pge, LivePGE *last_pge); void pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3); - int pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8); + int pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t value); int pge_ZOrder(LivePGE *pge, int16_t num, pge_ZOrderCallback compare, uint16_t unk); void pge_sendMessage(uint8_t src_pge_index, uint8_t dst_pge_index, int16_t num); void pge_removeFromInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3); @@ -331,6 +340,7 @@ struct Game { int pge_ZOrderIfTypeAndSameDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2); int pge_ZOrderIfTypeAndDifferentDirection(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2); int pge_ZOrderByNumber(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2); + void pge_updateZoom(); // collision @@ -386,7 +396,7 @@ struct Game { bool saveGameState(uint8_t slot); bool loadGameState(uint8_t slot); void saveState(File *f); - void loadState(File *f); + void loadState(File *f, int version); void clearStateRewind(); bool saveStateRewind(); bool loadStateRewind(); diff --git a/intern.h b/intern.h index a2118fc..82e2fee 100644 --- a/intern.h +++ b/intern.h @@ -137,6 +137,7 @@ struct Options { bool play_carte_cutscene; bool play_gamesaved_sound; bool restore_memo_cutscene; + bool order_inventory_original; }; struct Color { @@ -172,8 +173,8 @@ struct InitPGE { int16_t pos_y; uint16_t obj_node_number; uint16_t life; - int16_t counter_values[4]; // data - uint8_t object_type; + int16_t data[4]; + uint8_t object_type; // 1:conrad, 10:monster uint8_t init_room; uint8_t room_location; uint8_t init_flags; @@ -183,7 +184,7 @@ struct InitPGE { uint8_t skill; uint8_t mirror_x; uint8_t flags; // 1:xflip 4:active - uint8_t unk1C; // collidable, collision_data_len + uint8_t collision_data_len; uint16_t text_num; }; @@ -194,11 +195,11 @@ struct LivePGE { uint8_t anim_seq; uint8_t room_location; int16_t life; - int16_t counter_value; // msg + int16_t counter_value; uint8_t collision_slot; uint8_t next_inventory_PGE; uint8_t current_inventory_PGE; - uint8_t unkF; // unk_inventory_PGE + uint8_t ref_inventory_PGE; uint16_t anim_number; uint8_t flags; uint8_t index; @@ -209,7 +210,7 @@ struct LivePGE { struct MessagePGE { MessagePGE *next_entry; - uint16_t index; // src_pge + uint16_t src_pge; uint16_t msg_num; }; @@ -268,9 +269,9 @@ struct BankSlot { struct CollisionSlot2 { CollisionSlot2 *next_slot; - int8_t *unk2; + int8_t *unk2; // grid_data_pos uint8_t data_size; - uint8_t data_buf[0x10]; // XXX check size + uint8_t data_buf[0x10]; // <= InitPGE.collision_data_len }; struct InventoryItem { diff --git a/main.cpp b/main.cpp index cec0fbf..e8bfd83 100644 --- a/main.cpp +++ b/main.cpp @@ -102,6 +102,7 @@ static void initOptions() { g_options.play_carte_cutscene = false; g_options.play_gamesaved_sound = false; g_options.restore_memo_cutscene = true; + g_options.order_inventory_original = false; // read configuration file struct { const char *name; @@ -123,6 +124,7 @@ static void initOptions() { { "play_carte_cutscene", &g_options.play_carte_cutscene }, { "play_gamesaved_sound", &g_options.play_gamesaved_sound }, { "restore_memo_cutscene", &g_options.restore_memo_cutscene }, + { "order_inventory_original", &g_options.order_inventory_original }, { 0, 0 } }; static const char *filename = "rs.cfg"; @@ -194,6 +196,7 @@ int main(int argc, char *argv[]) { int levelNum = 0; bool fullscreen = false; bool autoSave = false; + uint32_t cheats = 0; WidescreenMode widescreen = kWidescreenNone; ScalerParameters scalerParameters = ScalerParameters::defaults(); int forcedLanguage = -1; @@ -214,6 +217,7 @@ int main(int argc, char *argv[]) { { "language", required_argument, 0, 6 }, { "widescreen", required_argument, 0, 7 }, { "autosave", no_argument, 0, 8 }, + { "cheats", required_argument, 0, 9 }, { 0, 0, 0, 0 } }; int index; @@ -264,6 +268,9 @@ int main(int argc, char *argv[]) { case 8: autoSave = true; break; + case 9: + cheats = atoi(optarg); + break; default: printf(USAGE, argv[0]); return 0; @@ -279,7 +286,7 @@ int main(int argc, char *argv[]) { } const Language language = (forcedLanguage == -1) ? detectLanguage(&fs) : (Language)forcedLanguage; SystemStub *stub = SystemStub_SDL_create(); - Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave); + Game *g = new Game(stub, &fs, savePath, levelNum, (ResourceType)version, language, widescreen, autoSave, cheats); stub->init(g_caption, g->_vid._w, g->_vid._h, fullscreen, widescreen, &scalerParameters); g->run(); delete g; diff --git a/menu.cpp b/menu.cpp index 98438d9..6e52c81 100644 --- a/menu.cpp +++ b/menu.cpp @@ -361,10 +361,8 @@ void Menu::handleTitleScreen() { ++menuItemsCount; _selectedOption = -1; - _currentScreen = -1; _nextScreen = SCREEN_TITLE; - bool quitLoop = false; int currentEntry = 0; static const struct { @@ -386,7 +384,7 @@ void Menu::handleTitleScreen() { } } - while (!quitLoop && !_stub->_pi.quit) { + while (!_stub->_pi.quit) { int selectedItem = -1; int previousLanguage = currentLanguage; @@ -398,7 +396,6 @@ void Menu::handleTitleScreen() { _charVar3 = 1; _charVar4 = 2; currentEntry = 0; - _currentScreen = _nextScreen; _nextScreen = -1; } @@ -444,30 +441,27 @@ void Menu::handleTitleScreen() { _selectedOption = menuItems[selectedItem].opt; switch (_selectedOption) { case MENU_OPTION_ITEM_START: - quitLoop = true; - break; + return; case MENU_OPTION_ITEM_SKILL: - _currentScreen = SCREEN_SKILL; handleSkillScreen(); break; case MENU_OPTION_ITEM_PASSWORD: - _currentScreen = SCREEN_PASSWORD; - quitLoop = handlePasswordScreen(); + if (handlePasswordScreen()) { + return; + } break; case MENU_OPTION_ITEM_LEVEL: - _currentScreen = SCREEN_LEVEL; - quitLoop = handleLevelScreen(); + if (handleLevelScreen()) { + return; + } break; case MENU_OPTION_ITEM_INFO: - _currentScreen = SCREEN_INFO; handleInfoScreen(); break; case MENU_OPTION_ITEM_DEMO: - quitLoop = true; - break; + return; case MENU_OPTION_ITEM_QUIT: - quitLoop = true; - break; + return; } _nextScreen = SCREEN_TITLE; continue; diff --git a/mixer.cpp b/mixer.cpp index 4a32442..25278c1 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -42,6 +42,7 @@ void Mixer::play(const uint8_t *data, uint32_t len, uint16_t freq, uint8_t volum if (cur->active) { if (cur->chunk.data == data) { cur->chunkPos = 0; + cur->volume = volume; return; } } else { @@ -89,22 +90,21 @@ static bool isMusicSfx(int num) { void Mixer::playMusic(int num) { debug(DBG_SND, "Mixer::playMusic(%d)", num); - if (num > MUSIC_TRACK && num != _musicTrack) { - if (_ogg.playTrack(num - MUSIC_TRACK)) { - _backgroundMusicType = _musicType = MT_OGG; - _musicTrack = num; - return; - } - if (_cpc.playTrack(num - MUSIC_TRACK)) { - _backgroundMusicType = _musicType = MT_CPC; - _musicTrack = num; - return; - } - } + int trackNum = -1; if (num == 1) { // menu screen - if (_cpc.playTrack(2) || _ogg.playTrack(2)) { + trackNum = 2; + } else if (num > MUSIC_TRACK) { + trackNum = num - MUSIC_TRACK; + } + if (trackNum != -1 && trackNum != _musicTrack) { + if (_ogg.playTrack(trackNum)) { _backgroundMusicType = _musicType = MT_OGG; - _musicTrack = 2; + _musicTrack = trackNum; + return; + } + if (_cpc.playTrack(trackNum)) { + _backgroundMusicType = _musicType = MT_CPC; + _musicTrack = trackNum; return; } } diff --git a/ogg_player.cpp b/ogg_player.cpp index 9c47b1d..7c1ebe9 100644 --- a/ogg_player.cpp +++ b/ogg_player.cpp @@ -7,6 +7,9 @@ #ifdef USE_TREMOR #include #endif +#ifdef USE_STB_VORBIS +#include "stb_vorbis.c" +#endif #include "file.h" #include "mixer.h" #include "ogg_player.h" @@ -67,13 +70,17 @@ struct OggDecoder_impl { } } - bool load(VorbisFile *f, int mixerSampleRate) { + bool load(const char *name, FileSystem *fs, int mixerSampleRate) { + if (!_f.open(name, "rb", fs)) { + return false; + } + _f.offset = 0; ov_callbacks ovcb; ovcb.read_func = VorbisFile::readHelper; ovcb.seek_func = VorbisFile::seekHelper; ovcb.close_func = VorbisFile::closeHelper; ovcb.tell_func = VorbisFile::tellHelper; - if (ov_open_callbacks(f, &_ovf, 0, 0, ovcb) < 0) { + if (ov_open_callbacks(&_f, &_ovf, 0, 0, ovcb) < 0) { warning("Invalid .ogg file"); return false; } @@ -131,6 +138,7 @@ struct OggDecoder_impl { return count; } + VorbisFile _f; OggVorbis_File _ovf; int _channels; bool _open; @@ -139,66 +147,163 @@ struct OggDecoder_impl { }; #endif +#ifdef USE_STB_VORBIS +static const int kMusicVolume = 192; + +struct OggDecoder_impl { + OggDecoder_impl() + : _v(0) { + } + ~OggDecoder_impl() { + if (_v) { + stb_vorbis_close(_v); + _v = 0; + } + } + bool load(const char *name, FileSystem *fs, int mixerSampleRate) { + if (!_f.open(name, "rb", fs)) { + return false; + } + _count = _f.read(_buffer, sizeof(_buffer)); + if (_count > 0) { + int bytes = 0; + int error = 0; + _v = stb_vorbis_open_pushdata(_buffer, _count, &bytes, &error, 0); + if (_v) { + _offset = bytes; + stb_vorbis_info info = stb_vorbis_get_info(_v); + if (info.channels != 2 || (int)info.sample_rate != mixerSampleRate) { + warning("Unhandled ogg/pcm format ch %d rate %d", info.channels, info.sample_rate); + return false; + } + _decodedSamplesLen = 0; + return true; + } + } + return false; + } + int read(int16_t *dst, int samples) { + int total = 0; + if (_decodedSamplesLen != 0) { + const int len = MIN(_decodedSamplesLen, samples); + for (int i = 0; i < len; ++i) { + const int sample = (_decodedSamples[0][i] + _decodedSamples[1][i]) / 2; + *dst = ADDC_S16(*dst, ((sample * kMusicVolume) >> 8)); + ++dst; + } + total += len; + _decodedSamplesLen -= len; + } + while (total < samples) { + int channels = 0; + float **outputs; + int count; + int bytes = stb_vorbis_decode_frame_pushdata(_v, _buffer + _offset, _count - _offset, &channels, &outputs, &count); + if (bytes == 0) { + if (_offset != _count) { + memmove(_buffer, _buffer + _offset, _count - _offset); + _offset = _count - _offset; + } else { + _offset = 0; + } + _count = sizeof(_buffer) - _offset; + bytes = _f.read(_buffer + _offset, _count); + if (bytes < 0) { + break; + } + if (bytes == 0) { + // rewind + _f.seek(0); + _count = _f.read(_buffer, sizeof(_buffer)); + stb_vorbis_flush_pushdata(_v); + } else { + _count = _offset + bytes; + } + _offset = 0; + continue; + } + _offset += bytes; + if (channels == 2) { + const int remain = samples - total; + const int len = MIN(count, remain); + for (int i = 0; i < len; ++i) { + const int l = int(outputs[0][i] * 32768 + .5); + const int r = int(outputs[1][i] * 32768 + .5); + const int sample = (l + r) / 2; + *dst = ADDC_S16(*dst, ((sample * kMusicVolume) >> 8)); + ++dst; + } + if (count > remain) { + _decodedSamplesLen = count - remain; + assert(_decodedSamplesLen < 1024); + for (int i = 0; i < _decodedSamplesLen; ++i) { + _decodedSamples[0][i] = int(outputs[0][len + i] * 32768 + .5); + _decodedSamples[1][i] = int(outputs[1][len + i] * 32768 + .5); + } + total = samples; + break; + } + } else { + warning("Invalid decoded data channels %d count %d", channels, count); + } + total += count; + } + return total; + } + + uint8_t _buffer[8192]; + int16_t _decodedSamples[2][1024]; + int _decodedSamplesLen; + uint32_t _offset, _count; + stb_vorbis *_v; + File _f; +}; +#endif + OggPlayer::OggPlayer(Mixer *mixer, FileSystem *fs) - : _mix(mixer), _fs(fs), _impl(0) { + : _mix(mixer), _fs(fs) { + _impl = new OggDecoder_impl; } OggPlayer::~OggPlayer() { -#ifdef USE_TREMOR delete _impl; -#endif + _impl = 0; } bool OggPlayer::playTrack(int num) { -#ifdef USE_TREMOR stopTrack(); char buf[16]; snprintf(buf, sizeof(buf), "track%02d.ogg", num); - VorbisFile *vf = new VorbisFile; - if (vf->open(buf, "rb", _fs)) { - vf->offset = 0; - _impl = new OggDecoder_impl(); - if (_impl->load(vf, _mix->getSampleRate())) { - debug(DBG_INFO, "Playing '%s'", buf); - _mix->setPremixHook(mixCallback, this); - return true; - } + if (_impl->load(buf, _fs, _mix->getSampleRate())) { + debug(DBG_INFO, "Playing '%s'", buf); + _mix->setPremixHook(mixCallback, this); + return true; } - delete vf; -#endif return false; } void OggPlayer::stopTrack() { -#ifdef USE_TREMOR - _mix->setPremixHook(0, 0); - delete _impl; - _impl = 0; -#endif -} - -void OggPlayer::pauseTrack() { -#ifdef USE_TREMOR if (_impl) { _mix->setPremixHook(0, 0); } -#endif +} + +void OggPlayer::pauseTrack() { + if (_impl) { + _mix->setPremixHook(0, 0); + } } void OggPlayer::resumeTrack() { -#ifdef USE_TREMOR if (_impl) { _mix->setPremixHook(mixCallback, this); } -#endif } bool OggPlayer::mix(int16_t *buf, int len) { -#ifdef USE_TREMOR if (_impl) { return _impl->read(buf, len) != 0; } -#endif return false; } diff --git a/piege.cpp b/piege.cpp index ed9af09..3ee9f8d 100644 --- a/piege.cpp +++ b/piege.cpp @@ -16,12 +16,12 @@ void Game::pge_resetMessages() { int n = 0xFF; while (n--) { le->next_entry = le + 1; - le->index = 0; + le->src_pge = 0; le->msg_num = 0; ++le; } le->next_entry = 0; - le->index = 0; + le->src_pge = 0; le->msg_num = 0; } @@ -33,7 +33,7 @@ void Game::pge_clearMessages(uint8_t pge_index) { while (le) { MessagePGE *cur = le->next_entry; le->next_entry = next; - le->index = 0; + le->src_pge = 0; le->msg_num = 0; next = le; le = cur; @@ -44,10 +44,10 @@ void Game::pge_clearMessages(uint8_t pge_index) { int Game::pge_hasMessageData(LivePGE *pge, uint16_t msg_num, uint16_t counter) const { assert(counter >= 1 && counter <= 4); - uint16_t pge_src_index = pge->init_PGE->counter_values[counter - 1]; + uint16_t pge_src_index = pge->init_PGE->data[counter - 1]; const MessagePGE *le = _pge_messagesTable[pge->index]; while (le) { - if (le->msg_num == msg_num && le->index == pge_src_index) { + if (le->msg_num == msg_num && le->src_pge == pge_src_index) { return 1; } le = le->next_entry; @@ -76,7 +76,7 @@ void Game::pge_loadForCurrentLevel(uint16_t idx) { live_pge->collision_slot = 0xFF; live_pge->next_inventory_PGE = 0xFF; live_pge->current_inventory_PGE = 0xFF; - live_pge->unkF = 0xFF; + live_pge->ref_inventory_PGE = 0xFF; live_pge->anim_number = 0; live_pge->index = idx; live_pge->next_PGE_in_room = 0; @@ -317,8 +317,14 @@ int Game::pge_execute(LivePGE *live_pge, InitPGE *init_pge, const Object *obj) { --live_pge->life; if (init_pge->object_type == 1) { _pge_processOBJ = true; + if (_cheats & kCheatLifeCounter) { + ++live_pge->life; + } } else if (init_pge->object_type == 10) { _score += 100; + if (_cheats & kCheatOneHitKill) { + live_pge->life = 0; + } } } if (obj->flags & 4) { @@ -369,7 +375,7 @@ void Game::pge_prepare() { void Game::pge_setupDefaultAnim(LivePGE *pge) { const uint8_t *anim_data = _res.getAniData(pge->obj_type); - if (pge->anim_seq < _res._readUint16(anim_data)) { + if (1 || pge->anim_seq < _res._readUint16(anim_data)) { /* matches disassembly but should probably be >= */ pge->anim_seq = 0; } const uint8_t *anim_frame = anim_data + 6 + pge->anim_seq * 4; @@ -435,7 +441,7 @@ void Game::pge_setupOtherPieges(LivePGE *pge, InitPGE *init_pge) { _currentRoom = room; col_prepareRoomState(); _loadMap = true; - if (!(_currentRoom & 0x80) && _currentRoom < 0x40) { + if (_currentRoom < 0x40) { LivePGE *pge_it = _pge_liveTable1[_currentRoom]; while (pge_it) { if (pge_it->init_PGE->flags & 4) { @@ -809,25 +815,25 @@ int Game::pge_hasPiegeSentMessage(ObjectOpcodeArgs *args) { int Game::pge_op_sendMessageData0(ObjectOpcodeArgs *args) { LivePGE *pge = args->pge; - pge_sendMessage(pge->index, pge->init_PGE->counter_values[0], args->a); + pge_sendMessage(pge->index, pge->init_PGE->data[0], args->a); return 0xFFFF; } int Game::pge_op_sendMessageData1(ObjectOpcodeArgs *args) { LivePGE *pge = args->pge; - pge_sendMessage(pge->index, pge->init_PGE->counter_values[1], args->a); + pge_sendMessage(pge->index, pge->init_PGE->data[1], args->a); return 0xFFFF; } int Game::pge_op_sendMessageData2(ObjectOpcodeArgs *args) { LivePGE *pge = args->pge; - pge_sendMessage(pge->index, pge->init_PGE->counter_values[2], args->a); + pge_sendMessage(pge->index, pge->init_PGE->data[2], args->a); return 0xFFFF; } int Game::pge_op_sendMessageData3(ObjectOpcodeArgs *args) { LivePGE *pge = args->pge; - pge_sendMessage(pge->index, pge->init_PGE->counter_values[3], args->a); + pge_sendMessage(pge->index, pge->init_PGE->data[3], args->a); return 0xFFFF; } @@ -1102,7 +1108,7 @@ int Game::pge_o_unk0x40(ObjectOpcodeArgs *args) { int Game::pge_op_wakeUpPiege(ObjectOpcodeArgs *args) { if (args->a <= 3) { - int16_t num = args->pge->init_PGE->counter_values[args->a]; + int16_t num = args->pge->init_PGE->data[args->a]; if (num >= 0) { LivePGE *pge = &_pgeLive[num]; pge->flags |= 4; @@ -1114,7 +1120,7 @@ int Game::pge_op_wakeUpPiege(ObjectOpcodeArgs *args) { int Game::pge_op_removePiege(ObjectOpcodeArgs *args) { if (args->a <= 3) { - int16_t num = args->pge->init_PGE->counter_values[args->a]; + int16_t num = args->pge->init_PGE->data[args->a]; if (num >= 0) { _pge_liveTable2[num] = 0; _pgeLive[num].flags &= ~4; @@ -1127,8 +1133,7 @@ int Game::pge_op_removePiegeIfNotNear(ObjectOpcodeArgs *args) { LivePGE *pge = args->pge; if (!(pge->init_PGE->flags & 4)) goto kill_pge; if (_currentRoom & 0x80) goto skip_pge; - if (pge->room_location & 0x80) goto kill_pge; - if (pge->room_location > 0x3F) goto kill_pge; + if (!(pge->room_location < 0x40)) goto kill_pge; if (pge->room_location == _currentRoom) goto skip_pge; if (pge->room_location == _res._ctData[CT_UP_ROOM + _currentRoom]) goto skip_pge; if (pge->room_location == _res._ctData[CT_DOWN_ROOM + _currentRoom]) goto skip_pge; @@ -1146,7 +1151,7 @@ skip_pge: } int Game::pge_op_loadPiegeCounter(ObjectOpcodeArgs *args) { - args->pge->counter_value = args->pge->init_PGE->counter_values[args->a]; + args->pge->counter_value = args->pge->init_PGE->data[args->a]; return 1; } @@ -1168,7 +1173,7 @@ int Game::pge_o_unk0x47(ObjectOpcodeArgs *args) { // used with Ian in level2 int Game::pge_o_unk0x48(ObjectOpcodeArgs *args) { - LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->counter_values[0]); + LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->data[0]); if (pge && pge->life == args->pge->life) { pge_sendMessage(args->pge->index, pge->index, args->a); return 1; @@ -1177,15 +1182,15 @@ int Game::pge_o_unk0x48(ObjectOpcodeArgs *args) { } int Game::pge_o_unk0x49(ObjectOpcodeArgs *args) { - return pge_ZOrder(&_pgeLive[0], args->a, &Game::pge_ZOrderIfIndex, args->pge->init_PGE->counter_values[0]); + return pge_ZOrder(&_pgeLive[0], args->a, &Game::pge_ZOrderIfIndex, args->pge->init_PGE->data[0]); } -int Game::pge_o_unk0x4A(ObjectOpcodeArgs *args) { +int Game::pge_op_killInventoryPiege(ObjectOpcodeArgs *args) { LivePGE *pge = args->pge; pge->room_location = 0xFE; pge->flags &= ~4; _pge_liveTable2[pge->index] = 0; - LivePGE *inv_pge = pge_getInventoryItemBefore(&_pgeLive[args->a], pge); + LivePGE *inv_pge = pge_getPreviousInventoryItem(&_pgeLive[args->a], pge); if (inv_pge == &_pgeLive[args->a]) { if (pge->index != inv_pge->current_inventory_PGE) { return 1; @@ -1266,14 +1271,11 @@ int Game::pge_op_incLife(ObjectOpcodeArgs *args) { return 1; } -// level2, Ian int Game::pge_op_setPiegeDefaultAnim(ObjectOpcodeArgs *args) { assert(args->a >= 0 && args->a < 4); - int16_t r = args->pge->init_PGE->counter_values[args->a]; - args->pge->room_location = r; - if (r == 1) { - // this happens after death tower, on earth, when Conrad passes - // by the first policeman who's about to shoot him in the back + InitPGE *init_pge = args->pge->init_PGE; + args->pge->room_location = init_pge->data[args->a]; + if (init_pge->object_type == 1) { _loadMap = true; } pge_setupDefaultAnim(args->pge); @@ -1281,7 +1283,7 @@ int Game::pge_op_setPiegeDefaultAnim(ObjectOpcodeArgs *args) { } int Game::pge_op_setLifeCounter(ObjectOpcodeArgs *args) { - _pgeLive[args->a].life = args->pge->init_PGE->counter_values[0]; + _pgeLive[args->a].life = args->pge->init_PGE->data[0]; return 1; } @@ -1297,8 +1299,9 @@ int Game::pge_op_playCutscene(ObjectOpcodeArgs *args) { return 1; } -int Game::pge_op_isTempVar2Set(ObjectOpcodeArgs *args) { - if (_pge_opTempVar2 == args->a) { +// unused +int Game::pge_op_compareUnkVar(ObjectOpcodeArgs *args) { + if (args->a == -1) { return 1; } return 0; @@ -1306,7 +1309,7 @@ int Game::pge_op_isTempVar2Set(ObjectOpcodeArgs *args) { int Game::pge_op_playDeathCutscene(ObjectOpcodeArgs *args) { if (_deathCutsceneCounter == 0) { - _deathCutsceneCounter = args->pge->init_PGE->counter_values[3] + 1; + _deathCutsceneCounter = args->pge->init_PGE->data[3] + 1; _cut._deathCutsceneId = args->a; } return 1; @@ -1327,7 +1330,7 @@ int Game::pge_o_unk0x5F(ObjectOpcodeArgs *args) { if (pge_room < 0 || pge_room >= 0x40) return 0; int16_t dx; - int16_t _cx = pge->init_PGE->counter_values[0]; + int16_t _cx = pge->init_PGE->data[0]; if (_cx <= 0) { dx = 1; _cx = -_cx; @@ -1367,7 +1370,7 @@ int Game::pge_op_findAndCopyPiege(ObjectOpcodeArgs *args) { MessagePGE *le = _pge_messagesTable[args->pge->index]; while (le) { if (le->msg_num == args->a) { - args->a = le->index; + args->a = le->src_pge; args->b = 0; pge_op_copyPiege(args); return 1; @@ -1401,16 +1404,16 @@ int Game::pge_o_unk0x64(ObjectOpcodeArgs *args) { int Game::pge_op_addToCredits(ObjectOpcodeArgs *args) { assert(args->a >= 0 && args->a < 3); - uint8_t pge = args->pge->init_PGE->counter_values[args->a]; - int16_t val = args->pge->init_PGE->counter_values[args->a + 1]; + uint8_t pge = args->pge->init_PGE->data[args->a]; + int16_t val = args->pge->init_PGE->data[args->a + 1]; _pgeLive[pge].life += val; return 1; } int Game::pge_op_subFromCredits(ObjectOpcodeArgs *args) { assert(args->a >= 0 && args->a < 3); - uint8_t pge = args->pge->init_PGE->counter_values[args->a]; - int16_t val = args->pge->init_PGE->counter_values[args->a + 1]; + uint8_t pge = args->pge->init_PGE->data[args->a]; + int16_t val = args->pge->init_PGE->data[args->a + 1]; _pgeLive[pge].life -= val; return 1; } @@ -1568,7 +1571,7 @@ int Game::pge_isToggleable(ObjectOpcodeArgs *args) { } int Game::pge_o_unk0x6C(ObjectOpcodeArgs *args) { - LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->counter_values[0]); + LivePGE *pge = col_findPiege(&_pgeLive[0], args->pge->init_PGE->data[0]); if (pge) { if (pge->life <= args->pge->life) { pge_sendMessage(args->pge->index, pge->index, args->a); @@ -1592,7 +1595,7 @@ int Game::pge_o_unk0x6E(ObjectOpcodeArgs *args) { MessagePGE *le = _pge_messagesTable[args->pge->index]; while (le) { if (args->a == le->msg_num) { - pge_updateInventory(&_pgeLive[le->index], args->pge); + pge_updateInventory(&_pgeLive[le->src_pge], args->pge); return 0xFFFF; } le = le->next_entry; @@ -1606,7 +1609,7 @@ int Game::pge_o_unk0x6F(ObjectOpcodeArgs *args) { MessagePGE *le = _pge_messagesTable[pge->index]; while (le) { if (args->a == le->msg_num) { - pge_sendMessage(pge->index, le->index, 0xC); + pge_sendMessage(pge->index, le->src_pge, 0xC); return 1; } le = le->next_entry; @@ -1685,7 +1688,7 @@ int Game::pge_op_isBelowConrad(ObjectOpcodeArgs *args) { if ((pge_conrad->pos_y - 8) / 72 < _si->pos_y / 72) { return 0xFFFF; } - } else if (!(_si->room_location & 0x80) && _si->room_location < 0x40) { + } else if (_si->room_location < 0x40) { if (pge_conrad->room_location == _res._ctData[CT_UP_ROOM + _si->room_location]) { return 0xFFFF; } @@ -1700,7 +1703,7 @@ int Game::pge_op_isAboveConrad(ObjectOpcodeArgs *args) { if ((pge_conrad->pos_y - 8) / 72 > _si->pos_y / 72) { return 0xFFFF; } - } else if (!(_si->room_location & 0x80) && _si->room_location < 0x40) { + } else if (_si->room_location < 0x40) { if (pge_conrad->room_location == _res._ctData[CT_DOWN_ROOM + _si->room_location]) { return 0xFFFF; } @@ -1735,7 +1738,7 @@ int Game::pge_op_isNotFacingConrad(ObjectOpcodeArgs *args) { } } } else if (args->a == 0) { - if (!(pge->room_location & 0x80) && pge->room_location < 0x40) { + if (pge->room_location < 0x40) { if (_pge_currentPiegeFacingDir) { if (pge_conrad->room_location == _res._ctData[CT_RIGHT_ROOM + pge->room_location]) return 0xFFFF; @@ -1776,7 +1779,7 @@ int Game::pge_op_isFacingConrad(ObjectOpcodeArgs *args) { } } } else if (args->a == 0) { - if (!(pge->room_location & 0x80) && pge->room_location < 0x40) { + if (pge->room_location < 0x40) { if (_pge_currentPiegeFacingDir) { if (pge_conrad->room_location == _res._ctData[CT_LEFT_ROOM + pge->room_location]) return 0xFFFF; @@ -1842,7 +1845,7 @@ int Game::pge_o_unk0x7F(ObjectOpcodeArgs *args) { CollisionSlot *slot = _col_slotsTable[var4]; while (slot) { if (slot->live_pge != args->pge) { - if (slot->live_pge->init_PGE->object_type == 3 && var2 != slot->live_pge->unkF) { + if (slot->live_pge->init_PGE->object_type == 3 && var2 != slot->live_pge->ref_inventory_PGE) { return 0; } } @@ -1856,7 +1859,7 @@ int Game::pge_o_unk0x7F(ObjectOpcodeArgs *args) { } int Game::pge_op_setPiegePosX(ObjectOpcodeArgs *args) { - uint8_t pge_num = args->pge->unkF; + uint8_t pge_num = args->pge->ref_inventory_PGE; if (pge_num != 0xFF) { args->pge->pos_x = _pgeLive[pge_num].pos_x; } @@ -1864,7 +1867,7 @@ int Game::pge_op_setPiegePosX(ObjectOpcodeArgs *args) { } int Game::pge_op_setPiegePosModX(ObjectOpcodeArgs *args) { - uint8_t pge_num = args->pge->unkF; + uint8_t pge_num = args->pge->ref_inventory_PGE; if (pge_num != 0xFF) { int16_t dx = _pgeLive[pge_num].pos_x % 256; if (dx >= args->pge->pos_x) { @@ -1877,30 +1880,27 @@ int Game::pge_op_setPiegePosModX(ObjectOpcodeArgs *args) { // taxi and teleporter int Game::pge_op_changeRoom(ObjectOpcodeArgs *args) { + // pge_op_protectionScreen InitPGE *init_pge_1 = args->pge->init_PGE; - assert(args->a >= 0 && args->a < 3); - const int16_t _ax = init_pge_1->counter_values[args->a]; - if (_ax == 0 && !g_options.bypass_protection && !g_options.use_words_protection && !_res.isMac()) { + const int16_t _ax = init_pge_1->data[args->a]; + if (_ax == 0 && !g_options.bypass_protection && !g_options.use_words_protection && (_res.isAmiga() || _res.isDOS())) { if (!handleProtectionScreenShape()) { - warning("Game::pge_op_changeRoom() protection check failed"); - // when protection check fails, the changeRoom opcode is disabled, - // rendering the teleporter unusable. - // // _pge_opcodeTable[0x82] = &Game::pge_op_nop; - // _pge_opTempVar1 = 0xFFFF; + // _pge_opGunVar = 0; // return; } } - const int16_t _bx = init_pge_1->counter_values[args->a + 1]; + // pge_op_changeRoom + const int16_t _bx = init_pge_1->data[args->a + 1]; LivePGE *live_pge_1 = &_pgeLive[_bx]; LivePGE *live_pge_2 = &_pgeLive[_ax]; int8_t pge_room = live_pge_1->room_location; if (pge_room >= 0 && pge_room < 0x40) { - int8_t _al = live_pge_2->room_location; + const int8_t room = live_pge_2->room_location; live_pge_2->pos_x = live_pge_1->pos_x; live_pge_2->pos_y = live_pge_1->pos_y; live_pge_2->room_location = live_pge_1->room_location; - pge_addToCurrentRoomList(live_pge_2, _al); + pge_addToCurrentRoomList(live_pge_2, room); InitPGE *init_pge_2 = live_pge_2->init_PGE; init_pge_1 = live_pge_1->init_PGE; if (init_pge_2->obj_node_number == init_pge_1->obj_node_number) { @@ -1962,7 +1962,7 @@ int Game::pge_o_unk0x86(ObjectOpcodeArgs *args) { int Game::pge_op_playSoundGroup(ObjectOpcodeArgs *args) { assert(args->a < 4); - uint16_t c = args->pge->init_PGE->counter_values[args->a]; + uint16_t c = args->pge->init_PGE->data[args->a]; uint8_t sfxId = c & 0xFF; uint8_t softVol = c >> 8; playSound(sfxId, softVol); @@ -1978,13 +1978,13 @@ int Game::pge_op_adjustPos(ObjectOpcodeArgs *args) { return 0xFFFF; } -int Game::pge_op_setTempVar1(ObjectOpcodeArgs *args) { - _pge_opTempVar1 = args->a; +int Game::pge_op_setGunVar(ObjectOpcodeArgs *args) { + _pge_opGunVar = args->a; return 0xFFFF; } -int Game::pge_op_isTempVar1Set(ObjectOpcodeArgs *args) { - if (_pge_opTempVar1 != args->a) { +int Game::pge_op_compareGunVar(ObjectOpcodeArgs *args) { + if (_pge_opGunVar != args->a) { return 0; } else { return 0xFFFF; @@ -1992,7 +1992,7 @@ int Game::pge_op_isTempVar1Set(ObjectOpcodeArgs *args) { } int Game::pge_setCurrentInventoryObject(LivePGE *pge) { - LivePGE *_bx = pge_getInventoryItemBefore(&_pgeLive[0], pge); + LivePGE *_bx = pge_getPreviousInventoryItem(&_pgeLive[0], pge); if (_bx == &_pgeLive[0]) { if (_bx->current_inventory_PGE != pge->index) { return 0; @@ -2008,17 +2008,17 @@ int Game::pge_setCurrentInventoryObject(LivePGE *pge) { } void Game::pge_updateInventory(LivePGE *pge1, LivePGE *pge2) { - if (pge2->unkF != 0xFF) { + if (pge2->ref_inventory_PGE != 0xFF) { pge_reorderInventory(pge2); } - LivePGE *_ax = pge_getInventoryItemBefore(pge1, 0); + LivePGE *_ax = pge_getPreviousInventoryItem(pge1, 0); pge_addToInventory(_ax, pge2, pge1); } void Game::pge_reorderInventory(LivePGE *pge) { - if (pge->unkF != 0xFF) { - LivePGE *_bx = &_pgeLive[pge->unkF]; - LivePGE *_di = pge_getInventoryItemBefore(_bx, pge); + if (pge->ref_inventory_PGE != 0xFF) { + LivePGE *_bx = &_pgeLive[pge->ref_inventory_PGE]; + LivePGE *_di = pge_getPreviousInventoryItem(_bx, pge); if (_di == _bx) { if (_di->current_inventory_PGE == pge->index) { pge_removeFromInventory(_di, pge, _bx); @@ -2031,7 +2031,7 @@ void Game::pge_reorderInventory(LivePGE *pge) { } } -LivePGE *Game::pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge) { +LivePGE *Game::pge_getPreviousInventoryItem(LivePGE *pge, LivePGE *last_pge) { LivePGE *_di = pge; uint8_t n = _di->current_inventory_PGE; while (n != 0xFF) { @@ -2047,7 +2047,7 @@ LivePGE *Game::pge_getInventoryItemBefore(LivePGE *pge, LivePGE *last_pge) { } void Game::pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) { - pge2->unkF = pge3->index; + pge2->ref_inventory_PGE = pge3->index; if (pge1 == pge3) { pge2->next_inventory_PGE = pge1->current_inventory_PGE; pge1->current_inventory_PGE = pge2->index; @@ -2057,9 +2057,9 @@ void Game::pge_addToInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) { } } -int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) { - uint8_t pge_unk1C = pge->init_PGE->unk1C; - if (!(pge->room_location & 0x80) && pge->room_location < 0x40) { +int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t value) { + const uint8_t pge_collision_data_len = pge->init_PGE->collision_data_len; + if (pge->room_location < 0x40) { int8_t *grid_data = &_res._ctData[0x100] + 0x70 * pge->room_location; int16_t pge_pos_y = ((pge->pos_y / 36) & ~1) + pge_dy; int16_t pge_pos_x = (pge->pos_x + 8) >> 4; @@ -2069,14 +2069,14 @@ int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) { CollisionSlot2 *slot1 = _col_slots2Next; int16_t i = 255; if (_pge_currentPiegeFacingDir) { - i = pge_unk1C - 1; + i = pge_collision_data_len - 1; grid_data -= i; } while (slot1) { if (slot1->unk2 == grid_data) { - slot1->data_size = pge_unk1C - 1; - assert(pge_unk1C < 0x70); - memset(grid_data, var8, pge_unk1C); + slot1->data_size = pge_collision_data_len - 1; + assert(pge_collision_data_len < 0x70); + memset(grid_data, value, pge_collision_data_len); return 1; } else { ++i; @@ -2089,14 +2089,14 @@ int Game::pge_updateCollisionState(LivePGE *pge, int16_t pge_dy, uint8_t var8) { if (_col_slots2Cur < &_col_slots2[255]) { slot1 = _col_slots2Cur; slot1->unk2 = grid_data; - slot1->data_size = pge_unk1C - 1; + slot1->data_size = pge_collision_data_len - 1; uint8_t *dst = &slot1->data_buf[0]; int8_t *src = grid_data; - int n = pge_unk1C; + int n = pge_collision_data_len; assert(n < 0x10); while (n--) { *dst++ = *src; - *src++ = var8; + *src++ = value; } ++_col_slots2Cur; slot1->next_slot = _col_slots2Next; @@ -2147,10 +2147,16 @@ void Game::pge_sendMessage(uint8_t src_pge_index, uint8_t dst_pge_index, int16_t if (pge_room != pge->room_location) { return; } - if (dst_pge_index == 0 && _blinkingConradCounter != 0) { + if (dst_pge_index == 0 && (_blinkingConradCounter != 0 || (_cheats & kCheatNoHit) != 0)) { return; } - // XXX + if (_stub->_pi.dbgMask & PlayerInput::DF_AUTOZOOM) { + const int type = _pgeLive[dst_pge_index].init_PGE->object_type; + if (type == 1 || type == 10) { + _pge_zoomPiegeNum = dst_pge_index; + _pge_zoomCounter = 0; + } + } } MessagePGE *le = _pge_nextFreeMessage; if (le) { @@ -2159,13 +2165,13 @@ void Game::pge_sendMessage(uint8_t src_pge_index, uint8_t dst_pge_index, int16_t MessagePGE *next = _pge_messagesTable[dst_pge_index]; _pge_messagesTable[dst_pge_index] = le; le->next_entry = next; - le->index = src_pge_index; + le->src_pge = src_pge_index; le->msg_num = num; } } void Game::pge_removeFromInventory(LivePGE *pge1, LivePGE *pge2, LivePGE *pge3) { - pge2->unkF = 0xFF; + pge2->ref_inventory_PGE = 0xFF; if (pge3 == pge1) { pge3->current_inventory_PGE = pge2->next_inventory_PGE; pge2->next_inventory_PGE = 0xFF; @@ -2269,3 +2275,69 @@ int Game::pge_ZOrderIfTypeAndDifferentDirection(LivePGE *pge1, LivePGE *pge2, ui int Game::pge_ZOrderByNumber(LivePGE *pge1, LivePGE *pge2, uint8_t comp, uint8_t comp2) { return pge1 - pge2; } + +static int pge_zoomDx(int prev_x, int cur_x) { + int dx = ABS(cur_x - prev_x); + if (dx < 4) { + dx = 1; + } else if (dx < 8) { + dx = 2; + } else if (dx < 16) { + dx = 4; + } else { + dx = 8; + } + return (prev_x < cur_x) ? dx : -dx; +} + +static int pge_zoomDy(int prev_y, int cur_y, bool flag) { + int dy = ABS(cur_y - prev_y); + if (flag) { + if (dy < 2) { + return 0; + } + } else { + if (dy < 4) { + return 0; + } + } + if (dy < 8) { + dy = 2; + } else if (dy < 16) { + dy = 4; + } else { + dy = 8; + } + return (prev_y < cur_y) ? dy : -dy; +} + +void Game::pge_updateZoom() { + static const int kZoomW = Video::GAMESCREEN_W / 2; + static const int kZoomH = Video::GAMESCREEN_H / 2; + if (_pge_zoomPiegeNum != 0) { + LivePGE *pge = &_pgeLive[_pge_zoomPiegeNum]; + if (pge->room_location != _currentRoom) { + _pge_zoomPiegeNum = 0; + } else if (_pge_zoomCounter < 30) { + int x = pge->pos_x + ((_pgeLive[0].flags & 1) ? 22 - kZoomW : -12); + x = CLIP(x, 0, Video::GAMESCREEN_W - kZoomW); + if (_pge_zoomCounter != 0 && _pge_zoomX != x) { + const int dx = pge_zoomDx(_pge_zoomX, x); + x = _pge_zoomX + dx; + } + _pge_zoomX = x; + int y = pge->pos_y - 24 - kZoomH / 2; + y = CLIP(y, 0, Video::GAMESCREEN_H - kZoomH); + if (_pge_zoomCounter != 0 && _pge_zoomY != y) { + const int dy = pge_zoomDy(_pge_zoomY, y, (pge->ref_inventory_PGE != 0xFF)); + y = _pge_zoomY + dy; + } + _pge_zoomY = y; + _stub->zoomRect(x, y, kZoomW, kZoomH); + } + ++_pge_zoomCounter; + if (_pge_zoomCounter == 40) { + _pge_zoomPiegeNum = 0; + } + } +} diff --git a/resource.cpp b/resource.cpp index df7c6b0..1a25940 100644 --- a/resource.cpp +++ b/resource.cpp @@ -1064,7 +1064,7 @@ void Resource::load_PGE(File *f) { pge->obj_node_number = f->readUint16LE(); pge->life = f->readUint16LE(); for (int lc = 0; lc < 4; ++lc) { - pge->counter_values[lc] = f->readUint16LE(); + pge->data[lc] = f->readUint16LE(); } pge->object_type = f->readByte(); pge->init_room = f->readByte(); @@ -1076,7 +1076,7 @@ void Resource::load_PGE(File *f) { pge->skill = f->readByte(); pge->mirror_x = f->readByte(); pge->flags = f->readByte(); - pge->unk1C = f->readByte(); + pge->collision_data_len = f->readByte(); f->readByte(); pge->text_num = f->readUint16LE(); } @@ -1095,7 +1095,7 @@ void Resource::decodePGE(const uint8_t *p, int size) { pge->obj_node_number = _readUint16(p); p += 2; pge->life = _readUint16(p); p += 2; for (int lc = 0; lc < 4; ++lc) { - pge->counter_values[lc] = _readUint16(p); p += 2; + pge->data[lc] = _readUint16(p); p += 2; } pge->object_type = *p++; pge->init_room = *p++; @@ -1107,7 +1107,7 @@ void Resource::decodePGE(const uint8_t *p, int size) { pge->skill = *p++; pge->mirror_x = *p++; pge->flags = *p++; - pge->unk1C = *p++; + pge->collision_data_len = *p++; ++p; pge->text_num = _readUint16(p); p += 2; } @@ -1417,6 +1417,10 @@ uint8_t *Resource::loadBankData(uint16_t num) { dataOffset &= 0xFFFF; } const int size = getBankDataSize(num); + if (size == 0) { + warning("Invalid bank data %d", num); + return _bankDataHead; + } const int avail = _bankDataTail - _bankDataHead; if (avail < size) { clearBankData(); diff --git a/resource_aba.cpp b/resource_aba.cpp index 1ef30c7..a930a95 100644 --- a/resource_aba.cpp +++ b/resource_aba.cpp @@ -15,7 +15,7 @@ ResourceAba::~ResourceAba() { } static int compareAbaEntry(const void *a, const void *b) { - return strcasecmp(((ResourceAbaEntry *)a)->name, ((ResourceAbaEntry *)b)->name); + return strcasecmp(((const ResourceAbaEntry *)a)->name, ((const ResourceAbaEntry *)b)->name); } void ResourceAba::readEntries(const char *aba) { diff --git a/rs.cfg b/rs.cfg index bdb992a..2e90fd8 100644 --- a/rs.cfg +++ b/rs.cfg @@ -19,14 +19,14 @@ use_text_cutscenes=false # enable playback of PC CD .SEQ cutscenes (use polygon cutscenes if false) use_seq_cutscenes=true -# if copy protection is enabled, display the words manual lookup screen (as in DOS SSI version). +# if copy protection is enabled, display the words manual lookup screen (as in DOS SSI version) use_words_protection=false # white t-shirt for Conrad use_white_tshirt=false # enable playback of 'ASC' cutscene -play_asc_cutscene=true +play_asc_cutscene=false # enable playback of 'CAILLOU-F.SET' cutscene play_caillou_cutscene=true @@ -45,3 +45,6 @@ play_gamesaved_sound=true # restore content from 'MEMO' cutscene restore_memo_cutscene=true + +# order inventory items as the original game, last grabbed item is at the bottom of the inventory +order_inventory_original=false diff --git a/screenshot.cpp b/screenshot.cpp index 2b56622..4feb5b0 100644 --- a/screenshot.cpp +++ b/screenshot.cpp @@ -2,83 +2,29 @@ #include "screenshot.h" #include "file.h" -#define kTgaImageTypeUncompressedTrueColor 2 -#define kTgaImageTypeRunLengthEncodedTrueColor 10 -#define kTgaDirectionTop (1 << 5) - -void saveTGA(const char *filename, const uint8_t *rgba, int w, int h) { - static const uint8_t kImageType = kTgaImageTypeRunLengthEncodedTrueColor; - File f; - if (f.open(filename, "wb", ".")) { - - f.writeByte(0); // ID Length - f.writeByte(0); // ColorMap Type - f.writeByte(kImageType); - f.writeUint16LE(0); // ColorMap Start - f.writeUint16LE(0); // ColorMap Length - f.writeByte(0); // ColorMap Bits - f.writeUint16LE(0); // X-origin - f.writeUint16LE(0); // Y-origin - f.writeUint16LE(w); // Image Width - f.writeUint16LE(h); // Image Height - f.writeByte(24); // Pixel Depth - f.writeByte(kTgaDirectionTop); // Descriptor - - if (kImageType == kTgaImageTypeUncompressedTrueColor) { - for (int i = 0; i < w * h; ++i) { - f.writeByte(rgba[0]); - f.writeByte(rgba[1]); - f.writeByte(rgba[2]); - rgba += 4; - } - } else { - assert(kImageType == kTgaImageTypeRunLengthEncodedTrueColor); - int prevColor = rgba[2] + (rgba[1] << 8) + (rgba[0] << 16); rgba += 4; - int count = 0; - for (int i = 1; i < w * h; ++i) { - int color = rgba[2] + (rgba[1] << 8) + (rgba[0] << 16); rgba += 4; - if (prevColor == color && count < 127) { - ++count; - continue; - } - f.writeByte(count | 0x80); - f.writeByte((prevColor >> 16) & 255); - f.writeByte((prevColor >> 8) & 255); - f.writeByte( prevColor & 255); - count = 0; - prevColor = color; - } - if (count != 0) { - f.writeByte(count | 0x80); - f.writeByte((prevColor >> 16) & 255); - f.writeByte((prevColor >> 8) & 255); - f.writeByte( prevColor & 255); - } - } - } -} - static const uint16_t TAG_BM = 0x4D42; void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int w, int h) { File f; if (f.open(filename, "wb", ".")) { - const int alignWidth = (w + 3) & ~3; + const int paletteSize = pal ? 4 * 256 : 0; + const int bitsCount = pal ? 8 : 32; + const int alignWidth = ((w * bitsCount / 8) + 3) & ~3; const int imageSize = alignWidth * h; // Write file header f.writeUint16LE(TAG_BM); - f.writeUint32LE(14 + 40 + 4 * 256 + imageSize); + f.writeUint32LE(14 + 40 + paletteSize + imageSize); f.writeUint16LE(0); // reserved1 f.writeUint16LE(0); // reserved2 - f.writeUint32LE(14 + 40 + 4 * 256); + f.writeUint32LE(14 + 40 + paletteSize); // Write info header f.writeUint32LE(40); f.writeUint32LE(w); f.writeUint32LE(h); f.writeUint16LE(1); // planes - f.writeUint16LE(8); // bit_count + f.writeUint16LE(bitsCount); // bit_count f.writeUint32LE(0); // compression f.writeUint32LE(imageSize); // size_image f.writeUint32LE(0); // x_pels_per_meter @@ -86,16 +32,19 @@ void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int f.writeUint32LE(0); // num_colors_used f.writeUint32LE(0); // num_colors_important - // Write palette data - for (int i = 0; i < 256; ++i) { - f.writeByte(pal[2]); - f.writeByte(pal[1]); - f.writeByte(pal[0]); - f.writeByte(0); - pal += 3; + if (pal) { + // Write palette data + for (int i = 0; i < 256; ++i) { + f.writeByte(pal[2]); + f.writeByte(pal[1]); + f.writeByte(pal[0]); + f.writeByte(0); + pal += 3; + } } // Write bitmap data + w *= bitsCount / 8; const int pitch = w; bits += h * pitch; for (int i = 0; i < h; ++i) { diff --git a/screenshot.h b/screenshot.h index b569b11..3474681 100644 --- a/screenshot.h +++ b/screenshot.h @@ -4,7 +4,6 @@ #include -void saveTGA(const char *filename, const uint8_t *rgb, int w, int h); void saveBMP(const char *filename, const uint8_t *bits, const uint8_t *pal, int w, int h); #endif diff --git a/seq_player.cpp b/seq_player.cpp index 8fbe468..be45185 100644 --- a/seq_player.cpp +++ b/seq_player.cpp @@ -57,9 +57,7 @@ bool SeqDemuxer::readFrameData() { } _f->seek(_frameOffset); _audioDataOffset = _f->readUint16LE(); - _audioDataSize = (_audioDataOffset != 0) ? kAudioBufferSize * 2 : 0; _paletteDataOffset = _f->readUint16LE(); - _paletteDataSize = (_paletteDataOffset != 0) ? 768 : 0; uint8_t num[4]; for (int i = 0; i < 4; ++i) { num[i] = _f->readByte(); @@ -220,6 +218,7 @@ SeqPlayer::SeqPlayer(SystemStub *stub, Mixer *mixer) : _stub(stub), _buf(0), _mix(mixer) { _soundQueuePreloadSize = 0; _soundQueue = 0; + _soundQueueTail = 0; } SeqPlayer::~SeqPlayer() { @@ -242,7 +241,7 @@ void SeqPlayer::play(File *f) { if (!_demux.readFrameData()) { break; } - if (_demux._audioDataSize != 0) { + if (_demux._audioDataOffset != 0) { SoundBufferQueue *sbq = (SoundBufferQueue *)malloc(sizeof(SoundBufferQueue)); if (sbq) { sbq->data = (int16_t *)calloc(SeqDemuxer::kAudioBufferSize, sizeof(int16_t)); @@ -258,25 +257,23 @@ void SeqPlayer::play(File *f) { } if (sbq) { LockAudioStack las(_stub); - if (!_soundQueue) { - _soundQueue = sbq; + if (_soundQueueTail) { + _soundQueueTail->next = sbq; } else { - SoundBufferQueue *p = _soundQueue; - while (p->next) { - p = p->next; - } - p->next = sbq; + assert(!_soundQueue); + _soundQueue = sbq; } + _soundQueueTail = sbq; if (_soundQueuePreloadSize < kSoundPreloadSize) { ++_soundQueuePreloadSize; } } } - if (_demux._paletteDataSize != 0) { + if (_demux._paletteDataOffset != 0) { uint8_t buf[256 * 3]; _demux.readPalette(buf); for (int i = 0; i < 256 * 3; ++i) { - buf[i] = (buf[i] << 2) | (buf[i] & 3); + buf[i] = (buf[i] << 2) | (buf[i] >> 4); } _stub->setPalette(buf, 256); } @@ -326,6 +323,7 @@ void SeqPlayer::play(File *f) { free(_soundQueue); _soundQueue = next; } + _soundQueueTail = 0; _soundQueuePreloadSize = 0; } } @@ -335,15 +333,20 @@ bool SeqPlayer::mix(int16_t *buf, int samples) { return true; } while (_soundQueue && samples > 0) { - *buf++ = _soundQueue->data[_soundQueue->read]; - ++_soundQueue->read; + const int count = MIN(samples, _soundQueue->size - _soundQueue->read); + memcpy(buf, _soundQueue->data + _soundQueue->read, count * sizeof(int16_t)); + buf += count; + _soundQueue->read += count; if (_soundQueue->read == _soundQueue->size) { SoundBufferQueue *next = _soundQueue->next; free(_soundQueue->data); free(_soundQueue); _soundQueue = next; } - --samples; + samples -= count; + } + if (!_soundQueue) { + _soundQueueTail = 0; } return true; } diff --git a/seq_player.h b/seq_player.h index dc8f8f8..797da23 100644 --- a/seq_player.h +++ b/seq_player.h @@ -32,9 +32,7 @@ struct SeqDemuxer { int _frameOffset; int _audioDataOffset; - int _audioDataSize; int _paletteDataOffset; - int _paletteDataSize; int _videoData; struct { int size; @@ -74,7 +72,7 @@ struct SeqPlayer { Mixer *_mix; SeqDemuxer _demux; int _soundQueuePreloadSize; - SoundBufferQueue *_soundQueue; + SoundBufferQueue *_soundQueue, *_soundQueueTail; }; #endif // SEQ_PLAYER_H__ diff --git a/staticres.cpp b/staticres.cpp index 56c1a50..4da0d96 100644 --- a/staticres.cpp +++ b/staticres.cpp @@ -3585,7 +3585,7 @@ const Game::pge_OpcodeProc Game::_pge_opcodeTable[] = { /* 0x48 */ &Game::pge_o_unk0x48, &Game::pge_o_unk0x49, - &Game::pge_o_unk0x4A, + &Game::pge_op_killInventoryPiege, &Game::pge_op_killPiege, /* 0x4C */ &Game::pge_op_isInCurrentRoom, @@ -3606,7 +3606,7 @@ const Game::pge_OpcodeProc Game::_pge_opcodeTable[] = { &Game::pge_op_setLifeCounter, &Game::pge_op_decLifeCounter, &Game::pge_op_playCutscene, - &Game::pge_op_isTempVar2Set, + &Game::pge_op_compareUnkVar, /* 0x5C */ &Game::pge_op_playDeathCutscene, &Game::pge_o_unk0x5D, @@ -3665,8 +3665,8 @@ const Game::pge_OpcodeProc Game::_pge_opcodeTable[] = { /* 0x88 */ &Game::pge_op_adjustPos, 0, - &Game::pge_op_setTempVar1, - &Game::pge_op_isTempVar1Set + &Game::pge_op_setGunVar, + &Game::pge_op_compareGunVar }; const uint8_t Game::_pge_modKeysTable[] = { diff --git a/systemstub.h b/systemstub.h index 6dd0802..cb8af31 100644 --- a/systemstub.h +++ b/systemstub.h @@ -20,7 +20,8 @@ struct PlayerInput { enum { DF_FASTMODE = 1 << 0, DF_DBLOCKS = 1 << 1, - DF_SETLIFE = 1 << 2 + DF_SETLIFE = 1 << 2, + DF_AUTOZOOM = 1 << 3 }; uint8_t dirMask; @@ -68,6 +69,7 @@ struct SystemStub { virtual void setOverscanColor(int i) = 0; virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch) = 0; virtual void copyRectRgb24(int x, int y, int w, int h, const uint8_t *rgb) = 0; + virtual void zoomRect(int x, int y, int h, int w) = 0; virtual void copyWidescreenLeft(int w, int h, const uint8_t *buf) = 0; virtual void copyWidescreenRight(int w, int h, const uint8_t *buf) = 0; virtual void copyWidescreenMirror(int w, int h, const uint8_t *buf) = 0; @@ -99,6 +101,22 @@ struct LockAudioStack { SystemStub *_stub; }; +struct ToggleWidescreenStack { + ToggleWidescreenStack(SystemStub *stub, bool state) + : _stub(stub), _state(state) { + if (_stub->hasWidescreen()) { + _stub->enableWidescreen(_state); + } + } + ~ToggleWidescreenStack() { + if (_stub->hasWidescreen()) { + _stub->enableWidescreen(!_state); + } + } + SystemStub *_stub; + bool _state; +}; + extern SystemStub *SystemStub_SDL_create(); #endif // SYSTEMSTUB_H__ diff --git a/systemstub_sdl.cpp b/systemstub_sdl.cpp index 407d18e..19df072 100644 --- a/systemstub_sdl.cpp +++ b/systemstub_sdl.cpp @@ -31,6 +31,7 @@ struct SystemStub_SDL : SystemStub { SDL_Renderer *_renderer; SDL_Texture *_texture; int _texW, _texH; + SDL_Rect _texRect; SDL_GameController *_controller; SDL_PixelFormat *_fmt; const char *_caption; @@ -66,6 +67,7 @@ struct SystemStub_SDL : SystemStub { virtual void setOverscanColor(int i); virtual void copyRect(int x, int y, int w, int h, const uint8_t *buf, int pitch); virtual void copyRectRgb24(int x, int y, int w, int h, const uint8_t *rgb); + virtual void zoomRect(int x, int y, int w, int h); virtual void copyWidescreenLeft(int w, int h, const uint8_t *buf); virtual void copyWidescreenRight(int w, int h, const uint8_t *buf); virtual void copyWidescreenMirror(int w, int h, const uint8_t *buf); @@ -98,7 +100,7 @@ SystemStub *SystemStub_SDL_create() { } void SystemStub_SDL::init(const char *title, int w, int h, bool fullscreen, int widescreenMode, const ScalerParameters *scalerParameters) { - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK); + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); SDL_ShowCursor(SDL_DISABLE); _caption = title; memset(&_pi, 0, sizeof(_pi)); @@ -269,6 +271,16 @@ void SystemStub_SDL::copyRectRgb24(int x, int y, int w, int h, const uint8_t *rg } } +void SystemStub_SDL::zoomRect(int x, int y, int w, int h) { + if (_pi.dbgMask & PlayerInput::DF_DBLOCKS) { + drawRect(x, y, w, h, 0xE7); + } + _texRect.x = x * _texW / _screenW; + _texRect.y = y * _texH / _screenH; + _texRect.w = w * _texW / _screenW; + _texRect.h = h * _texH / _screenH; +} + static void clearTexture(SDL_Texture *texture, int h, SDL_PixelFormat *fmt) { void *dst = 0; int pitch = 0; @@ -502,7 +514,7 @@ void SystemStub_SDL::updateScreen(int shakeOffset) { SDL_RenderGetLogicalSize(_renderer, &r.w, &r.h); r.x = (r.w - _texW) / 2; r.w = _texW; - SDL_RenderCopy(_renderer, _texture, 0, &r); + SDL_RenderCopy(_renderer, _texture, &_texRect, &r); } else { if (_fadeOnUpdateScreen) { SDL_SetRenderDrawBlendMode(_renderer, SDL_BLENDMODE_BLEND); @@ -524,9 +536,13 @@ void SystemStub_SDL::updateScreen(int shakeOffset) { r.x = 0; r.y = shakeOffset * _scaleFactor; SDL_RenderGetLogicalSize(_renderer, &r.w, &r.h); - SDL_RenderCopy(_renderer, _texture, 0, &r); + SDL_RenderCopy(_renderer, _texture, &_texRect, &r); } SDL_RenderPresent(_renderer); + _texRect.x = 0; + _texRect.y = 0; + _texRect.w = _texW; + _texRect.h = _texH; } void SystemStub_SDL::processEvents() { @@ -740,8 +756,8 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) { break; case SDLK_s: { char name[32]; - snprintf(name, sizeof(name), "screenshot-%03d.tga", _screenshot); - saveTGA(name, (const uint8_t *)_screenBuffer, _screenW, _screenH); + snprintf(name, sizeof(name), "screenshot-%03d.bmp", _screenshot); + saveBMP(name, (const uint8_t *)_screenBuffer, 0, _screenW, _screenH); ++_screenshot; debug(DBG_INFO, "Written '%s'", name); } @@ -771,6 +787,10 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) { case SDLK_r: _pi.rewind = true; break; + case SDLK_g: + _pi.dbgMask ^= PlayerInput::DF_AUTOZOOM; + debug(DBG_INFO, "Auto zoom %s", (_pi.dbgMask & PlayerInput::DF_AUTOZOOM) ? "enabled" : "disabled"); + break; case SDLK_KP_PLUS: case SDLK_PAGEUP: _pi.stateSlot = 1; @@ -785,15 +805,19 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) { setAsciiChar(_pi, &ev.key.keysym); switch (ev.key.keysym.sym) { case SDLK_LEFT: + case SDLK_KP_4: _pi.dirMask &= ~PlayerInput::DIR_LEFT; break; case SDLK_RIGHT: + case SDLK_KP_6: _pi.dirMask &= ~PlayerInput::DIR_RIGHT; break; case SDLK_UP: + case SDLK_KP_8: _pi.dirMask &= ~PlayerInput::DIR_UP; break; case SDLK_DOWN: + case SDLK_KP_2: _pi.dirMask &= ~PlayerInput::DIR_DOWN; break; case SDLK_SPACE: @@ -829,15 +853,19 @@ void SystemStub_SDL::processEvent(const SDL_Event &ev, bool &paused) { } switch (ev.key.keysym.sym) { case SDLK_LEFT: + case SDLK_KP_4: _pi.dirMask |= PlayerInput::DIR_LEFT; break; case SDLK_RIGHT: + case SDLK_KP_6: _pi.dirMask |= PlayerInput::DIR_RIGHT; break; case SDLK_UP: + case SDLK_KP_8: _pi.dirMask |= PlayerInput::DIR_UP; break; case SDLK_DOWN: + case SDLK_KP_2: _pi.dirMask |= PlayerInput::DIR_DOWN; break; case SDLK_BACKSPACE: @@ -935,6 +963,10 @@ void SystemStub_SDL::prepareGraphics() { _texH *= _scaleFactor; break; } + _texRect.x = 0; + _texRect.y = 0; + _texRect.w = _texW; + _texRect.h = _texH; int windowW = _screenW * _scaleFactor; int windowH = _screenH * _scaleFactor; int flags = 0; @@ -1061,7 +1093,7 @@ void SystemStub_SDL::setScaler(const ScalerParameters *parameters) { } } } - _scaleFactor = _scaler ? CLIP(parameters->factor, _scaler->factorMin, _scaler->factorMax) : 1; + _scaleFactor = _scaler ? CLIP(parameters->factor, _scaler->factorMin, _scaler->factorMax) : parameters->factor; } void SystemStub_SDL::changeScaler(int scalerNum) { diff --git a/video.cpp b/video.cpp index c950c83..92e1afe 100644 --- a/video.cpp +++ b/video.cpp @@ -1058,9 +1058,9 @@ void Video::fillRect(int x, int y, int w, int h, uint8_t color) { static void fixOffsetDecodeBuffer(DecodeBuffer *buf, const uint8_t *dataPtr) { if (buf->xflip) { - buf->x += (int16_t)READ_BE_UINT16(dataPtr + 4) - READ_BE_UINT16(dataPtr) - 1; + buf->x += (int16_t)READ_BE_UINT16(dataPtr + 4) - READ_BE_UINT16(dataPtr) - 1; } else { - buf->x -= (int16_t)READ_BE_UINT16(dataPtr + 4); + buf->x -= (int16_t)READ_BE_UINT16(dataPtr + 4); } buf->y -= (int16_t)READ_BE_UINT16(dataPtr + 6); }