/* 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. * * $URL$ * $Id$ * */ #include "common/endian.h" #include "common/events.h" #include "common/savefile.h" #include "cine/cine.h" #include "cine/main_loop.h" #include "cine/object.h" #include "cine/sound.h" #include "cine/bg_list.h" #include "cine/various.h" namespace Cine { bool disableSystemMenu = false; bool inMenu; int16 commandVar3[4]; int16 commandVar1; int16 commandVar2; //Message messageTable[NUM_MAX_MESSAGE]; uint16 var2; uint16 var3; uint16 var4; uint16 var5; int16 buildObjectListCommand(int16 param); int16 canUseOnObject = 0; void drawString(const char *string, byte param) { } void waitPlayerInput(void) { } void setTextWindow(uint16 param1, uint16 param2, uint16 param3, uint16 param4) { } uint16 errorVar; byte menuVar; bool fadeRequired; uint16 allowPlayerInput; uint16 checkForPendingDataLoadSwitch; uint16 isDrawCommandEnabled; uint16 waitForPlayerClick; uint16 menuCommandLen; bool _paletteNeedUpdate; uint16 _messageLen; byte _danKeysPressed; int16 playerCommand; Common::String commandBuffer; char currentPrcName[20]; char currentRelName[20]; char currentObjectName[20]; char currentMsgName[20]; char newPrcName[20]; char newRelName[20]; char newObjectName[20]; char newMsgName[20]; char currentCtName[15]; char currentPartName[15]; char currentDatName[30]; int16 saveVar2; byte isInPause = 0; /*! \brief Values used by the xMoveKeyb variable */ enum xMoveKeybEnums { kKeybMoveCenterX = 0, kKeybMoveRight = 1, kKeybMoveLeft = 2 }; /*! \brief Values used by the yMoveKeyb variable */ enum yMoveKeybEnums { kKeybMoveCenterY = 0, kKeybMoveDown = 1, kKeybMoveUp = 2 }; uint16 xMoveKeyb = kKeybMoveCenterX; bool egoMovedWithKeyboard = false; uint16 yMoveKeyb = kKeybMoveCenterY; SelectedObjStruct currentSelectedObject; static CommandeType currentSaveName[10]; int16 currentDisk; static const int16 choiceResultTable[] = { 1, 1, 1, 2, 1, 1, 1 }; static const int16 subObjectUseTable[] = { 3, 3, 3, 3, 3, 0, 0 }; static const int16 canUseOnItemTable[] = { 1, 0, 0, 1, 1, 0, 0 }; CommandeType objectListCommand[20]; int16 objListTab[20]; Common::Array zoneData; Common::Array zoneQuery; //!< Only exists in Operation Stealth /*! \brief Move the player character using the keyboard * \param x Negative values move left, positive right, zero not at all * \param y Negative values move down, positive up, zero not at all * NOTE: If both x and y are zero then the character stops * FIXME: This seems to only work in Operation Stealth. May need code changes somewhere else... */ void moveUsingKeyboard(int x, int y) { if (x > 0) { xMoveKeyb = kKeybMoveRight; } else if (x < 0) { xMoveKeyb = kKeybMoveLeft; } else { xMoveKeyb = kKeybMoveCenterX; } if (y > 0) { yMoveKeyb = kKeybMoveUp; } else if (y < 0) { yMoveKeyb = kKeybMoveDown; } else { yMoveKeyb = kKeybMoveCenterY; } egoMovedWithKeyboard = x || y; } void stopMusicAfterFadeOut(void) { // if (g_sfxPlayer->_fadeOutCounter != 0 && g_sfxPlayer->_fadeOutCounter < 100) { // g_sfxPlayer->stop(); // } } void runObjectScript(int16 entryIdx) { ScriptPtr tmp(scriptInfo->create(*relTable[entryIdx], entryIdx)); assert(tmp); objectScripts.push_back(tmp); } /*! \brief Add action result message to overlay list * \param cmd Message description * \todo Why are x, y, width and color left uninitialized? */ void addPlayerCommandMessage(int16 cmd) { overlay tmp; memset(&tmp, 0, sizeof(tmp)); tmp.objIdx = cmd; tmp.type = 3; overlayList.push_back(tmp); } int16 getRelEntryForObject(uint16 param1, uint16 param2, SelectedObjStruct *pSelectedObject) { int16 i; int16 found = -1; for (i = 0; i < (int16)relTable.size(); i++) { if (relTable[i]->_param1 == param1 && relTable[i]->_param2 == pSelectedObject->idx) { if (param2 == 1) { found = i; } else if (param2 == 2) { if (relTable[i]->_param3 == pSelectedObject->param) { found = i; } } } if (found != -1) break; } return found; } /*! \brief Find index of the object under cursor * \param x Mouse cursor coordinate * \param y Mouse cursor coordinate * \todo Fix displaced type 1 objects */ int16 getObjectUnderCursor(uint16 x, uint16 y) { Common::List::iterator it; int16 objX, objY, frame, part, threshold, height, xdif, ydif; int width; // reverse_iterator would be nice for (it = overlayList.reverse_begin(); it != overlayList.end(); --it) { if (it->type >= 2 || !objectTable[it->objIdx].name[0]) { continue; } objX = objectTable[it->objIdx].x; objY = objectTable[it->objIdx].y; frame = ABS((int16)(objectTable[it->objIdx].frame)); part = objectTable[it->objIdx].part; // Additional case for negative frame values in Operation Stealth if (g_cine->getGameType() == Cine::GType_OS && objectTable[it->objIdx].frame < 0) { if ((it->type == 1) && (x >= objX) && (objX + frame >= x) && (y >= objY) && (objY + part >= y)) { return it->objIdx; } else { continue; } } if (it->type == 0) { threshold = animDataTable[frame]._var1; } else { threshold = animDataTable[frame]._width / 2; } height = animDataTable[frame]._height; width = animDataTable[frame]._realWidth; xdif = x - objX; ydif = y - objY; if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif <= 0) || (ydif >= height) || !animDataTable[frame].data()) { continue; } if (g_cine->getGameType() == Cine::GType_OS) { // This test isn't present in Operation Stealth's PC version's disassembly // but removing it makes things crash sometimes (e.g. when selecting a verb // and moving the mouse cursor around the floor in the airport's bathroom). if (xdif >= width) { continue; } if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != (part & 0x0F)) { return it->objIdx; } else if (it->type == 1 && gfxGetBit(xdif, ydif, animDataTable[frame].data(), animDataTable[frame]._width * 4)) { return it->objIdx; } } else if (it->type == 0) { // use generated mask if (gfxGetBit(xdif, ydif, animDataTable[frame].mask(), animDataTable[frame]._width)) { return it->objIdx; } } else if (it->type == 1) { // is mask if (gfxGetBit(xdif, ydif, animDataTable[frame].data(), animDataTable[frame]._width * 4)) { return it->objIdx; } } } return -1; } bool writeChunkHeader(Common::OutSaveFile &out, const ChunkHeader &header) { out.writeUint32BE(header.id); out.writeUint32BE(header.version); out.writeUint32BE(header.size); return !out.ioFailed(); } bool loadChunkHeader(Common::SeekableReadStream &in, ChunkHeader &header) { header.id = in.readUint32BE(); header.version = in.readUint32BE(); header.size = in.readUint32BE(); return !in.ioFailed(); } void saveObjectTable(Common::OutSaveFile &out) { out.writeUint16BE(NUM_MAX_OBJECT); // Entry count out.writeUint16BE(0x20); // Entry size for (int i = 0; i < NUM_MAX_OBJECT; i++) { out.writeUint16BE(objectTable[i].x); out.writeUint16BE(objectTable[i].y); out.writeUint16BE(objectTable[i].mask); out.writeUint16BE(objectTable[i].frame); out.writeUint16BE(objectTable[i].costume); out.write(objectTable[i].name, 20); out.writeUint16BE(objectTable[i].part); } } void saveZoneData(Common::OutSaveFile &out) { for (int i = 0; i < 16; i++) { out.writeUint16BE(zoneData[i]); } } void saveCommandVariables(Common::OutSaveFile &out) { for (int i = 0; i < 4; i++) { out.writeUint16BE(commandVar3[i]); } } /*! \brief Save the 80 bytes long command buffer padded to that length with zeroes. */ void saveCommandBuffer(Common::OutSaveFile &out) { // Let's make sure there's space for the trailing zero // (That's why we subtract one from the maximum command buffer size here). uint32 size = MIN(commandBuffer.size(), kMaxCommandBufferSize - 1); out.write(commandBuffer.c_str(), size); // Write the rest as zeroes (Here we also write the string's trailing zero) for (uint i = 0; i < kMaxCommandBufferSize - size; i++) { out.writeByte(0); } } void saveAnimDataTable(Common::OutSaveFile &out) { out.writeUint16BE(NUM_MAX_ANIMDATA); // Entry count out.writeUint16BE(0x1E); // Entry size for (int i = 0; i < NUM_MAX_ANIMDATA; i++) { animDataTable[i].save(out); } } void saveScreenParams(Common::OutSaveFile &out) { // Screen parameters, unhandled out.writeUint16BE(0); out.writeUint16BE(0); out.writeUint16BE(0); out.writeUint16BE(0); out.writeUint16BE(0); out.writeUint16BE(0); } void saveGlobalScripts(Common::OutSaveFile &out) { ScriptList::const_iterator it; out.writeUint16BE(globalScripts.size()); for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { (*it)->save(out); } } void saveObjectScripts(Common::OutSaveFile &out) { ScriptList::const_iterator it; out.writeUint16BE(objectScripts.size()); for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { (*it)->save(out); } } void saveOverlayList(Common::OutSaveFile &out) { Common::List::const_iterator it; out.writeUint16BE(overlayList.size()); for (it = overlayList.begin(); it != overlayList.end(); ++it) { out.writeUint32BE(0); // next out.writeUint32BE(0); // previous? out.writeUint16BE(it->objIdx); out.writeUint16BE(it->type); out.writeSint16BE(it->x); out.writeSint16BE(it->y); out.writeSint16BE(it->width); out.writeSint16BE(it->color); } } void saveBgIncrustList(Common::OutSaveFile &out) { Common::List::const_iterator it; out.writeUint16BE(bgIncrustList.size()); for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { out.writeUint32BE(0); // next out.writeUint32BE(0); // previous? out.writeUint16BE(it->objIdx); out.writeUint16BE(it->param); out.writeUint16BE(it->x); out.writeUint16BE(it->y); out.writeUint16BE(it->frame); out.writeUint16BE(it->part); } } void saveZoneQuery(Common::OutSaveFile &out) { for (int i = 0; i < 16; i++) { out.writeUint16BE(zoneQuery[i]); } } void saveSeqList(Common::OutSaveFile &out) { Common::List::const_iterator it; out.writeUint16BE(seqList.size()); for (it = seqList.begin(); it != seqList.end(); ++it) { out.writeSint16BE(it->var4); out.writeUint16BE(it->objIdx); out.writeSint16BE(it->var8); out.writeSint16BE(it->frame); out.writeSint16BE(it->varC); out.writeSint16BE(it->varE); out.writeSint16BE(it->var10); out.writeSint16BE(it->var12); out.writeSint16BE(it->var14); out.writeSint16BE(it->var16); out.writeSint16BE(it->var18); out.writeSint16BE(it->var1A); out.writeSint16BE(it->var1C); out.writeSint16BE(it->var1E); } } bool CineEngine::loadSaveDirectory(void) { Common::InSaveFile *fHandle; char tmp[80]; snprintf(tmp, 80, "%s.dir", _targetName.c_str()); fHandle = g_saveFileMan->openForLoading(tmp); if (!fHandle) { return false; } fHandle->read(currentSaveName, 10 * 20); delete fHandle; return true; } /*! \brief Savegame format detector * \param fHandle Savefile to check * \return Savegame format on success, ANIMSIZE_UNKNOWN on failure * * This function seeks through the savefile and tries to determine the * savegame format it uses. There's a miniscule chance that the detection * algorithm could get confused and think that the file uses both the older * and the newer format but that is such a remote possibility that I wouldn't * worry about it at all. * * Also detects the temporary Operation Stealth savegame format now. */ enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle) { const uint32 prevStreamPos = fHandle.pos(); // First check for the temporary Operation Stealth savegame format. fHandle.seek(0); ChunkHeader hdr; loadChunkHeader(fHandle, hdr); fHandle.seek(prevStreamPos); if (hdr.id == TEMP_OS_FORMAT_ID) { return TEMP_OS_FORMAT; } // Ok, so the savegame isn't using the temporary Operation Stealth savegame format. // Let's check for the plain Future Wars savegame format and its different versions then. // The animDataTable begins at savefile position 0x2315. // Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443) // and 30 bytes in the save format after that (Revision 31444 and onwards). // There are 255 entries in the animDataTable in both of the savefile formats. static const uint animDataTableStart = 0x2315; static const uint animEntriesCount = 255; static const uint oldAnimEntrySize = 23; static const uint newAnimEntrySize = 30; static const uint animEntrySizeChoices[] = {oldAnimEntrySize, newAnimEntrySize}; Common::Array animEntrySizeMatches; // Try to walk through the savefile using different animDataTable entry sizes // and make a list of all the successful entry sizes. for (uint i = 0; i < ARRAYSIZE(animEntrySizeChoices); i++) { // 206 = 2 * 50 * 2 + 2 * 3 (Size of global and object script entries) // 20 = 4 * 2 + 2 * 6 (Size of overlay and background incrust entries) static const uint sizeofScreenParams = 2 * 6; static const uint globalScriptEntrySize = 206; static const uint objectScriptEntrySize = 206; static const uint overlayEntrySize = 20; static const uint bgIncrustEntrySize = 20; static const uint chainEntrySizes[] = { globalScriptEntrySize, objectScriptEntrySize, overlayEntrySize, bgIncrustEntrySize }; uint animEntrySize = animEntrySizeChoices[i]; // Jump over the animDataTable entries and the screen parameters int32 newPos = animDataTableStart + animEntrySize * animEntriesCount + sizeofScreenParams; // Check that there's data left after the point we're going to jump to if (newPos >= fHandle.size()) { continue; } fHandle.seek(newPos); // Jump over the remaining items in the savegame file // (i.e. the global scripts, object scripts, overlays and background incrusts). bool chainWalkSuccess = true; for (uint chainIndex = 0; chainIndex < ARRAYSIZE(chainEntrySizes); chainIndex++) { // Read entry count and jump over the entries int entryCount = fHandle.readSint16BE(); newPos = fHandle.pos() + chainEntrySizes[chainIndex] * entryCount; // Check that we didn't go past the end of file. // Note that getting exactly to the end of file is acceptable. if (newPos > fHandle.size()) { chainWalkSuccess = false; break; } fHandle.seek(newPos); } // If we could walk the chain successfully and // got exactly to the end of file then we've got a match. if (chainWalkSuccess && fHandle.pos() == fHandle.size()) { // We found a match, let's save it animEntrySizeMatches.push_back(animEntrySize); } } // Check that we got only one entry size match. // If we didn't, then return an error. enum CineSaveGameFormat result = ANIMSIZE_UNKNOWN; if (animEntrySizeMatches.size() == 1) { const uint animEntrySize = animEntrySizeMatches[0]; assert(animEntrySize == oldAnimEntrySize || animEntrySize == newAnimEntrySize); if (animEntrySize == oldAnimEntrySize) { result = ANIMSIZE_23; } else { // animEntrySize == newAnimEntrySize // Check data and mask pointers in all of the animDataTable entries // to see whether we've got the version with the broken data and mask pointers or not. // In the broken format all data and mask pointers were always zero. static const uint relativeDataPos = 2 * 4; bool pointersIntact = false; for (uint i = 0; i < animEntriesCount; i++) { fHandle.seek(animDataTableStart + i * animEntrySize + relativeDataPos); uint32 data = fHandle.readUint32BE(); uint32 mask = fHandle.readUint32BE(); if ((data != 0) || (mask != 0)) { pointersIntact = true; break; } } result = (pointersIntact ? ANIMSIZE_30_PTRS_INTACT : ANIMSIZE_30_PTRS_BROKEN); } } else if (animEntrySizeMatches.size() > 1) { warning("Savegame format detector got confused by input data. Detecting savegame to be using an unknown format"); } else { // animEtrySizeMatches.size() == 0 debug(3, "Savegame format detector was unable to detect savegame's format"); } fHandle.seek(prevStreamPos); return result; } /*! \brief Restore script list item from savefile * \param fHandle Savefile handle open for reading * \param isGlobal Restore object or global script? */ void loadScriptFromSave(Common::SeekableReadStream &fHandle, bool isGlobal) { ScriptVars localVars, labels; uint16 compare, pos; int16 idx; labels.load(fHandle); localVars.load(fHandle); compare = fHandle.readUint16BE(); pos = fHandle.readUint16BE(); idx = fHandle.readUint16BE(); // no way to reinitialize these if (idx < 0) { return; } // original code loaded everything into globalScripts, this should be // the correct behavior if (isGlobal) { ScriptPtr tmp(scriptInfo->create(*scriptTable[idx], idx, labels, localVars, compare, pos)); assert(tmp); globalScripts.push_back(tmp); } else { ScriptPtr tmp(scriptInfo->create(*relTable[idx], idx, labels, localVars, compare, pos)); assert(tmp); objectScripts.push_back(tmp); } } /*! \brief Restore overlay sprites from savefile * \param fHandle Savefile open for reading */ void loadOverlayFromSave(Common::SeekableReadStream &fHandle) { overlay tmp; fHandle.readUint32BE(); fHandle.readUint32BE(); tmp.objIdx = fHandle.readUint16BE(); tmp.type = fHandle.readUint16BE(); tmp.x = fHandle.readSint16BE(); tmp.y = fHandle.readSint16BE(); tmp.width = fHandle.readSint16BE(); tmp.color = fHandle.readSint16BE(); overlayList.push_back(tmp); } void CineEngine::resetEngine() { g_sound->stopMusic(); freeAnimDataTable(); overlayList.clear(); bgIncrustList.clear(); closePart(); objectScripts.clear(); globalScripts.clear(); relTable.clear(); scriptTable.clear(); messageTable.clear(); resetObjectTable(); globalVars.reset(); var2 = var3 = var4 = var5 = 0; strcpy(newPrcName, ""); strcpy(newRelName, ""); strcpy(newObjectName, ""); strcpy(newMsgName, ""); strcpy(currentCtName, ""); allowPlayerInput = 0; waitForPlayerClick = 0; playerCommand = -1; isDrawCommandEnabled = 0; commandBuffer = ""; globalVars[VAR_MOUSE_X_POS] = 0; globalVars[VAR_MOUSE_Y_POS] = 0; fadeRequired = false; renderer->clear(); checkForPendingDataLoadSwitch = 0; if (g_cine->getGameType() == Cine::GType_OS) { seqList.clear(); currentAdditionalBgIdx = 0; currentAdditionalBgIdx2 = 0; // TODO: Add resetting of the following variables // adBgVar1 = 0; // adBgVar0 = 0; // gfxFadeOutCompleted = 0; } } bool loadObjectTable(Common::SeekableReadStream &in) { in.readUint16BE(); // Entry count in.readUint16BE(); // Entry size for (int i = 0; i < NUM_MAX_OBJECT; i++) { objectTable[i].x = in.readSint16BE(); objectTable[i].y = in.readSint16BE(); objectTable[i].mask = in.readUint16BE(); objectTable[i].frame = in.readSint16BE(); objectTable[i].costume = in.readSint16BE(); in.read(objectTable[i].name, 20); objectTable[i].part = in.readUint16BE(); } return !in.ioFailed(); } bool loadZoneData(Common::SeekableReadStream &in) { for (int i = 0; i < 16; i++) { zoneData[i] = in.readUint16BE(); } return !in.ioFailed(); } bool loadCommandVariables(Common::SeekableReadStream &in) { for (int i = 0; i < 4; i++) { commandVar3[i] = in.readUint16BE(); } return !in.ioFailed(); } bool loadScreenParams(Common::SeekableReadStream &in) { // TODO: handle screen params (really required ?) in.readUint16BE(); in.readUint16BE(); in.readUint16BE(); in.readUint16BE(); in.readUint16BE(); in.readUint16BE(); return !in.ioFailed(); } bool loadGlobalScripts(Common::SeekableReadStream &in) { int size = in.readSint16BE(); for (int i = 0; i < size; i++) { loadScriptFromSave(in, true); } return !in.ioFailed(); } bool loadObjectScripts(Common::SeekableReadStream &in) { int size = in.readSint16BE(); for (int i = 0; i < size; i++) { loadScriptFromSave(in, false); } return !in.ioFailed(); } bool loadOverlayList(Common::SeekableReadStream &in) { int size = in.readSint16BE(); for (int i = 0; i < size; i++) { loadOverlayFromSave(in); } return !in.ioFailed(); } bool loadSeqList(Common::SeekableReadStream &in) { uint size = in.readUint16BE(); SeqListElement tmp; for (uint i = 0; i < size; i++) { tmp.var4 = in.readSint16BE(); tmp.objIdx = in.readUint16BE(); tmp.var8 = in.readSint16BE(); tmp.frame = in.readSint16BE(); tmp.varC = in.readSint16BE(); tmp.varE = in.readSint16BE(); tmp.var10 = in.readSint16BE(); tmp.var12 = in.readSint16BE(); tmp.var14 = in.readSint16BE(); tmp.var16 = in.readSint16BE(); tmp.var18 = in.readSint16BE(); tmp.var1A = in.readSint16BE(); tmp.var1C = in.readSint16BE(); tmp.var1E = in.readSint16BE(); seqList.push_back(tmp); } return !in.ioFailed(); } bool loadZoneQuery(Common::SeekableReadStream &in) { for (int i = 0; i < 16; i++) { zoneQuery[i] = in.readUint16BE(); } return !in.ioFailed(); } bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) { char musicName[13]; char bgNames[8][13]; // First check the temporary Operation Stealth savegame format header. ChunkHeader hdr; loadChunkHeader(in, hdr); if (hdr.id != TEMP_OS_FORMAT_ID) { warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame"); return false; } else if (hdr.version > CURRENT_OS_SAVE_VER) { warning("loadTempSaveOS: Detected newer format version. Not loading savegame"); return false; } else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) { warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break"); } else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match)."); } // There shouldn't be any data in the header's chunk currently so it's an error if there is. if (hdr.size > 0) { warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame"); return false; } // Ok, so we've got a correct header for a temporary Operation Stealth savegame. // Let's start loading the plain savegame data then. currentDisk = in.readUint16BE(); in.read(currentPartName, 13); in.read(currentPrcName, 13); in.read(currentRelName, 13); in.read(currentMsgName, 13); // Load the 8 background names. for (uint i = 0; i < 8; i++) { in.read(bgNames[i], 13); } in.read(currentCtName, 13); // Moved the loading of current procedure, relation, // backgrounds and Ct here because if they were at the // end of this function then the global scripts loading // made an array out of bounds access. In the original // game's disassembly these aren't here but at the end. // The difference is probably in how we handle loading // the global scripts and some other things (i.e. the // loading routines aren't exactly the same and subtle // semantic differences result in having to do things // in a different order). { // Not sure if this is needed with Operation Stealth... checkDataDisk(currentDisk); if (strlen(currentPrcName)) { loadPrc(currentPrcName); } if (strlen(currentRelName)) { loadRel(currentRelName); } // Load first background (Uses loadBg) if (strlen(bgNames[0])) { loadBg(bgNames[0]); } // Add backgrounds 1-7 (Uses addBackground) for (int i = 1; i < 8; i++) { if (strlen(bgNames[i])) { addBackground(bgNames[i], i); } } if (strlen(currentCtName)) { loadCtOS(currentCtName); } } loadObjectTable(in); renderer->restorePalette(in); globalVars.load(in, NUM_MAX_VAR); loadZoneData(in); loadCommandVariables(in); char tempCommandBuffer[kMaxCommandBufferSize]; in.read(tempCommandBuffer, kMaxCommandBufferSize); commandBuffer = tempCommandBuffer; renderer->setCommand(commandBuffer); loadZoneQuery(in); // TODO: Use the loaded string (Current music name (String, 13 bytes)). in.read(musicName, 13); // TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)). in.readUint16BE(); // TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)). in.readUint16BE(); renderer->_cmdY = in.readUint16BE(); in.readUint16BE(); // Some unknown variable that seems to always be zero allowPlayerInput = in.readUint16BE(); playerCommand = in.readUint16BE(); commandVar1 = in.readUint16BE(); isDrawCommandEnabled = in.readUint16BE(); var5 = in.readUint16BE(); var4 = in.readUint16BE(); var3 = in.readUint16BE(); var2 = in.readUint16BE(); commandVar2 = in.readUint16BE(); renderer->_messageBg = in.readUint16BE(); // TODO: Use the loaded value (adBgVar1 (Uint16BE)). in.readUint16BE(); currentAdditionalBgIdx = in.readSint16BE(); currentAdditionalBgIdx2 = in.readSint16BE(); // TODO: Check whether the scroll value really gets used correctly after this. // Note that the backgrounds are loaded only later than this value is set. renderer->setScroll(in.readUint16BE()); // TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?). in.readUint16BE(); disableSystemMenu = in.readUint16BE(); // TODO: adBgVar1 = 1 here // Load the animDataTable entries in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth). in.readUint16BE(); // Entry size (36 in the PC version of Operation Stealth). loadResourcesFromSave(in, ANIMSIZE_30_PTRS_INTACT); loadScreenParams(in); loadGlobalScripts(in); loadObjectScripts(in); loadSeqList(in); loadOverlayList(in); loadBgIncrustFromSave(in); // Left this here instead of moving it earlier in this function with // the other current value loadings (e.g. loading of current procedure, // current backgrounds etc). Mostly emulating the way we've handled // Future Wars savegames and hoping that things work out. if (strlen(currentMsgName)) { loadMsg(currentMsgName); } // TODO: Add current music loading and playing here // TODO: Palette handling? if (in.pos() == in.size()) { debug(3, "loadTempSaveOS: Loaded the whole savefile."); } else { warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over"); } return !in.ioFailed(); } bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { char bgName[13]; // At savefile position 0x0000: currentDisk = in.readUint16BE(); // At 0x0002: in.read(currentPartName, 13); // At 0x000F: in.read(currentDatName, 13); // At 0x001C: saveVar2 = in.readSint16BE(); // At 0x001E: in.read(currentPrcName, 13); // At 0x002B: in.read(currentRelName, 13); // At 0x0038: in.read(currentMsgName, 13); // At 0x0045: in.read(bgName, 13); // At 0x0052: in.read(currentCtName, 13); checkDataDisk(currentDisk); if (strlen(currentPartName)) { loadPart(currentPartName); } if (strlen(currentPrcName)) { loadPrc(currentPrcName); } if (strlen(currentRelName)) { loadRel(currentRelName); } if (strlen(bgName)) { loadBg(bgName); } if (strlen(currentCtName)) { loadCtFW(currentCtName); } // At 0x005F: loadObjectTable(in); // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): renderer->restorePalette(in); // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): globalVars.load(in, NUM_MAX_VAR); // At 0x2281 (i.e. 0x2083 + 255 * 2): loadZoneData(in); // At 0x22A1 (i.e. 0x2281 + 16 * 2): loadCommandVariables(in); // At 0x22A9 (i.e. 0x22A1 + 4 * 2): char tempCommandBuffer[kMaxCommandBufferSize]; in.read(tempCommandBuffer, kMaxCommandBufferSize); commandBuffer = tempCommandBuffer; renderer->setCommand(commandBuffer); // At 0x22F9 (i.e. 0x22A9 + 0x50): renderer->_cmdY = in.readUint16BE(); // At 0x22FB: bgVar0 = in.readUint16BE(); // At 0x22FD: allowPlayerInput = in.readUint16BE(); // At 0x22FF: playerCommand = in.readSint16BE(); // At 0x2301: commandVar1 = in.readSint16BE(); // At 0x2303: isDrawCommandEnabled = in.readUint16BE(); // At 0x2305: var5 = in.readUint16BE(); // At 0x2307: var4 = in.readUint16BE(); // At 0x2309: var3 = in.readUint16BE(); // At 0x230B: var2 = in.readUint16BE(); // At 0x230D: commandVar2 = in.readSint16BE(); // At 0x230F: renderer->_messageBg = in.readUint16BE(); // At 0x2311: in.readUint16BE(); // At 0x2313: in.readUint16BE(); // At 0x2315: loadResourcesFromSave(in, saveGameFormat); loadScreenParams(in); loadGlobalScripts(in); loadObjectScripts(in); loadOverlayList(in); loadBgIncrustFromSave(in); if (strlen(currentMsgName)) { loadMsg(currentMsgName); } if (strlen(currentDatName)) { /* i = saveVar2; saveVar2 = 0; loadMusic(); if (i) { playMusic(); }*/ } return !in.ioFailed(); } bool CineEngine::makeLoad(char *saveName) { Common::SharedPtr saveFile(g_saveFileMan->openForLoading(saveName)); if (!saveFile) { drawString(otherMessages[0], 0); waitPlayerInput(); // restoreScreen(); checkDataDisk(-1); return false; } setMouseCursor(MOUSE_CURSOR_DISK); uint32 saveSize = saveFile->size(); // TODO: Evaluate the maximum savegame size for the temporary Operation Stealth savegame format. if (saveSize == 0) { // Savefile's compressed using zlib format can't tell their unpacked size, test for it // Can't get information about the savefile's size so let's try // reading as much as we can from the file up to a predefined upper limit. // // Some estimates for maximum savefile sizes (All with 255 animDataTable entries of 30 bytes each): // With 256 global scripts, object scripts, overlays and background incrusts: // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 256 = ~129kB // With 512 global scripts, object scripts, overlays and background incrusts: // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 512 = ~242kB // // I think it extremely unlikely that there would be over 512 global scripts, object scripts, // overlays and background incrusts so 256kB seems like quite a safe upper limit. // NOTE: If the savegame format is changed then this value might have to be re-evaluated! // Hopefully devices with more limited memory can also cope with this memory allocation. saveSize = 256 * 1024; } Common::SharedPtr in(saveFile->readStream(saveSize)); // Try to detect the used savegame format enum CineSaveGameFormat saveGameFormat = detectSaveGameFormat(*in); // Handle problematic savegame formats bool load = true; // Should we try to load the savegame? bool result = false; if (saveGameFormat == ANIMSIZE_30_PTRS_BROKEN) { // One might be able to load the ANIMSIZE_30_PTRS_BROKEN format but // that's not implemented here because it was never used in a stable // release of ScummVM but only during development (From revision 31453, // which introduced the problem, until revision 32073, which fixed it). // Therefore we bail out if we detect this particular savegame format. warning("Detected a known broken savegame format, not loading savegame"); load = false; // Don't load the savegame } else if (saveGameFormat == ANIMSIZE_UNKNOWN) { // If we can't detect the savegame format // then let's try the default format and hope for the best. warning("Couldn't detect the used savegame format, trying default savegame format. Things may break"); saveGameFormat = ANIMSIZE_30_PTRS_INTACT; } if (load) { // Reset the engine's state resetEngine(); if (saveGameFormat == TEMP_OS_FORMAT) { // Load the temporary Operation Stealth savegame format result = loadTempSaveOS(*in); } else { // Load the plain Future Wars savegame format result = loadPlainSaveFW(*in, saveGameFormat); } } setMouseCursor(MOUSE_CURSOR_NORMAL); return result; } void CineEngine::makeSaveFW(Common::OutSaveFile &out) { out.writeUint16BE(currentDisk); out.write(currentPartName, 13); out.write(currentDatName, 13); out.writeUint16BE(saveVar2); out.write(currentPrcName, 13); out.write(currentRelName, 13); out.write(currentMsgName, 13); renderer->saveBgNames(out); out.write(currentCtName, 13); saveObjectTable(out); renderer->savePalette(out); globalVars.save(out, NUM_MAX_VAR); saveZoneData(out); saveCommandVariables(out); saveCommandBuffer(out); out.writeUint16BE(renderer->_cmdY); out.writeUint16BE(bgVar0); out.writeUint16BE(allowPlayerInput); out.writeUint16BE(playerCommand); out.writeUint16BE(commandVar1); out.writeUint16BE(isDrawCommandEnabled); out.writeUint16BE(var5); out.writeUint16BE(var4); out.writeUint16BE(var3); out.writeUint16BE(var2); out.writeUint16BE(commandVar2); out.writeUint16BE(renderer->_messageBg); saveAnimDataTable(out); saveScreenParams(out); saveGlobalScripts(out); saveObjectScripts(out); saveOverlayList(out); saveBgIncrustList(out); } void CineEngine::makeSave(char *saveFileName) { Common::SharedPtr fHandle(g_saveFileMan->openForSaving(saveFileName)); setMouseCursor(MOUSE_CURSOR_DISK); if (!fHandle) { drawString(otherMessages[1], 0); waitPlayerInput(); // restoreScreen(); checkDataDisk(-1); } else { if (g_cine->getGameType() == GType_FW) { makeSaveFW(*fHandle); } else { makeSaveOS(*fHandle); } } setMouseCursor(MOUSE_CURSOR_NORMAL); } void CineEngine::makeSystemMenu(void) { int16 numEntry, systemCommand; int16 mouseX, mouseY, mouseButton; int16 selectedSave; if (!disableSystemMenu) { inMenu = true; do { manageEvents(); getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY); } while (mouseButton); numEntry = 6; if (!allowPlayerInput) { numEntry--; } systemCommand = makeMenuChoice(systemMenu, numEntry, mouseX, mouseY, 140); switch (systemCommand) { case 0: { drawString(otherMessages[2], 0); waitPlayerInput(); break; } case 1: { getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY); if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) { //reinitEngine(); } break; } case 2: { getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY); if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) { quitGame(); } break; } case 3: // Select save drive... change ? { break; } case 4: // load game { if (loadSaveDirectory()) { // int16 selectedSave; getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY); selectedSave = makeMenuChoice(currentSaveName, 10, mouseX, mouseY + 8, 180); if (selectedSave >= 0) { char saveNameBuffer[256]; sprintf(saveNameBuffer, "%s.%1d", _targetName.c_str(), selectedSave); getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY); if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) { char loadString[256]; sprintf(loadString, otherMessages[3], currentSaveName[selectedSave]); drawString(loadString, 0); makeLoad(saveNameBuffer); } else { drawString(otherMessages[4], 0); waitPlayerInput(); checkDataDisk(-1); } } else { drawString(otherMessages[4], 0); waitPlayerInput(); checkDataDisk(-1); } } else { drawString(otherMessages[5], 0); waitPlayerInput(); checkDataDisk(-1); } break; } case 5: { loadSaveDirectory(); selectedSave = makeMenuChoice(currentSaveName, 10, mouseX, mouseY + 8, 180); if (selectedSave >= 0) { char saveFileName[256]; char saveName[20]; saveName[0] = 0; if (!makeTextEntryMenu(otherMessages[6], saveName, 20, 120)) break; strncpy(currentSaveName[selectedSave], saveName, 20); sprintf(saveFileName, "%s.%1d", _targetName.c_str(), selectedSave); getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY); if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) { char saveString[256], tmp[80]; snprintf(tmp, 80, "%s.dir", _targetName.c_str()); Common::OutSaveFile *fHandle = g_saveFileMan->openForSaving(tmp); if (!fHandle) { warning("Unable to open file %s for saving", tmp); break; } fHandle->write(currentSaveName, 200); delete fHandle; sprintf(saveString, otherMessages[3], currentSaveName[selectedSave]); drawString(saveString, 0); makeSave(saveFileName); checkDataDisk(-1); } else { drawString(otherMessages[4], 0); waitPlayerInput(); checkDataDisk(-1); } } break; } } inMenu = false; } } /** * Save an Operation Stealth type savegame. WIP! * * NOTE: This is going to be very much a work in progress so the Operation Stealth's * savegame formats that are going to be tried are extremely probably not going * to be supported at all after Operation Stealth becomes officially supported. * This means that the savegame format will hopefully change to something nicer * when official support for Operation Stealth begins. */ void CineEngine::makeSaveOS(Common::OutSaveFile &out) { int i; // Make a temporary Operation Stealth savegame format chunk header and save it. ChunkHeader header; header.id = TEMP_OS_FORMAT_ID; header.version = CURRENT_OS_SAVE_VER; header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it. writeChunkHeader(out, header); // Start outputting the plain savegame data right after the chunk header. out.writeUint16BE(currentDisk); out.write(currentPartName, 13); out.write(currentPrcName, 13); out.write(currentRelName, 13); out.write(currentMsgName, 13); renderer->saveBgNames(out); out.write(currentCtName, 13); saveObjectTable(out); renderer->savePalette(out); globalVars.save(out, NUM_MAX_VAR); saveZoneData(out); saveCommandVariables(out); saveCommandBuffer(out); saveZoneQuery(out); // FIXME: Save a proper name here, saving an empty string currently. // 0x2925: Current music name (String, 13 bytes). for (i = 0; i < 13; i++) { out.writeByte(0); } // FIXME: Save proper value for this variable, currently writing zero // 0x2932: Is music loaded? (Uint16BE, Boolean). out.writeUint16BE(0); // FIXME: Save proper value for this variable, currently writing zero // 0x2934: Is music playing? (Uint16BE, Boolean). out.writeUint16BE(0); out.writeUint16BE(renderer->_cmdY); out.writeUint16BE(0); // Some unknown variable that seems to always be zero out.writeUint16BE(allowPlayerInput); out.writeUint16BE(playerCommand); out.writeUint16BE(commandVar1); out.writeUint16BE(isDrawCommandEnabled); out.writeUint16BE(var5); out.writeUint16BE(var4); out.writeUint16BE(var3); out.writeUint16BE(var2); out.writeUint16BE(commandVar2); out.writeUint16BE(renderer->_messageBg); // FIXME: Save proper value for this variable, currently writing zero. // An unknown variable at 0x295E: adBgVar1 (Uint16BE). out.writeUint16BE(0); out.writeSint16BE(currentAdditionalBgIdx); out.writeSint16BE(currentAdditionalBgIdx2); // FIXME: Save proper value for this variable, currently writing zero. // 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift. out.writeUint16BE(0); // FIXME: Save proper value for this variable, currently writing zero. // An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0? out.writeUint16BE(0); out.writeUint16BE(disableSystemMenu); saveAnimDataTable(out); saveScreenParams(out); saveGlobalScripts(out); saveObjectScripts(out); saveSeqList(out); saveOverlayList(out); saveBgIncrustList(out); } void drawMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 offset, int16 color, byte* page) { gfxDrawLine(x + offset, y + offset, x + width - offset, y + offset, color, page); // top gfxDrawLine(x + offset, currentY + 4 - offset, x + width - offset, currentY + 4 - offset, color, page); // bottom gfxDrawLine(x + offset, y + offset, x + offset, currentY + 4 - offset, color, page); // left gfxDrawLine(x + width - offset, y + offset, x + width - offset, currentY + 4 - offset, color, page); // right } void drawDoubleMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 color, byte* page) { drawMessageBox(x, y, width, currentY, 1, 0, page); drawMessageBox(x, y, width, currentY, 0, color, page); } void processInventory(int16 x, int16 y) { uint16 button; int menuWidth; int listSize; int commandParam; if (g_cine->getGameType() == Cine::GType_FW) { menuWidth = 140; commandParam = -2; } else { // Operation Stealth menuWidth = 160; commandParam = -3; } listSize = buildObjectListCommand(commandParam); if (!listSize) return; renderer->drawMenu(objectListCommand, listSize, x, y, menuWidth, -1); renderer->blit(); do { manageEvents(); getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16); } while (!button); do { manageEvents(); getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16); } while (button); // TODO: Both Future Wars and Operation Stealth call showMouse, drawMouse or something similar here. } int16 buildObjectListCommand(int16 param) { int16 i = 0, j = 0; for (i = 0; i < 20; i++) { objectListCommand[i][0] = 0; } for (i = 0; i < 255; i++) { if (objectTable[i].name[0] && objectTable[i].costume == param) { strcpy(objectListCommand[j], objectTable[i].name); objListTab[j] = i; j++; } } return j; } int16 selectSubObject(int16 x, int16 y, int16 param) { int16 listSize = buildObjectListCommand(param); int16 selectedObject; bool osExtras = g_cine->getGameType() == Cine::GType_OS; if (!listSize) { return -2; } selectedObject = makeMenuChoice(objectListCommand, listSize, x, y, 140, osExtras); if (selectedObject == -1) return -1; if (osExtras) { if (selectedObject >= 8000) { return objListTab[selectedObject - 8000] + 8000; } } return objListTab[selectedObject]; } // TODO: Make separate functions for Future Wars's and Operation Stealth's version of this function, this is getting too messy // TODO: Add support for using the different prepositions for different verbs (Doesn't work currently) void makeCommandLine(void) { uint16 x, y; commandVar1 = 0; commandVar2 = -10; if (playerCommand != -1) { commandBuffer = defaultActionCommand[playerCommand]; } else { commandBuffer = ""; } if ((playerCommand != -1) && (choiceResultTable[playerCommand] == 2)) { // need object selection ? int16 si; getMouseData(mouseUpdateStatus, &dummyU16, &x, &y); if (g_cine->getGameType() == Cine::GType_FW) { si = selectSubObject(x, y + 8, -2); } else { si = selectSubObject(x, y + 8, -subObjectUseTable[playerCommand]); } if (si < 0) { if (g_cine->getGameType() == Cine::GType_OS) { canUseOnObject = 0; } else { // Future Wars playerCommand = -1; commandBuffer = ""; } } else { if (g_cine->getGameType() == Cine::GType_OS) { if (si >= 8000) { si -= 8000; canUseOnObject = canUseOnItemTable[playerCommand]; } else { canUseOnObject = 0; } } commandVar3[0] = si; commandVar1 = 1; commandBuffer += " "; commandBuffer += objectTable[commandVar3[0]].name; commandBuffer += " "; if (g_cine->getGameType() == Cine::GType_OS) { commandBuffer += commandPrepositionTable[playerCommand]; } else { // Future Wars commandBuffer += defaultCommandPreposition; } } } if (g_cine->getGameType() == Cine::GType_OS || !(playerCommand != -1 && choiceResultTable[playerCommand] == 2)) { if (playerCommand == 2) { getMouseData(mouseUpdateStatus, &dummyU16, &x, &y); processInventory(x, y + 8); playerCommand = -1; commandVar1 = 0; commandBuffer = ""; } } if (g_cine->getGameType() == Cine::GType_OS && playerCommand != 2) { if (playerCommand != -1 && canUseOnObject != 0) { // call use on sub object int16 si; getMouseData(mouseUpdateStatus, &dummyU16, &x, &y); si = selectSubObject(x, y + 8, -subObjectUseTable[playerCommand]); if (si >= 0) { if (si >= 8000) { si -= 8000; } commandVar3[commandVar1] = si; commandVar1++; commandBuffer += " "; commandBuffer += objectTable[si].name; } } isDrawCommandEnabled = 1; if (playerCommand != -1 && choiceResultTable[playerCommand] == commandVar1) { SelectedObjStruct obj; obj.idx = commandVar3[0]; obj.param = commandVar3[1]; int16 di = getRelEntryForObject(playerCommand, commandVar1, &obj); if (di != -1) { runObjectScript(di); } // TODO: else addFailureMessage(playerCommand) playerCommand = -1; commandVar1 = 0; commandBuffer = ""; } } if (g_cine->getGameType() == Cine::GType_OS || !disableSystemMenu) { isDrawCommandEnabled = 1; renderer->setCommand(commandBuffer); } } uint16 needMouseSave = 0; uint16 menuVar4 = 0; uint16 menuVar5 = 0; int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, bool recheckValue) { int16 paramY; uint16 button; int16 var_A; int16 di; uint16 j; int16 mouseX, mouseY; int16 var_16; int16 var_14; int16 currentSelection, oldSelection; int16 var_4; if (disableSystemMenu) return -1; paramY = (height * 9) + 10; if (X + width > 319) { X = 319 - width; } if (Y + paramY > 199) { Y = 199 - paramY; } renderer->drawMenu(commandList, height, X, Y, width, -1); renderer->blit(); do { manageEvents(); getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16); } while (button); var_A = 0; currentSelection = 0; di = currentSelection * 9 + Y + 4; renderer->drawMenu(commandList, height, X, Y, width, currentSelection); renderer->blit(); manageEvents(); getMouseData(mouseUpdateStatus, &button, (uint16 *)&mouseX, (uint16 *)&mouseY); var_16 = mouseX; var_14 = mouseY; menuVar = 0; do { manageEvents(); getMouseData(mouseUpdateStatus, &button, (uint16 *)&mouseX, (uint16 *)&mouseY); if (button) { var_A = 1; } oldSelection = currentSelection; if (needMouseSave) { for (j = 0; j < 3; j++) { mainLoopSub6(); } if (menuVar4 && currentSelection > 0) { // go up currentSelection--; } if (menuVar5) { // go down if (height - 1 > currentSelection) { currentSelection++; } } } else { if (mouseX > X && mouseX < X + width && mouseY > Y && mouseY < Y + height * 9) { currentSelection = (mouseY - (Y + 4)) / 9; if (currentSelection < 0) currentSelection = 0; if (currentSelection >= height) currentSelection = height - 1; } } if (currentSelection != oldSelection) { // old != new if (needMouseSave) { hideMouse(); } di = currentSelection * 9 + Y + 4; renderer->drawMenu(commandList, height, X, Y, width, currentSelection); renderer->blit(); // if (needMouseSave) { // gfxRedrawMouseCursor(); // } } } while (!var_A); assert(!needMouseSave); var_4 = button; menuVar = 0; do { manageEvents(); getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16); } while (button); if (var_4 == 2) { // recheck if (!recheckValue) return -1; else return currentSelection + 8000; } return currentSelection; } void makeActionMenu(void) { uint16 mouseButton; uint16 mouseX; uint16 mouseY; inMenu = true; getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY); if (g_cine->getGameType() == Cine::GType_OS) { playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70, true); if (playerCommand >= 8000) { playerCommand -= 8000; canUseOnObject = 1; } } else { playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70); } inMenu = false; } uint16 executePlayerInput(void) { uint16 var_5E; uint16 var_2; uint16 mouseX, mouseY, mouseButton; uint16 currentEntry = 0; uint16 di = 0; canUseOnObject = 0; if (isInPause) { drawString(otherMessages[2], 0); waitPlayerInput(); isInPause = 0; } if (allowPlayerInput) { if (isDrawCommandEnabled) { renderer->setCommand(commandBuffer); isDrawCommandEnabled = 0; } getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY); while (mouseButton && currentEntry < 200) { if (mouseButton & 1) { di |= 1; } if (mouseButton & 2) { di |= 2; } getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY); currentEntry++; } if (di) { mouseButton = di; } if (playerCommand != -1) { if (mouseButton & 1) { if (mouseButton & 2) { g_cine->makeSystemMenu(); } else { int16 si; do { manageEvents(); getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16); } while (mouseButton); si = getObjectUnderCursor(mouseX, mouseY); if (si != -1) { commandVar3[commandVar1] = si; commandVar1++; commandBuffer += " "; commandBuffer += objectTable[si].name; isDrawCommandEnabled = 1; if (choiceResultTable[playerCommand] == commandVar1) { int16 relEntry; SelectedObjStruct obj; obj.idx = commandVar3[0]; obj.param = commandVar3[1]; relEntry = getRelEntryForObject(playerCommand, commandVar1, &obj); if (relEntry != -1) { runObjectScript(relEntry); } else { addPlayerCommandMessage(playerCommand); } playerCommand = -1; commandVar1 = 0; commandBuffer = ""; renderer->setCommand(commandBuffer); } } else { globalVars[VAR_MOUSE_X_POS] = mouseX; globalVars[VAR_MOUSE_Y_POS] = mouseY; } } } else if (mouseButton & 2) { if (mouseButton & 1) { g_cine->makeSystemMenu(); } makeActionMenu(); makeCommandLine(); } else { int16 objIdx; objIdx = getObjectUnderCursor(mouseX, mouseY); if (commandVar2 != objIdx) { if (objIdx != -1) { renderer->setCommand(commandBuffer + " " + objectTable[objIdx].name); } else { isDrawCommandEnabled = 1; } } commandVar2 = objIdx; } } else { if (mouseButton & 2) { if (!(mouseButton & 1)) { if (g_cine->getGameType() == Cine::GType_OS) { playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70, true); if (playerCommand >= 8000) { playerCommand -= 8000; canUseOnObject = 1; } } else { playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70); } makeCommandLine(); } else { g_cine->makeSystemMenu(); } } else { if (mouseButton & 1) { if (!(mouseButton & 2)) { int16 objIdx; int16 relEntry; globalVars[VAR_MOUSE_X_POS] = mouseX; if (!mouseX) { globalVars[VAR_MOUSE_X_POS]++; } globalVars[VAR_MOUSE_Y_POS] = mouseY; objIdx = getObjectUnderCursor(mouseX, mouseY); if (objIdx != -1) { currentSelectedObject.idx = objIdx; currentSelectedObject.param = -1; relEntry = getRelEntryForObject(6, 1, ¤tSelectedObject); if (relEntry != -1) { runObjectScript(relEntry); } } } else { g_cine->makeSystemMenu(); } } } } } else { di = 0; getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY); while (mouseButton) { if (mouseButton & 1) { di |= 1; } if (mouseButton & 2) { di |= 2; } manageEvents(); getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY); } if (di) { mouseButton = di; } if ((mouseButton & 1) && (mouseButton & 2)) { g_cine->makeSystemMenu(); } } var_2 = menuVar & 0x7F; var_5E = var_2; if (menuVar & 0x80) { var_5E = 0; var_2 = 0; } if (egoMovedWithKeyboard && allowPlayerInput) { // use keyboard egoMovedWithKeyboard = false; switch (globalVars[VAR_MOUSE_X_MODE]) { case 1: mouseX = objectTable[1].x + 12; break; case 2: mouseX = objectTable[1].x + 7; break; default: mouseX = globalVars[VAR_MOUSE_X_POS]; break; } switch (globalVars[VAR_MOUSE_Y_MODE]) { case 1: mouseY = objectTable[1].y + 34; break; case 2: mouseY = objectTable[1].y + 28; break; default: mouseY = globalVars[VAR_MOUSE_Y_POS]; break; } if (var_5E == bgVar0) { var_5E = 0; globalVars[VAR_MOUSE_X_POS] = mouseX; globalVars[VAR_MOUSE_Y_POS] = mouseY; } else { if (xMoveKeyb) { if (xMoveKeyb == kKeybMoveLeft) { globalVars[VAR_MOUSE_X_POS] = 1; } else { globalVars[VAR_MOUSE_X_POS] = 320; } } else { globalVars[VAR_MOUSE_X_POS] = mouseX; } if (yMoveKeyb) { if (yMoveKeyb == kKeybMoveUp) { globalVars[VAR_MOUSE_Y_POS] = 1; } else { globalVars[VAR_MOUSE_Y_POS] = 200; } } else { globalVars[VAR_MOUSE_Y_POS] = mouseY; } } bgVar0 = var_5E; } else { // don't use keyboard for move -> shortcuts to commands getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY); switch (var_2 - 59) { case 0: case 1: case 2: case 3: case 4: case 5: if (allowPlayerInput) { playerCommand = var_2 - 59; makeCommandLine(); } break; case 6: case 7: case 8: case 23: break; case 9: case 24: g_cine->makeSystemMenu(); break; default: // printf("Unhandled case %d in last part of executePlayerInput\n",var2-59); break; } } // Update Operation Stealth specific global variables. // This fixes swimming at the bottom of the ocean after // having been thrown into it with the girl. if (g_cine->getGameType() == Cine::GType_OS) { globalVars[251] = globalVars[VAR_MOUSE_X_POS]; globalVars[252] = globalVars[VAR_MOUSE_Y_POS]; } return var_5E; } void drawSprite(Common::List::iterator it, const byte *spritePtr, const byte *maskPtr, uint16 width, uint16 height, byte *page, int16 x, int16 y) { byte *msk = NULL; int16 maskX, maskY, maskWidth, maskHeight; uint16 maskSpriteIdx; msk = (byte *)malloc(width * height); if (g_cine->getGameType() == Cine::GType_OS) { generateMask(spritePtr, msk, width * height, objectTable[it->objIdx].part); } else { memcpy(msk, maskPtr, width * height); } for (++it; it != overlayList.end(); ++it) { if (it->type != 5) { continue; } maskX = objectTable[it->objIdx].x; maskY = objectTable[it->objIdx].y; maskSpriteIdx = ABS((int16)(objectTable[it->objIdx].frame)); maskWidth = animDataTable[maskSpriteIdx]._realWidth; maskHeight = animDataTable[maskSpriteIdx]._height; gfxUpdateSpriteMask(msk, x, y, width, height, animDataTable[maskSpriteIdx].data(), maskX, maskY, maskWidth, maskHeight); #ifdef DEBUG_SPRITE_MASK gfxFillSprite(animDataTable[maskSpriteIdx].data(), maskWidth, maskHeight, page, maskX, maskY, 1); #endif } gfxDrawMaskedSprite(spritePtr, msk, width, height, page, x, y); free(msk); } void removeMessages() { Common::List::iterator it; bool remove; for (it = overlayList.begin(); it != overlayList.end(); ) { if (g_cine->getGameType() == Cine::GType_OS) { // NOTE: These are really removeOverlay calls that have been deferred. // In Operation Stealth's disassembly elements are removed from the // overlay list right in the drawOverlays function (And actually in // some other places too) and that's where incrementing a the overlay's // last parameter by one if it's negative and testing it for positivity // comes from too. remove = it->type == 3 || (it->type == 2 && (it->color >= 0 || ++it->color >= 0)); } else { // Future Wars remove = it->type == 2 || it->type == 3; } if (remove) { it = overlayList.erase(it); } else { ++it; } } } uint16 processKeyboard(uint16 param) { return 0; } void mainLoopSub6(void) { } void checkForPendingDataLoad(void) { if (newPrcName[0] != 0) { bool loadPrcOk = loadPrc(newPrcName); strcpy(currentPrcName, newPrcName); strcpy(newPrcName, ""); // Check that the loading of the script file was successful before // trying to add script 1 from it to the global scripts list. This // fixes a crash when failing copy protection in Amiga or Atari ST // versions of Future Wars. if (loadPrcOk) { addScriptToGlobalScripts(1); } else if (scumm_stricmp(currentPrcName, COPY_PROT_FAIL_PRC_NAME)) { // We only show an error here for other files than the file that // is loaded if copy protection fails (i.e. L201.ANI). warning("checkForPendingDataLoad: loadPrc(%s) failed", currentPrcName); } } if (newRelName[0] != 0) { loadRel(newRelName); strcpy(currentRelName, newRelName); strcpy(newRelName, ""); } if (newObjectName[0] != 0) { overlayList.clear(); loadObject(newObjectName); strcpy(currentObjectName, newObjectName); strcpy(newObjectName, ""); } if (newMsgName[0] != 0) { loadMsg(newMsgName); strcpy(currentMsgName, newMsgName); strcpy(newMsgName, ""); } } void hideMouse(void) { } void removeExtention(char *dest, const char *source) { strcpy(dest, source); byte *ptr = (byte *) strchr(dest, '.'); if (ptr) { *ptr = 0; } } void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 param5) { overlay tmp; tmp.objIdx = param1; tmp.type = 2; tmp.x = param2; tmp.y = param3; tmp.width = param4; tmp.color = param5; overlayList.push_back(tmp); } Common::List seqList; void removeSeq(uint16 param1, uint16 param2, uint16 param3) { Common::List::iterator it; for (it = seqList.begin(); it != seqList.end(); ++it) { if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) { it->var4 = -1; break; } } } bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) { Common::List::iterator it; for (it = seqList.begin(); it != seqList.end(); ++it) { if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) { // Just to be on the safe side there's a restriction of the // addition's result to 16-bit arithmetic here like in the // original. It's possible that it's not strictly needed. return ((it->var14 + it->var16) & 0xFFFF) == 0; } } return true; } void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8) { Common::List::iterator it; SeqListElement tmp; for (it = seqList.begin(); it != seqList.end() && it->varE < param7; ++it) ; tmp.objIdx = objIdx; tmp.var4 = param1; tmp.var8 = param2; tmp.frame = frame; tmp.varC = param4; tmp.var14 = 0; tmp.var16 = 0; tmp.var18 = param5; tmp.var1A = param6; tmp.varE = param7; tmp.var10 = param8; tmp.var12 = param8; tmp.var1C = 0; tmp.var1E = 0; seqList.insert(it, tmp); } void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4) { // Find a suitable list element and modify it for (Common::List::iterator it = seqList.begin(); it != seqList.end(); ++it) { if (it->objIdx == objIdx && it->var4 == var4Test) { it->varC = param1; it->var18 = param2; it->var1A = param3; it->var10 = it->var12 = param4; break; } } } void computeMove1(SeqListElement &element, int16 x, int16 y, int16 param1, int16 param2, int16 x2, int16 y2) { element.var16 = 0; element.var14 = 0; if (y2) { if (y - param2 > y2) { element.var16 = 2; } if (y + param2 < y2) { element.var16 = 1; } } if (x2) { if (x - param1 > x2) { element.var14 = 2; } if (x + param1 < x2) { element.var14 = 1; } } } uint16 computeMove2(SeqListElement &element) { int16 returnVar = 0; if (element.var16 == 1) { returnVar = 4; } else if (element.var16 == 2) { returnVar = 3; } if (element.var14 == 1) { returnVar = 1; } else if (element.var14 == 2) { returnVar = 2; } return returnVar; } uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &element, uint16 param3, int16 *param4) { const int8 *ptrData; const int8 *ptr2; int16 di; debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d", param1, objIdx, ptr, element.var8, element.var14, param3); // In the original an error string is set and 0 is returned if the following doesn't hold assert(ptr); // We probably could just use a local variable here instead of the dummyU16 but // haven't checked if this has any side-effects so keeping it this way still. dummyU16 = READ_BE_UINT16(ptr + param1 * 2 + 8); ptrData = ptr + dummyU16; // In the original an error string is set and 0 is returned if the following doesn't hold assert(*ptrData); di = (objectTable[objIdx].costume + 1) % (*ptrData); ++ptrData; // Jump over the just read byte // Here ptr2 seems to be indexing a table of structs (8 bytes per struct): // struct { // int8 x; // 0 (Used with checkCollision) // int8 y; // 1 (Used with checkCollision) // int8 numZones; // 2 (Used with checkCollision) // int8 var3; // 3 (Not used in this function) // int8 xAdd; // 4 (Used with an object) // int8 yAdd; // 5 (Used with an object) // int8 maskAdd; // 6 (Used with an object) // int8 frameAdd; // 7 (Used with an object) // }; ptr2 = ptrData + di * 8; // We might probably safely discard the AND by 1 here because // at least in the original checkCollision returns always 0 or 1. if ((checkCollision(objIdx, ptr2[0], ptr2[1], ptr2[2], ptr[0]) & 1)) { return 0; } objectTable[objIdx].x += ptr2[4]; objectTable[objIdx].y += ptr2[5]; objectTable[objIdx].mask += ptr2[6]; if (ptr2[6]) { resetGfxEntityEntry(objIdx); } objectTable[objIdx].frame = ptr2[7] + element.var8; if (param3 || !element.var14) { objectTable[objIdx].costume = di; } else { assert(param4); *param4 = di; } return 1; } /*! * Permutates the overlay list into a different order according to some logic. * \todo Check this function for correctness (Wasn't very easy to reverse engineer so there may be errors) */ void resetGfxEntityEntry(uint16 objIdx) { Common::List::iterator it, bObjsCutPoint; Common::List aReverseObjs, bObjs; bool foundCutPoint = false; // Go through the overlay list and partition the whole list into two categories (Type A and type B objects) for (it = overlayList.begin(); it != overlayList.end(); ++it) { if (it->objIdx == objIdx && it->type != 2 && it->type != 3) { // Type A object aReverseObjs.push_front(*it); } else { // Type B object bObjs.push_back(*it); uint16 objectMask; if (it->type == 2 || it->type == 3) { objectMask = 10000; } else { objectMask = objectTable[it->objIdx].mask; } if (objectTable[objIdx].mask > objectMask) { // Check for B objects' cut point bObjsCutPoint = bObjs.reverse_begin(); foundCutPoint = true; } } } // Recreate the overlay list in a different order. overlayList.clear(); if (foundCutPoint) { // If a cut point was found the order is: // B objects before the cut point, the cut point, A objects in reverse order, B objects after cut point. ++bObjsCutPoint; // Include the cut point in the first list insertion overlayList.insert(overlayList.end(), bObjs.begin(), bObjsCutPoint); overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end()); overlayList.insert(overlayList.end(), bObjsCutPoint, bObjs.end()); } else { // If no cut point was found the order is: // A objects in reverse order, B objects. overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end()); overlayList.insert(overlayList.end(), bObjs.begin(), bObjs.end()); } } void processSeqListElement(SeqListElement &element) { int16 x = objectTable[element.objIdx].x; int16 y = objectTable[element.objIdx].y; const int8 *ptr1 = (const int8 *) animDataTable[element.frame].data(); int16 var_10; int16 var_4; int16 var_2; // Initial interpretations for variables addressed through ptr1 (8-bit addressing): // These may be inaccurate! // 0: ? // 1: xRadius // 2: yRadius // 3: ? // 4: xAdd // 5: yAdd // 6: ? // 7: ? // After this come (At least at positions 0, 1 and 3 in 16-bit addressing) // 16-bit big-endian values used for addressing through ptr1. if (element.var12 < element.var10) { element.var12++; return; } element.var12 = 0; if (ptr1) { int16 param1 = ptr1[1]; int16 param2 = ptr1[2]; if (element.varC != 255) { int16 x2 = element.var18; int16 y2 = element.var1A; if (element.varC) { x2 += objectTable[element.varC].x; y2 += objectTable[element.varC].y; } computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, x2, y2); } else { if (xMoveKeyb && allowPlayerInput) { int16 adder = param1 + 1; if (xMoveKeyb != kKeybMoveRight) { adder = -adder; } // FIXME: In Operation Stealth's disassembly global variable 251 is used here // but it's named as VAR_MOUSE_Y_MODE in ScummVM. Is it correct or a // left over from Future Wars's reverse engineering? globalVars[VAR_MOUSE_X_POS] = globalVars[251] = ptr1[4] + x + adder; } if (yMoveKeyb && allowPlayerInput) { int16 adder = param2 + 1; if (yMoveKeyb != kKeybMoveDown) { adder = -adder; } // TODO: Name currently unnamed global variable 252 globalVars[VAR_MOUSE_Y_POS] = globalVars[252] = ptr1[5] + y + adder; } if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) { computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]); } else { element.var16 = 0; element.var14 = 0; } } var_10 = computeMove2(element); if (var_10) { element.var1C = var_10; element.var1E = var_10; } var_4 = -1; if ((element.var16 == 1 && !addAni(3, element.objIdx, ptr1, element, 0, &var_4)) || (element.var16 == 2 && !addAni(2, element.objIdx, ptr1, element, 0, &var_4))) { if (element.varC == 255) { globalVars[VAR_MOUSE_Y_POS] = 0; } } if ((element.var14 == 1 && !addAni(0, element.objIdx, ptr1, element, 1, &var_2))) { if (element.varC == 255) { globalVars[VAR_MOUSE_X_POS] = 0; if (var_4 != -1) { objectTable[element.objIdx].costume = var_4; } } } if ((element.var14 == 2 && !addAni(1, element.objIdx, ptr1, element, 1, &var_2))) { if (element.varC == 255) { globalVars[VAR_MOUSE_X_POS] = 0; if (var_4 != -1) { objectTable[element.objIdx].costume = var_4; } } } if (element.var16 + element.var14 == 0) { if (element.var1C) { if (element.var1E) { objectTable[element.objIdx].costume = 0; element.var1E = 0; } addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, &var_2); } } } } void processSeqList(void) { Common::List::iterator it; for (it = seqList.begin(); it != seqList.end(); ++it) { if (it->var4 == -1) { continue; } processSeqListElement(*it); } } bool makeTextEntryMenu(const char *messagePtr, char *inputString, int stringMaxLength, int y) { int len = strlen(messagePtr); int16 width = 6 * len + 20; width = CLIP((int)width, 180, 250); int16 x = (320 - width) / 2; getKeyData(); // clear input key int quit = 0; bool redraw = true; CommandeType tempString; int inputLength = strlen(inputString); int inputPos = inputLength + 1; while (!quit) { if (redraw) { renderer->drawInputBox(messagePtr, inputString, inputPos, x - 16, y, width + 32); renderer->blit(); redraw = false; } char ch[2]; memset(tempString, 0, stringMaxLength); ch[1] = 0; manageEvents(); int keycode = getKeyData(); uint16 mouseButton, mouseX, mouseY; getMouseData(0, &mouseButton, &mouseX, &mouseY); if (mouseButton & 2) quit = 2; else if (mouseButton & 1) quit = 1; switch (keycode) { case Common::KEYCODE_BACKSPACE: if (inputPos <= 1) { break; } inputPos--; redraw = true; case Common::KEYCODE_DELETE: if (inputPos <= inputLength) { if (inputPos != 1) { strncpy(tempString, inputString, inputPos - 1); } if (inputPos != inputLength) { strncat(tempString, &inputString[inputPos], inputLength - inputPos); } strcpy(inputString, tempString); inputLength = strlen(inputString); redraw = true; } break; case Common::KEYCODE_LEFT: if (inputPos > 1) { inputPos--; redraw = true; } break; case Common::KEYCODE_RIGHT: if (inputPos <= inputLength) { inputPos++; redraw = true; } break; default: if (((keycode >= 'a') && (keycode <='z')) || ((keycode >= '0') && (keycode <='9')) || ((keycode >= 'A') && (keycode <='Z')) || (keycode == ' ')) { if (inputLength < stringMaxLength - 1) { ch[0] = keycode; if (inputPos != 1) { strncpy(tempString, inputString, inputPos - 1); strcat(tempString, ch); } if ((inputLength == 0) || (inputPos == 1)) { strcpy(tempString, ch); } if ((inputLength != 0) && (inputPos != inputLength)) { strncat(tempString, &inputString[inputPos - 1], inputLength - inputPos + 1); } strcpy(inputString, tempString); inputLength = strlen(inputString); inputPos++; redraw = true; } } break; } } if (quit == 2) return false; return true; } } // End of namespace Cine