/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "common/endian.h" #include "common/util.h" #include "common/textconsole.h" #include "common/translation.h" #include "sword1/logic.h" #include "sword1/text.h" #include "sword1/sound.h" #include "sword1/eventman.h" #include "sword1/menu.h" #include "sword1/router.h" #include "sword1/screen.h" #include "sword1/mouse.h" #include "sword1/sword1.h" #include "sword1/music.h" #include "sword1/swordres.h" #include "sword1/animation.h" #include "sword1/debug.h" #include "gui/message.h" namespace Sword1 { #define MAX_STACK_SIZE 10 #define SCRIPT_VERSION 13 #define LAST_FRAME 999 uint32 Logic::_scriptVars[NUM_SCRIPT_VARS]; Logic::Logic(SwordEngine *vm, ObjectMan *pObjMan, ResMan *resMan, Screen *pScreen, Mouse *pMouse, Sound *pSound, Music *pMusic, Menu *pMenu, OSystem *system, Audio::Mixer *mixer) : _rnd("sword1") { _vm = vm; _objMan = pObjMan; _resMan = resMan; _screen = pScreen; _mouse = pMouse; _music = pMusic; _sound = pSound; _menu = pMenu; _textMan = NULL; _screen->useTextManager(_textMan); _router = new Router(_objMan, _resMan); _eventMan = NULL; _system = system; _mixer = mixer; setupMcodeTable(); } Logic::~Logic() { delete _textMan; delete _router; delete _eventMan; } void Logic::initialize() { memset(_scriptVars, 0, NUM_SCRIPT_VARS * sizeof(uint32)); for (uint8 cnt = 0; cnt < NON_ZERO_SCRIPT_VARS; cnt++) _scriptVars[_scriptVarInit[cnt][0]] = _scriptVarInit[cnt][1]; if (SwordEngine::_systemVars.isDemo) _scriptVars[PLAYINGDEMO] = 1; delete _eventMan; _eventMan = new EventManager(); delete _textMan; _textMan = new Text(_objMan, _resMan, (SwordEngine::_systemVars.language == BS1_CZECH) ? true : false); _screen->useTextManager(_textMan); _textRunning = _speechRunning = false; _speechFinished = true; } void Logic::newScreen(uint32 screen) { Object *compact = (Object *)_objMan->fetchObject(PLAYER); // work around script bug #911508 if (((screen == 25) || (_scriptVars[SCREEN] == 25)) && (_scriptVars[SAND_FLAG] == 4)) { Object *cpt = _objMan->fetchObject(SAND_25); Object *george = _objMan->fetchObject(PLAYER); if (george->o_place == HOLDING_REPLICA_25) // is george holding the replica in his hands? fnFullSetFrame(cpt, SAND_25, IMPFLRCDT, IMPFLR, 0, 0, 0, 0); // empty impression in floor else fnFullSetFrame(cpt, SAND_25, IMPPLSCDT, IMPPLS, 0, 0, 0, 0); // impression filled with plaster } // work around, at screen 69 in psx version TOP menu gets stuck at disabled, fix it at next screen (71) if ((screen == 71) && (SwordEngine::isPsx())) _scriptVars[TOP_MENU_DISABLED] = 0; if (SwordEngine::_systemVars.justRestoredGame) { // if we've just restored a game - we want George to be exactly as saved fnAddHuman(NULL, 0, 0, 0, 0, 0, 0, 0); if (_scriptVars[GEORGE_WALKING]) { // except that if George was walking when we saveed the game fnStandAt(compact, PLAYER, _scriptVars[CHANGE_X], _scriptVars[CHANGE_Y], _scriptVars[CHANGE_DIR], _scriptVars[CHANGE_STANCE], 0, 0); fnIdle(compact, PLAYER, 0, 0, 0, 0, 0, 0); _scriptVars[GEORGE_WALKING] = 0; } SwordEngine::_systemVars.justRestoredGame = 0; _music->startMusic(_scriptVars[CURRENT_MUSIC], 1); } else { // if we haven't just restored a game, set George to stand, etc compact->o_screen = _scriptVars[NEW_SCREEN]; //move the mega/player at this point between screens fnStandAt(compact, PLAYER, _scriptVars[CHANGE_X], _scriptVars[CHANGE_Y], _scriptVars[CHANGE_DIR], _scriptVars[CHANGE_STANCE], 0, 0); fnChangeFloor(compact, PLAYER, _scriptVars[CHANGE_PLACE], 0, 0, 0, 0, 0); } } void Logic::engine() { debug(8, "\n\nNext logic cycle"); _eventMan->serviceGlobalEventList(); for (uint16 sectCnt = 0; sectCnt < TOTAL_SECTIONS; sectCnt++) { if (_objMan->sectionAlive(sectCnt)) { uint32 numCpts = _objMan->fetchNoObjects(sectCnt); for (uint32 cptCnt = 0; cptCnt < numCpts; cptCnt++) { uint32 currentId = sectCnt * ITM_PER_SEC + cptCnt; Object *compact = _objMan->fetchObject(currentId); if (compact->o_status & STAT_LOGIC) { // does the object want to be processed? if (compact->o_status & STAT_EVENTS) { //subscribed to the global-event-switcher? and in logic mode switch (compact->o_logic) { case LOGIC_pause_for_event: case LOGIC_idle: case LOGIC_AR_animate: _eventMan->checkForEvent(compact); break; } } debug(7, "Logic::engine: handling compact %d (%X)", currentId, currentId); processLogic(compact, currentId); compact->o_sync = 0; // syncs are only available for 1 cycle. } if ((uint32)compact->o_screen == _scriptVars[SCREEN]) { if (compact->o_status & STAT_FORE) _screen->addToGraphicList(0, currentId); if (compact->o_status & STAT_SORT) _screen->addToGraphicList(1, currentId); if (compact->o_status & STAT_BACK) _screen->addToGraphicList(2, currentId); if (compact->o_status & STAT_MOUSE) _mouse->addToList(currentId, compact); } } } } //_collision->checkCollisions(); } void Logic::processLogic(Object *compact, uint32 id) { int logicRet; do { switch (compact->o_logic) { case LOGIC_idle: logicRet = 0; break; case LOGIC_pause: case LOGIC_pause_for_event: if (compact->o_pause) { compact->o_pause--; logicRet = 0; } else { compact->o_logic = LOGIC_script; logicRet = 1; } break; case LOGIC_quit: compact->o_logic = LOGIC_script; logicRet = 0; break; case LOGIC_wait_for_sync: if (compact->o_sync) { logicRet = 1; compact->o_logic = LOGIC_script; } else logicRet = 0; break; case LOGIC_choose: _scriptVars[CUR_ID] = id; logicRet = _menu->logicChooser(compact); break; case LOGIC_wait_for_talk: logicRet = logicWaitTalk(compact); break; case LOGIC_start_talk: logicRet = logicStartTalk(compact); break; case LOGIC_script: _scriptVars[CUR_ID] = id; logicRet = scriptManager(compact, id); break; case LOGIC_new_script: compact->o_tree.o_script_pc[compact->o_tree.o_script_level] = _newScript; compact->o_tree.o_script_id[compact->o_tree.o_script_level] = _newScript; compact->o_logic = LOGIC_script; logicRet = 1; break; case LOGIC_AR_animate: logicRet = logicArAnimate(compact, id); break; case LOGIC_restart: compact->o_tree.o_script_pc[compact->o_tree.o_script_level] = compact->o_tree.o_script_id[compact->o_tree.o_script_level]; compact->o_logic = LOGIC_script; logicRet = 1; break; case LOGIC_bookmark: memcpy(&(compact->o_tree.o_script_level), &(compact->o_bookmark.o_script_level), sizeof(ScriptTree)); if (id == GMASTER_79) { // workaround for ending script. // GMASTER_79 is not prepared for mega_interact receiving INS_quit fnSuicide(compact, id, 0, 0, 0, 0, 0, 0); logicRet = 0; } else { compact->o_logic = LOGIC_script; logicRet = 1; } break; case LOGIC_speech: logicRet = speechDriver(compact); break; case LOGIC_full_anim: logicRet = fullAnimDriver(compact); break; case LOGIC_anim: logicRet = animDriver(compact); break; default: error("Fatal error: compact %d's logic == %X", id, compact->o_logic); break; } } while (logicRet); } int Logic::logicWaitTalk(Object *compact) { Object *target = _objMan->fetchObject(compact->o_down_flag); if (target->o_status & STAT_TALK_WAIT) { compact->o_logic = LOGIC_script; return 1; } else { return 0; } } int Logic::logicStartTalk(Object *compact) { Object *target = _objMan->fetchObject(compact->o_down_flag); //holds id of person we're waiting for if (target->o_status & STAT_TALK_WAIT) { //response? compact->o_logic = LOGIC_script; //back to script again return SCRIPT_CONT; } if (_eventMan->eventValid(compact->o_down_flag)) return SCRIPT_STOP; //event still valid - keep waiting //the event has gone - so back to script with error code compact->o_down_flag = 0; compact->o_logic = LOGIC_script; return SCRIPT_CONT; } int Logic::logicArAnimate(Object *compact, uint32 id) { WalkData *route; int32 walkPc; if ((_scriptVars[GEORGE_WALKING] == 0) && (id == PLAYER)) _scriptVars[GEORGE_WALKING] = 1; compact->o_resource = compact->o_walk_resource; compact->o_status |= STAT_SHRINK; route = compact->o_route; walkPc = compact->o_walk_pc; compact->o_frame = route[walkPc].frame; compact->o_dir = route[walkPc].dir; compact->o_xcoord = route[walkPc].x; compact->o_ycoord = route[walkPc].y; compact->o_anim_x = compact->o_xcoord; compact->o_anim_y = compact->o_ycoord; if (((_scriptVars[GEORGE_WALKING] == 2) && (walkPc > 5) && (id == PLAYER) && (route[walkPc - 1].step == 5) && (route[walkPc].step == 0)) || ((_scriptVars[GEORGE_WALKING] == 3) && (id == PLAYER))) { compact->o_frame = 96 + compact->o_dir; //reset if ((compact->o_dir != 2) && (compact->o_dir != 6)) { // on verticals and diagonals stand where george is compact->o_xcoord = route[walkPc - 1].x; compact->o_ycoord = route[walkPc - 1].y; compact->o_anim_x = compact->o_xcoord; compact->o_anim_y = compact->o_ycoord; } compact->o_logic = LOGIC_script; compact->o_down_flag = 0; //0 means error _scriptVars[GEORGE_WALKING] = 0; route[compact->o_walk_pc + 1].frame = 512; //end of sequence if (_scriptVars[MEGA_ON_GRID] == 2) _scriptVars[MEGA_ON_GRID] = 0; } compact->o_walk_pc++; if (route[compact->o_walk_pc].frame == 512) { //end of sequence compact->o_logic = LOGIC_script; if (((_scriptVars[GEORGE_WALKING] == 2) || (_scriptVars[GEORGE_WALKING] == 1)) && (id == PLAYER)) { _scriptVars[GEORGE_WALKING] = 0; if (_scriptVars[MEGA_ON_GRID] == 2) _scriptVars[MEGA_ON_GRID] = 0; } } return 0; } int Logic::speechDriver(Object *compact) { if ((!_speechClickDelay) && (_mouse->testEvent() & BS1L_BUTTON_DOWN)) _speechFinished = true; if (_speechClickDelay) _speechClickDelay--; if (_speechRunning) { if (_sound->speechFinished()) _speechFinished = true; } else { if (!compact->o_speech_time) _speechFinished = true; else compact->o_speech_time--; } if (_speechFinished) { if (_speechRunning) _sound->stopSpeech(); compact->o_logic = LOGIC_script; if (_textRunning) { _textMan->releaseText(compact->o_text_id); _objMan->fetchObject(compact->o_text_id)->o_status = 0; // kill compact linking text sprite } _speechRunning = _textRunning = false; _speechFinished = true; } if (compact->o_anim_resource) { uint8 *animData = ((uint8 *)_resMan->openFetchRes(compact->o_anim_resource)) + sizeof(Header); int32 numFrames = _resMan->readUint32(animData); animData += 4; compact->o_anim_pc++; // go to next frame of anim if (_speechFinished || (compact->o_anim_pc >= numFrames) || (_speechRunning && (_sound->amISpeaking() == 0))) compact->o_anim_pc = 0; //set to frame 0, closed mouth AnimUnit *animPtr = (AnimUnit *)(animData + sizeof(AnimUnit) * compact->o_anim_pc); if (!(compact->o_status & STAT_SHRINK)) { compact->o_anim_x = _resMan->getUint32(animPtr->animX); compact->o_anim_y = _resMan->getUint32(animPtr->animY); } compact->o_frame = _resMan->getUint32(animPtr->animFrame); _resMan->resClose(compact->o_anim_resource); } return 0; } int Logic::fullAnimDriver(Object *compact) { if (compact->o_sync) { // return to script immediately if we've received a sync compact->o_logic = LOGIC_script; return 1; } uint8 *data = ((uint8 *)_resMan->openFetchRes(compact->o_anim_resource)) + sizeof(Header); uint32 numFrames = _resMan->readUint32(data); data += 4; AnimUnit *animPtr = (AnimUnit *)(data + compact->o_anim_pc * sizeof(AnimUnit)); compact->o_anim_x = compact->o_xcoord = _resMan->getUint32(animPtr->animX); compact->o_anim_y = compact->o_ycoord = _resMan->getUint32(animPtr->animY); compact->o_frame = _resMan->getUint32(animPtr->animFrame); compact->o_anim_pc++; if (compact->o_anim_pc == (int)numFrames) compact->o_logic = LOGIC_script; _resMan->resClose(compact->o_anim_resource); return 0; } int Logic::animDriver(Object *compact) { if (compact->o_sync) { compact->o_logic = LOGIC_script; return 1; } uint8 *data = ((uint8 *)_resMan->openFetchRes(compact->o_anim_resource)) + sizeof(Header); uint32 numFrames = _resMan->readUint32(data); AnimUnit *animPtr = (AnimUnit *)(data + 4 + compact->o_anim_pc * sizeof(AnimUnit)); if (!(compact->o_status & STAT_SHRINK)) { compact->o_anim_x = _resMan->getUint32(animPtr->animX); compact->o_anim_y = _resMan->getUint32(animPtr->animY); } compact->o_frame = _resMan->getUint32(animPtr->animFrame); compact->o_anim_pc++; if (compact->o_anim_pc == (int)numFrames) compact->o_logic = LOGIC_script; _resMan->resClose(compact->o_anim_resource); return 0; } void Logic::updateScreenParams() { Object *compact = (Object *)_objMan->fetchObject(PLAYER); _screen->setScrolling((int16)(compact->o_xcoord - _scriptVars[FEET_X]), (int16)(compact->o_ycoord - _scriptVars[FEET_Y])); } int Logic::scriptManager(Object *compact, uint32 id) { int ret; do { uint32 level = compact->o_tree.o_script_level; uint32 script = compact->o_tree.o_script_id[level]; Debug::interpretScript(id, level, script, compact->o_tree.o_script_pc[level] & ITM_ID); ret = interpretScript(compact, id, _resMan->lockScript(script), script, compact->o_tree.o_script_pc[level] & ITM_ID); _resMan->unlockScript(script); if (!ret) { if (compact->o_tree.o_script_level) compact->o_tree.o_script_level--; else error("ScriptManager: basescript %d for cpt %d ended", script, id); } else compact->o_tree.o_script_pc[level] = ret; } while (!ret); return 1; //Logic continues - but the script must have changed logic mode //this is a radical change from S2.0 where once a script finished there //was no more processing for that object on that cycle - the Logic_engine terminated. //This meant that new logics that needed immediate action got a first call from the //setup function. This was a bit tweeky. This technique ensures that the script is a //totally seamless concept that takes up zero cycle time. The only downside is that //an FN_quit becomes slightly more convoluted, but so what you might ask. } void Logic::runMouseScript(Object *cpt, int32 scriptId) { Header *script = _resMan->lockScript(scriptId); debug(9, "running mouse script %d", scriptId); interpretScript(cpt, _scriptVars[SPECIAL_ITEM], script, scriptId, scriptId); _resMan->unlockScript(scriptId); } int Logic::interpretScript(Object *compact, int id, Header *scriptModule, int scriptBase, int scriptNum) { int32 *scriptCode = (int32 *)(((uint8 *)scriptModule) + sizeof(Header)); int32 stack[MAX_STACK_SIZE]; int32 stackIdx = 0; int32 offset; int32 pc; if (memcmp(scriptModule->type, "Script", 6)) error("Invalid script module"); if (scriptModule->version != SCRIPT_VERSION) error("Illegal script version"); if (scriptNum < 0) error("negative script number"); if ((uint32)scriptNum >= scriptModule->decomp_length) error("Script number out of bounds"); if (scriptNum < scriptCode[0]) pc = scriptCode[scriptNum + 1]; else pc = scriptNum; int32 startOfScript = scriptCode[(scriptBase & ITM_ID) + 1]; int32 a, b, c, d, e, f; int mCodeReturn = 0; int32 mCodeNumber = 0, mCodeArguments = 0; uint32 varNum = 0; while (1) { assert((stackIdx >= 0) && (stackIdx <= MAX_STACK_SIZE)); switch (scriptCode[pc++]) { case IT_MCODE: a = b = c = d = e = f = 0; mCodeNumber = scriptCode[pc++]; mCodeArguments = scriptCode[pc++]; switch (mCodeArguments) { case 6: f = stack[--stackIdx]; case 5: e = stack[--stackIdx]; case 4: d = stack[--stackIdx]; case 3: c = stack[--stackIdx]; case 2: b = stack[--stackIdx]; case 1: a = stack[--stackIdx]; case 0: Debug::callMCode(mCodeNumber, mCodeArguments, a, b, c, d, e, f); mCodeReturn = (this->*_mcodeTable[mCodeNumber])(compact, id, a, b, c, d, e, f); break; default: warning("mcode[%d]: too many arguments(%d)", mCodeNumber, mCodeArguments); } if (mCodeReturn == 0) return pc; break; case IT_PUSHNUMBER: debug(9, "IT_PUSH: %d", scriptCode[pc]); stack[stackIdx++] = scriptCode[pc++]; break; case IT_PUSHVARIABLE: debug(9, "IT_PUSHVARIABLE: ScriptVar[%d] => %d", scriptCode[pc], _scriptVars[scriptCode[pc]]); varNum = scriptCode[pc++]; if (SwordEngine::_systemVars.isDemo && SwordEngine::isPc()) { if (varNum >= 397) // BS1 Demo has different number of script variables varNum++; if (varNum >= 699) varNum++; } stack[stackIdx++] = _scriptVars[varNum]; break; case IT_NOTEQUAL: stackIdx--; debug(9, "IT_NOTEQUAL: RESULT = %d", stack[stackIdx - 1] != stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] != stack[stackIdx]); break; case IT_ISEQUAL: stackIdx--; debug(9, "IT_ISEQUAL: RESULT = %d", stack[stackIdx - 1] == stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] == stack[stackIdx]); break; case IT_PLUS: stackIdx--; debug(9, "IT_PLUS: RESULT = %d", stack[stackIdx - 1] + stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] + stack[stackIdx]); break; case IT_TIMES: stackIdx--; debug(9, "IT_TIMES: RESULT = %d", stack[stackIdx - 1] * stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] * stack[stackIdx]); break; case IT_ANDAND: stackIdx--; debug(9, "IT_ANDAND: RESULT = %d", stack[stackIdx - 1] && stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] && stack[stackIdx]); break; case IT_OROR: // || stackIdx--; debug(9, "IT_OROR: RESULT = %d", stack[stackIdx - 1] || stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] || stack[stackIdx]); break; case IT_LESSTHAN: stackIdx--; debug(9, "IT_LESSTHAN: RESULT = %d", stack[stackIdx - 1] < stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] < stack[stackIdx]); break; case IT_NOT: debug(9, "IT_NOT: RESULT = %d", stack[stackIdx - 1] ? 0 : 1); if (stack[stackIdx - 1]) stack[stackIdx - 1] = 0; else stack[stackIdx - 1] = 1; break; case IT_MINUS: stackIdx--; debug(9, "IT_MINUS: RESULT = %d", stack[stackIdx - 1] - stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] - stack[stackIdx]); break; case IT_AND: stackIdx--; debug(9, "IT_AND: RESULT = %d", stack[stackIdx - 1] & stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] & stack[stackIdx]); break; case IT_OR: stackIdx--; debug(9, "IT_OR: RESULT = %d", stack[stackIdx - 1] | stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] | stack[stackIdx]); break; case IT_GTE: stackIdx--; debug(9, "IT_GTE: RESULT = %d", stack[stackIdx - 1] >= stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] >= stack[stackIdx]); break; case IT_LTE: stackIdx--; debug(9, "IT_LTE: RESULT = %d", stack[stackIdx - 1] <= stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] <= stack[stackIdx]); break; case IT_DEVIDE: stackIdx--; debug(9, "IT_DEVIDE: RESULT = %d", stack[stackIdx - 1] / stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] / stack[stackIdx]); break; case IT_GT: stackIdx--; debug(9, "IT_GT: RESULT = %d", stack[stackIdx - 1] > stack[stackIdx]); stack[stackIdx - 1] = (stack[stackIdx - 1] > stack[stackIdx]); break; case IT_SCRIPTEND: debug(9, "IT_SCRIPTEND"); return 0; case IT_POPVAR: // pop a variable debug(9, "IT_POPVAR: ScriptVars[%d] = %d", scriptCode[pc], stack[stackIdx - 1]); varNum = scriptCode[pc++]; if (SwordEngine::_systemVars.isDemo && SwordEngine::isPc()) { if (varNum >= 397) // BS1 Demo has different number of script variables varNum++; if (varNum >= 699) varNum++; } _scriptVars[varNum] = stack[--stackIdx]; break; case IT_POPLONGOFFSET: offset = scriptCode[pc++]; debug(9, "IT_POPLONGOFFSET: Cpt[%d] = %d", offset, stack[stackIdx - 1]); *((int32 *)((uint8 *)compact + offset)) = stack[--stackIdx]; break; case IT_PUSHLONGOFFSET: offset = scriptCode[pc++]; debug(9, "IT_PUSHLONGOFFSET: PUSH Cpt[%d] (==%d)", offset, *((int32 *)((uint8 *)compact + offset))); stack[stackIdx++] = *((int32 *)((uint8 *)compact + offset)); break; case IT_SKIPONFALSE: debug(9, "IT_SKIPONFALSE: %d (%s)", scriptCode[pc], (stack[stackIdx - 1] ? "IS TRUE (NOT SKIPPED)" : "IS FALSE (SKIPPED)")); if (stack[--stackIdx]) pc++; else pc += scriptCode[pc]; break; case IT_SKIP: debug(9, "IT_SKIP: %d", scriptCode[pc]); pc += scriptCode[pc]; break; case IT_SWITCH: // The mega switch statement debug(9, "IT_SWITCH: [SORRY, NO DEBUG INFO]"); { int switchValue = stack[--stackIdx]; int switchCount = scriptCode[pc++]; int doneSwitch = 0; for (int cnt = 0; (cnt < switchCount) && (doneSwitch == 0); cnt++) { if (switchValue == scriptCode[pc]) { pc += scriptCode[pc + 1]; doneSwitch = 1; } else pc += 2; } if (doneSwitch == 0) pc += scriptCode[pc]; } break; case IT_SKIPONTRUE: // skip if expression true debug(9, "IT_SKIPONTRUE: %d (%s)", scriptCode[pc], (stack[stackIdx - 1] ? "IS TRUE (SKIPPED)" : "IS FALSE (NOT SKIPPED)")); stackIdx--; if (stack[stackIdx]) pc += scriptCode[pc]; else pc++; break; case IT_PRINTF: debug(0, "IT_PRINTF(%d)", stack[stackIdx]); break; case IT_RESTARTSCRIPT: debug(9, "IT_RESTARTSCRIPT"); pc = startOfScript; break; case IT_POPWORDOFFSET: offset = scriptCode[pc++]; debug(9, "IT_POPWORDOFFSET: Cpt[%d] = %d", offset, stack[stackIdx - 1] & 0xFFFF); *((int32 *)((uint8 *)compact + offset)) = stack[--stackIdx] & 0xffff; break; case IT_PUSHWORDOFFSET: offset = scriptCode[pc++]; debug(9, "IT_PUSHWORDOFFSET: PUSH Cpt[%d] == %d", offset, (*((int32 *)((uint8 *)compact + offset))) & 0xffff); stack[stackIdx++] = (*((int32 *)((uint8 *)compact + offset))) & 0xffff; break; default: error("Invalid operator %d", scriptCode[pc - 1]); return 0; // for compilers that don't support NORETURN } } } void Logic::setupMcodeTable() { static const BSMcodeTable mcodeTable[100] = { &Logic::fnBackground, &Logic::fnForeground, &Logic::fnSort, &Logic::fnNoSprite, &Logic::fnMegaSet, &Logic::fnAnim, &Logic::fnSetFrame, &Logic::fnFullAnim, &Logic::fnFullSetFrame, &Logic::fnFadeDown, &Logic::fnFadeUp, &Logic::fnCheckFade, &Logic::fnSetSpritePalette, &Logic::fnSetWholePalette, &Logic::fnSetFadeTargetPalette, &Logic::fnSetPaletteToFade, &Logic::fnSetPaletteToCut, &Logic::fnPlaySequence, &Logic::fnIdle, &Logic::fnPause, &Logic::fnPauseSeconds, &Logic::fnQuit, &Logic::fnKillId, &Logic::fnSuicide, &Logic::fnNewScript, &Logic::fnSubScript, &Logic::fnRestartScript, &Logic::fnSetBookmark, &Logic::fnGotoBookmark, &Logic::fnSendSync, &Logic::fnWaitSync, &Logic::cfnClickInteract, &Logic::cfnSetScript, &Logic::cfnPresetScript, &Logic::fnInteract, &Logic::fnIssueEvent, &Logic::fnCheckForEvent, &Logic::fnWipeHands, &Logic::fnISpeak, &Logic::fnTheyDo, &Logic::fnTheyDoWeWait, &Logic::fnWeWait, &Logic::fnChangeSpeechText, &Logic::fnTalkError, &Logic::fnStartTalk, &Logic::fnCheckForTextLine, &Logic::fnAddTalkWaitStatusBit, &Logic::fnRemoveTalkWaitStatusBit, &Logic::fnNoHuman, &Logic::fnAddHuman, &Logic::fnBlankMouse, &Logic::fnNormalMouse, &Logic::fnLockMouse, &Logic::fnUnlockMouse, &Logic::fnSetMousePointer, &Logic::fnSetMouseLuggage, &Logic::fnMouseOn, &Logic::fnMouseOff, &Logic::fnChooser, &Logic::fnEndChooser, &Logic::fnStartMenu, &Logic::fnEndMenu, &Logic::cfnReleaseMenu, &Logic::fnAddSubject, &Logic::fnAddObject, &Logic::fnRemoveObject, &Logic::fnEnterSection, &Logic::fnLeaveSection, &Logic::fnChangeFloor, &Logic::fnWalk, &Logic::fnTurn, &Logic::fnStand, &Logic::fnStandAt, &Logic::fnFace, &Logic::fnFaceXy, &Logic::fnIsFacing, &Logic::fnGetTo, &Logic::fnGetToError, &Logic::fnGetPos, &Logic::fnGetGamepadXy, &Logic::fnPlayFx, &Logic::fnStopFx, &Logic::fnPlayMusic, &Logic::fnStopMusic, &Logic::fnInnerSpace, &Logic::fnRandom, &Logic::fnSetScreen, &Logic::fnPreload, &Logic::fnCheckCD, &Logic::fnRestartGame, &Logic::fnQuitGame, &Logic::fnDeathScreen, &Logic::fnSetParallax, &Logic::fnTdebug, &Logic::fnRedFlash, &Logic::fnBlueFlash, &Logic::fnYellow, &Logic::fnGreen, &Logic::fnPurple, &Logic::fnBlack }; _mcodeTable = mcodeTable; } int Logic::fnBackground(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status &= ~(STAT_FORE | STAT_SORT); cpt->o_status |= STAT_BACK; return SCRIPT_CONT; } int Logic::fnForeground(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status &= ~(STAT_BACK | STAT_SORT); cpt->o_status |= STAT_FORE; return SCRIPT_CONT; } int Logic::fnSort(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status &= ~(STAT_BACK | STAT_FORE); cpt->o_status |= STAT_SORT; return SCRIPT_CONT; } int Logic::fnNoSprite(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status &= ~(STAT_BACK | STAT_FORE | STAT_SORT); return SCRIPT_CONT; } int Logic::fnMegaSet(Object *cpt, int32 id, int32 walk_data, int32 spr, int32 e, int32 f, int32 z, int32 x) { cpt->o_mega_resource = walk_data; cpt->o_walk_resource = spr; return SCRIPT_CONT; } int Logic::fnAnim(Object *cpt, int32 id, int32 cdt, int32 spr, int32 e, int32 f, int32 z, int32 x) { AnimSet *animTab; if (cdt && (!spr)) { animTab = (AnimSet *)((uint8 *)_resMan->openFetchRes(cdt) + sizeof(Header)); animTab += cpt->o_dir; cpt->o_anim_resource = _resMan->getUint32(animTab->cdt); cpt->o_resource = _resMan->getUint32(animTab->spr); _resMan->resClose(cdt); } else { cpt->o_anim_resource = cdt; cpt->o_resource = spr; } if ((cpt->o_anim_resource == 0) || (cpt->o_resource == 0)) error("fnAnim called width (%d/%d) => (%d/%d)", cdt, spr, cpt->o_anim_resource, cpt->o_resource); FrameHeader *frameHead = _resMan->fetchFrame(_resMan->openFetchRes(cpt->o_resource), 0); if (frameHead->offsetX || frameHead->offsetY) { // boxed mega anim? cpt->o_status |= STAT_SHRINK; cpt->o_anim_x = cpt->o_xcoord; // set anim coords to 'feet' coords - only need to do this once cpt->o_anim_y = cpt->o_ycoord; } else { // Anim_driver sets anim coords to cdt coords for every frame of a loose anim cpt->o_status &= ~STAT_SHRINK; } _resMan->resClose(cpt->o_resource); cpt->o_logic = LOGIC_anim; cpt->o_anim_pc = 0; cpt->o_sync = 0; return SCRIPT_STOP; } int Logic::fnSetFrame(Object *cpt, int32 id, int32 cdt, int32 spr, int32 frameNo, int32 f, int32 z, int32 x) { AnimUnit *animPtr; uint8 *data = (uint8 *)_resMan->openFetchRes(cdt); data += sizeof(Header); if (frameNo == LAST_FRAME) frameNo = _resMan->readUint32(data) - 1; data += 4; animPtr = (AnimUnit *)(data + frameNo * sizeof(AnimUnit)); cpt->o_anim_x = _resMan->getUint32(animPtr->animX); cpt->o_anim_y = _resMan->getUint32(animPtr->animY); cpt->o_frame = _resMan->getUint32(animPtr->animFrame); cpt->o_resource = spr; cpt->o_status &= ~STAT_SHRINK; _resMan->resClose(cdt); return SCRIPT_CONT; } int Logic::fnFullAnim(Object *cpt, int32 id, int32 anim, int32 graphic, int32 e, int32 f, int32 z, int32 x) { cpt->o_logic = LOGIC_full_anim; cpt->o_anim_pc = 0; cpt->o_anim_resource = anim; cpt->o_resource = graphic; cpt->o_status &= ~STAT_SHRINK; cpt->o_sync = 0; return SCRIPT_STOP; } int Logic::fnFullSetFrame(Object *cpt, int32 id, int32 cdt, int32 spr, int32 frameNo, int32 f, int32 z, int32 x) { uint8 *data = (uint8 *)_resMan->openFetchRes(cdt) + sizeof(Header); if (frameNo == LAST_FRAME) frameNo = _resMan->readUint32(data) - 1; data += 4; AnimUnit *animPtr = (AnimUnit *)(data + sizeof(AnimUnit) * frameNo); cpt->o_anim_x = cpt->o_xcoord = _resMan->getUint32(animPtr->animX); cpt->o_anim_y = cpt->o_ycoord = _resMan->getUint32(animPtr->animY); cpt->o_frame = _resMan->getUint32(animPtr->animFrame); cpt->o_resource = spr; cpt->o_status &= ~STAT_SHRINK; _resMan->resClose(cdt); return SCRIPT_CONT; } int Logic::fnFadeDown(Object *cpt, int32 id, int32 speed, int32 d, int32 e, int32 f, int32 z, int32 x) { _screen->fadeDownPalette(); return SCRIPT_CONT; } int Logic::fnFadeUp(Object *cpt, int32 id, int32 speed, int32 d, int32 e, int32 f, int32 z, int32 x) { _screen->fadeUpPalette(); return SCRIPT_CONT; } int Logic::fnCheckFade(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _scriptVars[RETURN_VALUE] = (uint8)_screen->stillFading(); return SCRIPT_CONT; } int Logic::fnSetSpritePalette(Object *cpt, int32 id, int32 spritePal, int32 d, int32 e, int32 f, int32 z, int32 x) { _screen->fnSetPalette(184, 72, spritePal, false); return SCRIPT_CONT; } int Logic::fnSetWholePalette(Object *cpt, int32 id, int32 spritePal, int32 d, int32 e, int32 f, int32 z, int32 x) { _screen->fnSetPalette(0, 256, spritePal, false); return SCRIPT_CONT; } int Logic::fnSetFadeTargetPalette(Object *cpt, int32 id, int32 spritePal, int32 d, int32 e, int32 f, int32 z, int32 x) { _screen->fnSetPalette(0, 184, spritePal, true); return SCRIPT_CONT; } int Logic::fnSetPaletteToFade(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { SwordEngine::_systemVars.wantFade = true; return SCRIPT_CONT; } int Logic::fnSetPaletteToCut(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { SwordEngine::_systemVars.wantFade = false; return SCRIPT_CONT; } int Logic::fnPlaySequence(Object *cpt, int32 id, int32 sequenceId, int32 d, int32 e, int32 f, int32 z, int32 x) { // A cutscene usually (always?) means the room will change. In the // meantime, we don't want any looping sound effects still playing. _sound->quitScreen(); MoviePlayer *player = makeMoviePlayer(sequenceId, _vm, _textMan, _mixer, _system); if (player) { _screen->clearScreen(); if (player->load(sequenceId)) player->play(); delete player; } return SCRIPT_CONT; } int Logic::fnIdle(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_tree.o_script_level = 0; // force to level 0 cpt->o_logic = LOGIC_idle; return SCRIPT_STOP; } int Logic::fnPause(Object *cpt, int32 id, int32 pause, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_pause = pause; cpt->o_logic = LOGIC_pause; return SCRIPT_STOP; } int Logic::fnPauseSeconds(Object *cpt, int32 id, int32 pause, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_pause = pause * FRAME_RATE; cpt->o_logic = LOGIC_pause; return SCRIPT_STOP; } int Logic::fnQuit(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_logic = LOGIC_quit; return SCRIPT_STOP; } int Logic::fnKillId(Object *cpt, int32 id, int32 target, int32 d, int32 e, int32 f, int32 z, int32 x) { Object *targetObj = _objMan->fetchObject(target); targetObj->o_status = 0; return SCRIPT_CONT; } int Logic::fnSuicide(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status = 0; cpt->o_logic = LOGIC_quit; return SCRIPT_STOP; } int Logic::fnNewScript(Object *cpt, int32 id, int32 script, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_logic = LOGIC_new_script; _newScript = script; return SCRIPT_STOP; } int Logic::fnSubScript(Object *cpt, int32 id, int32 script, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_tree.o_script_level++; if (cpt->o_tree.o_script_level == TOTAL_script_levels) error("Compact %d: script level exceeded in fnSubScript", id); cpt->o_tree.o_script_pc[cpt->o_tree.o_script_level] = script; cpt->o_tree.o_script_id[cpt->o_tree.o_script_level] = script; return SCRIPT_STOP; } int Logic::fnRestartScript(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_logic = LOGIC_restart; return SCRIPT_STOP; } int Logic::fnSetBookmark(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { memcpy(&cpt->o_bookmark.o_script_level, &cpt->o_tree.o_script_level, sizeof(ScriptTree)); return SCRIPT_CONT; } int Logic::fnGotoBookmark(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_logic = LOGIC_bookmark; return SCRIPT_STOP; } int Logic::fnSendSync(Object *cpt, int32 id, int32 sendId, int32 syncValue, int32 e, int32 f, int32 z, int32 x) { Object *target = _objMan->fetchObject(sendId); target->o_sync = syncValue; return SCRIPT_CONT; } int Logic::fnWaitSync(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_logic = LOGIC_wait_for_sync; return SCRIPT_STOP; } int Logic::cfnClickInteract(Object *cpt, int32 id, int32 target, int32 d, int32 e, int32 f, int32 z, int32 x) { Object *tar = _objMan->fetchObject(target); cpt = _objMan->fetchObject(PLAYER); cpt->o_tree.o_script_level = 0; cpt->o_tree.o_script_pc[0] = tar->o_interact; cpt->o_tree.o_script_id[0] = tar->o_interact; cpt->o_logic = LOGIC_script; return SCRIPT_STOP; } int Logic::cfnSetScript(Object *cpt, int32 id, int32 target, int32 script, int32 e, int32 f, int32 z, int32 x) { Object *tar = _objMan->fetchObject(target); tar->o_tree.o_script_level = 0; tar->o_tree.o_script_pc[0] = script; tar->o_tree.o_script_id[0] = script; tar->o_logic = LOGIC_script; return SCRIPT_CONT; } int Logic::cfnPresetScript(Object *cpt, int32 id, int32 target, int32 script, int32 e, int32 f, int32 z, int32 x) { Object *tar = _objMan->fetchObject(target); tar->o_tree.o_script_level = 0; tar->o_tree.o_script_pc[0] = script; tar->o_tree.o_script_id[0] = script; if (tar->o_logic == LOGIC_idle) tar->o_logic = LOGIC_script; return SCRIPT_CONT; } int Logic::fnInteract(Object *cpt, int32 id, int32 target, int32 d, int32 e, int32 f, int32 z, int32 x) { Object *tar = _objMan->fetchObject(target); cpt->o_place = tar->o_place; Object *floorObject = _objMan->fetchObject(tar->o_place); cpt->o_scale_a = floorObject->o_scale_a; cpt->o_scale_b = floorObject->o_scale_b; cpt->o_tree.o_script_level++; cpt->o_tree.o_script_pc[cpt->o_tree.o_script_level] = tar->o_interact; cpt->o_tree.o_script_id[cpt->o_tree.o_script_level] = tar->o_interact; return SCRIPT_STOP; } int Logic::fnIssueEvent(Object *cpt, int32 id, int32 event, int32 delay, int32 e, int32 f, int32 z, int32 x) { _eventMan->fnIssueEvent(cpt, id, event, delay); return SCRIPT_CONT; } int Logic::fnCheckForEvent(Object *cpt, int32 id, int32 pause, int32 d, int32 e, int32 f, int32 z, int32 x) { return _eventMan->fnCheckForEvent(cpt, id, pause); } int Logic::fnWipeHands(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _scriptVars[OBJECT_HELD] = 0; _mouse->setLuggage(0, 0); _menu->refresh(MENU_TOP); return SCRIPT_CONT; } int Logic::fnISpeak(Object *cpt, int32 id, int32 cdt, int32 textNo, int32 spr, int32 f, int32 z, int32 x) { _speechClickDelay = 3; if (((textNo & ~1) == 0x3f0012) && (!cdt) && (!spr)) { cdt = GEOSTDLCDT; // workaround for missing animation when examining spr = GEOSTDL; // the conductor on the train roof } cpt->o_logic = LOGIC_speech; // first setup the talk animation if (cdt && (!spr)) { // if 'cdt' is non-zero but 'spr' is zero - 'cdt' is an anim table tag AnimSet *animTab = (AnimSet *)((uint8 *)_resMan->openFetchRes(cdt) + sizeof(Header)); animTab += cpt->o_dir; cpt->o_anim_resource = _resMan->getUint32(animTab->cdt); if (animTab->cdt) cpt->o_resource = _resMan->getUint32(animTab->spr); _resMan->resClose(cdt); } else { cpt->o_anim_resource = cdt; if (cdt) cpt->o_resource = spr; } cpt->o_anim_pc = 0; // start anim from first frame if (cpt->o_anim_resource) { if (!cpt->o_resource) error("ID %d: Can't run anim with cdt=%d, spr=%d", id, cdt, spr); FrameHeader *frameHead = _resMan->fetchFrame(_resMan->openFetchRes(cpt->o_resource), 0); if (frameHead->offsetX && frameHead->offsetY) { // is this a boxed mega? cpt->o_status |= STAT_SHRINK; cpt->o_anim_x = cpt->o_xcoord; cpt->o_anim_y = cpt->o_ycoord; } else cpt->o_status &= ~STAT_SHRINK; _resMan->resClose(cpt->o_resource); } if (SwordEngine::_systemVars.playSpeech) _speechRunning = _sound->startSpeech(textNo >> 16, textNo & 0xFFFF); else _speechRunning = false; _speechFinished = false; if (SwordEngine::_systemVars.showText || (!_speechRunning)) { _textRunning = true; char *text = _objMan->lockText(textNo); cpt->o_speech_time = strlen(text) + 5; uint32 textCptId = _textMan->lowTextManager((uint8 *)text, cpt->o_speech_width, (uint8)cpt->o_speech_pen); _objMan->unlockText(textNo); Object *textCpt = _objMan->fetchObject(textCptId); textCpt->o_screen = cpt->o_screen; textCpt->o_target = textCptId; // the graphic is a property of Text, so we don't lock/unlock it. uint16 textSpriteWidth = _resMan->getUint16(_textMan->giveSpriteData(textCpt->o_target)->width); uint16 textSpriteHeight = _resMan->getUint16(_textMan->giveSpriteData(textCpt->o_target)->height); cpt->o_text_id = textCptId; // now set text coords, above the player, usually #define TEXT_MARGIN 3 // distance kept from edges of screen #define ABOVE_HEAD 20 // distance kept above talking sprite uint16 textX, textY; if (((id == GEORGE) || ((id == NICO) && (_scriptVars[SCREEN] == 10))) && (!cpt->o_anim_resource)) { // if George is doing Voice-Over text (centered at the bottom of the screen) textX = _scriptVars[SCROLL_OFFSET_X] + 128 + (640 / 2) - textSpriteWidth / 2; textY = _scriptVars[SCROLL_OFFSET_Y] + 128 + 400; } else { if ((id == GEORGE) && (_scriptVars[SCREEN] == 79)) textX = cpt->o_mouse_x2; // move it off george's head else textX = (cpt->o_mouse_x1 + cpt->o_mouse_x2) / 2 - textSpriteWidth / 2; textY = cpt->o_mouse_y1 - textSpriteHeight - ABOVE_HEAD; } // now ensure text is within visible screen uint16 textLeftMargin, textRightMargin, textTopMargin, textBottomMargin; textLeftMargin = SCREEN_LEFT_EDGE + TEXT_MARGIN + _scriptVars[SCROLL_OFFSET_X]; textRightMargin = SCREEN_RIGHT_EDGE - TEXT_MARGIN + _scriptVars[SCROLL_OFFSET_X] - textSpriteWidth; textTopMargin = SCREEN_TOP_EDGE + TEXT_MARGIN + _scriptVars[SCROLL_OFFSET_Y]; textBottomMargin = SCREEN_BOTTOM_EDGE - TEXT_MARGIN + _scriptVars[SCROLL_OFFSET_Y] - textSpriteHeight; textCpt->o_anim_x = textCpt->o_xcoord = inRange(textLeftMargin, textX, textRightMargin); textCpt->o_anim_y = textCpt->o_ycoord = inRange(textTopMargin, textY, textBottomMargin); } return SCRIPT_STOP; } //send instructions to mega in conversation with player //the instruction is interpreted by the script mega_interact int Logic::fnTheyDo(Object *cpt, int32 id, int32 tar, int32 instruc, int32 param1, int32 param2, int32 param3, int32 x) { Object *target; target = _objMan->fetchObject(tar); target->o_down_flag = instruc; // instruction for the mega target->o_ins1 = param1; target->o_ins2 = param2; target->o_ins3 = param3; return SCRIPT_CONT; } //send an instruction to mega we're talking to and wait //until it has finished before returning to script int Logic::fnTheyDoWeWait(Object *cpt, int32 id, int32 tar, int32 instruc, int32 param1, int32 param2, int32 param3, int32 x) { // workaround for scriptbug #928791: Freeze at hospital // in at least one game version, a script forgets to set sam_returning back to zero if ((tar == SAM) && (instruc == INS_talk) && (param2 == 2162856)) _scriptVars[SAM_RETURNING] = 0; Object *target = _objMan->fetchObject(tar); target->o_down_flag = instruc; // instruction for the mega target->o_ins1 = param1; target->o_ins2 = param2; target->o_ins3 = param3; target->o_status &= ~STAT_TALK_WAIT; cpt->o_logic = LOGIC_wait_for_talk; cpt->o_down_flag = tar; return SCRIPT_STOP; } int Logic::fnWeWait(Object *cpt, int32 id, int32 tar, int32 d, int32 e, int32 f, int32 z, int32 x) { Object *target = _objMan->fetchObject(tar); target->o_status &= ~STAT_TALK_WAIT; cpt->o_logic = LOGIC_wait_for_talk; cpt->o_down_flag = tar; return SCRIPT_STOP; } int Logic::fnChangeSpeechText(Object *cpt, int32 id, int32 tar, int32 width, int32 pen, int32 f, int32 z, int32 x) { Object *target = _objMan->fetchObject(tar); target->o_speech_width = width; target->o_speech_pen = pen; return SCRIPT_STOP; } //mega_interact has received an instruction it does not understand - //The game is halted for debugging. Maybe we'll remove this later. int Logic::fnTalkError(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { error("fnTalkError for id %d, instruction %d", id, cpt->o_down_flag); return SCRIPT_STOP; // for compilers that don't support NORETURN } int Logic::fnStartTalk(Object *cpt, int32 id, int32 target, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_down_flag = target; cpt->o_logic = LOGIC_start_talk; return SCRIPT_STOP; } int Logic::fnCheckForTextLine(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _scriptVars[RETURN_VALUE] = _objMan->fnCheckForTextLine(id); return SCRIPT_CONT; } int Logic::fnAddTalkWaitStatusBit(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status |= STAT_TALK_WAIT; return SCRIPT_CONT; } int Logic::fnRemoveTalkWaitStatusBit(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status &= ~STAT_TALK_WAIT; return SCRIPT_CONT; } int Logic::fnNoHuman(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _mouse->fnNoHuman(); return SCRIPT_CONT; } int Logic::fnAddHuman(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _mouse->fnAddHuman(); return SCRIPT_CONT; } int Logic::fnBlankMouse(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _mouse->fnBlankMouse(); return SCRIPT_CONT; } int Logic::fnNormalMouse(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _mouse->fnNormalMouse(); return SCRIPT_CONT; } int Logic::fnLockMouse(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _mouse->fnLockMouse(); return SCRIPT_CONT; } int Logic::fnUnlockMouse(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _mouse->fnUnlockMouse(); return SCRIPT_CONT; } int Logic::fnSetMousePointer(Object *cpt, int32 id, int32 tag, int32 rate, int32 e, int32 f, int32 z, int32 x) { _mouse->setPointer(tag, rate); return SCRIPT_CONT; } int Logic::fnSetMouseLuggage(Object *cpt, int32 id, int32 tag, int32 rate, int32 e, int32 f, int32 z, int32 x) { _mouse->setLuggage(tag, rate); return SCRIPT_CONT; } int Logic::fnMouseOn(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status |= STAT_MOUSE; return SCRIPT_CONT; } int Logic::fnMouseOff(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_status &= ~STAT_MOUSE; return SCRIPT_CONT; } int Logic::fnChooser(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _menu->fnChooser(cpt); return SCRIPT_STOP; } int Logic::fnEndChooser(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _menu->fnEndChooser(); return SCRIPT_CONT; } int Logic::fnStartMenu(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _menu->fnStartMenu(); return SCRIPT_CONT; } int Logic::fnEndMenu(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _menu->fnEndMenu(); return SCRIPT_CONT; } int Logic::cfnReleaseMenu(Object *cpt, int32 id, int32 c, int32 d, int32 e, int32 f, int32 z, int32 x) { _menu->cfnReleaseMenu(); return SCRIPT_STOP; } int Logic::fnAddSubject(Object *cpt, int32 id, int32 sub, int32 d, int32 e, int32 f, int32 z, int32 x) { _menu->fnAddSubject(sub); return SCRIPT_CONT; } int Logic::fnAddObject(Object *cpt, int32 id, int32 objectNo, int32 d, int32 e, int32 f, int32 z, int32 x) { _scriptVars[POCKET_1 + objectNo - 1] = 1; // basically means: carrying object objectNo = true; return SCRIPT_CONT; } int Logic::fnRemoveObject(Object *cpt, int32 id, int32 objectNo, int32 d, int32 e, int32 f, int32 z, int32 x) { _scriptVars[POCKET_1 + objectNo - 1] = 0; return SCRIPT_CONT; } int Logic::fnEnterSection(Object *cpt, int32 id, int32 screen, int32 d, int32 e, int32 f, int32 z, int32 x) { if (screen >= TOTAL_SECTIONS) error("mega %d tried entering section %d", id, screen); /* if (cpt->o_type == TYPE_PLAYER) ^= this was the original condition from the game sourcecode. not sure why it doesn't work*/ if (id == PLAYER) _scriptVars[NEW_SCREEN] = screen; else cpt->o_screen = screen; // move the mega _objMan->megaEntering(screen); return SCRIPT_CONT; } int Logic::fnLeaveSection(Object *cpt, int32 id, int32 oldScreen, int32 d, int32 e, int32 f, int32 z, int32 x) { if (oldScreen >= TOTAL_SECTIONS) error("mega %d leaving section %d", id, oldScreen); _objMan->megaLeaving(oldScreen, id); return SCRIPT_CONT; } int Logic::fnChangeFloor(Object *cpt, int32 id, int32 floor, int32 d, int32 e, int32 f, int32 z, int32 x) { cpt->o_place = floor; Object *floorCpt = _objMan->fetchObject(floor); cpt->o_scale_a = floorCpt->o_scale_a; cpt->o_scale_b = floorCpt->o_scale_b; return SCRIPT_CONT; } int Logic::fnWalk(Object *cpt, int32 id, int32 x, int32 y, int32 dir, int32 stance, int32 a, int32 b) { if (stance > 0) dir = 9; cpt->o_walk_pc = 0; cpt->o_route[1].frame = 512; // end of sequence if (id == PLAYER) _router->setPlayerTarget(x, y, dir, stance); int32 routeRes = _router->routeFinder(id, cpt, x, y, dir); if (id == PLAYER) { if ((routeRes == 1) || (routeRes == 2)) { _scriptVars[MEGA_ON_GRID] = 0; _scriptVars[REROUTE_GEORGE] = 0; } } if ((routeRes == 1) || (routeRes == 2)) { cpt->o_down_flag = 1; // 1 means okay. // if both mouse buttons were pressed on an exit => skip george's walk if ((id == GEORGE) && (_mouse->testEvent() == MOUSE_BOTH_BUTTONS)) { int32 target = _scriptVars[CLICK_ID]; // exceptions: compacts that use hand pointers but are not actually exits if ((target != LEFT_SCROLL_POINTER) && (target != RIGHT_SCROLL_POINTER) && (target != FLOOR_63) && (target != ROOF_63) && (target != GUARD_ROOF_63) && (target != LEFT_TREE_POINTER_71) && (target != RIGHT_TREE_POINTER_71)) { target = _objMan->fetchObject(_scriptVars[CLICK_ID])->o_mouse_on; if ((target >= SCR_exit0) && (target <= SCR_exit9)) { fnStandAt(cpt, id, x, y, dir, stance, 0, 0); return SCRIPT_STOP; } } } cpt->o_logic = LOGIC_AR_animate; return SCRIPT_STOP; } else if (routeRes == 3) cpt->o_down_flag = 1; // pretend it was successful else cpt->o_down_flag = 0; // 0 means error return SCRIPT_CONT; } int Logic::fnTurn(Object *cpt, int32 id, int32 dir, int32 stance, int32 c, int32 d, int32 a, int32 b) { if (stance > 0) dir = 9; int route = _router->routeFinder(id, cpt, cpt->o_xcoord, cpt->o_ycoord, dir); if (route) cpt->o_down_flag = 1; //1 means ok else cpt->o_down_flag = 0; //0 means error cpt->o_logic = LOGIC_AR_animate; cpt->o_walk_pc = 0; //reset return SCRIPT_STOP; } int Logic::fnStand(Object *cpt, int32 id, int32 dir, int32 stance, int32 c, int32 d, int32 a, int32 b) { if ((dir < 0) || (dir > 8)) { warning("fnStand:: invalid direction %d", dir); return SCRIPT_CONT; } if (dir == 8) dir = cpt->o_dir; cpt->o_resource = cpt->o_walk_resource; cpt->o_status |= STAT_SHRINK; cpt->o_anim_x = cpt->o_xcoord; cpt->o_anim_y = cpt->o_ycoord; cpt->o_frame = 96 + dir; cpt->o_dir = dir; return SCRIPT_STOP; } int Logic::fnStandAt(Object *cpt, int32 id, int32 x, int32 y, int32 dir, int32 stance, int32 a, int32 b) { if ((dir < 0) || (dir > 8)) { warning("fnStandAt:: invalid direction %d", dir); return SCRIPT_CONT; } if (dir == 8) dir = cpt->o_dir; cpt->o_xcoord = x; cpt->o_ycoord = y; return fnStand(cpt, id, dir, stance, 0, 0, 0, 0); } int Logic::fnFace(Object *cpt, int32 id, int32 targetId, int32 b, int32 c, int32 d, int32 a, int32 z) { Object *target = _objMan->fetchObject(targetId); int32 x, y; if ((target->o_type == TYPE_MEGA) || (target->o_type == TYPE_PLAYER)) { x = target->o_xcoord; y = target->o_ycoord; } else { x = (target->o_mouse_x1 + target->o_mouse_x2) / 2; y = target->o_mouse_y2; } int32 megaTarDir = whatTarget(cpt->o_xcoord, cpt->o_ycoord, x, y); fnTurn(cpt, id, megaTarDir, 0, 0, 0, 0, 0); return SCRIPT_STOP; } int Logic::fnFaceXy(Object *cpt, int32 id, int32 x, int32 y, int32 c, int32 d, int32 a, int32 b) { int megaTarDir = whatTarget(cpt->o_xcoord, cpt->o_ycoord, x, y); fnTurn(cpt, id, megaTarDir, 0, 0, 0, 0, 0); return SCRIPT_STOP; } int Logic::fnIsFacing(Object *cpt, int32 id, int32 targetId, int32 b, int32 c, int32 d, int32 a, int32 z) { Object *target = _objMan->fetchObject(targetId); int32 x, y, dir; if ((target->o_type == TYPE_MEGA) || (target->o_type == TYPE_PLAYER)) { x = target->o_xcoord; y = target->o_ycoord; dir = target->o_dir; } else error("fnIsFacing:: Target isn't a mega"); int32 lookDir = whatTarget(x, y, cpt->o_xcoord, cpt->o_ycoord); lookDir -= dir; lookDir = ABS(lookDir); if (lookDir > 4) lookDir = 8 - lookDir; _scriptVars[RETURN_VALUE] = lookDir; return SCRIPT_STOP; } int Logic::fnGetTo(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { Object *place = _objMan->fetchObject(cpt->o_place); cpt->o_tree.o_script_level++; cpt->o_tree.o_script_pc[cpt->o_tree.o_script_level] = place->o_get_to_script; cpt->o_tree.o_script_id[cpt->o_tree.o_script_level] = place->o_get_to_script; return SCRIPT_STOP; } int Logic::fnGetToError(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { debug(1, "fnGetToError: compact %d at place %d no get-to for target %d, click_id %d\n", id, cpt->o_place, cpt->o_target, _scriptVars[CLICK_ID]); return SCRIPT_CONT; } int Logic::fnRandom(Object *compact, int32 id, int32 min, int32 max, int32 e, int32 f, int32 z, int32 x) { _scriptVars[RETURN_VALUE] = _rnd.getRandomNumberRng(min, max); return SCRIPT_CONT; } int Logic::fnGetPos(Object *cpt, int32 id, int32 targetId, int32 b, int32 c, int32 d, int32 z, int32 x) { Object *target = _objMan->fetchObject(targetId); if ((target->o_type == TYPE_MEGA) || (target->o_type == TYPE_PLAYER)) { _scriptVars[RETURN_VALUE] = target->o_xcoord; _scriptVars[RETURN_VALUE_2] = target->o_ycoord; } else { _scriptVars[RETURN_VALUE] = (target->o_mouse_x1 + target->o_mouse_x2) / 2; _scriptVars[RETURN_VALUE_2] = target->o_mouse_y2; } _scriptVars[RETURN_VALUE_3] = target->o_dir; int32 megaSeperation; if (targetId == DUANE) megaSeperation = 70; // George & Duane stand with feet 70 pixels apart when at full scale else if (targetId == BENOIR) megaSeperation = 61; // George & Benoir else megaSeperation = 42; // George & Nico/Goinfre stand with feet 42 pixels apart when at full scale if (target->o_status & STAT_SHRINK) { int32 scale = (target->o_scale_a * target->o_ycoord + target->o_scale_b) / 256; _scriptVars[RETURN_VALUE_4] = (megaSeperation * scale) / 256; } else _scriptVars[RETURN_VALUE_4] = megaSeperation; return SCRIPT_CONT; } int Logic::fnGetGamepadXy(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { // playstation only return SCRIPT_CONT; } int Logic::fnPlayFx(Object *cpt, int32 id, int32 fxNo, int32 b, int32 c, int32 d, int32 z, int32 x) { _scriptVars[RETURN_VALUE] = _sound->addToQueue(fxNo); return SCRIPT_CONT; } int Logic::fnStopFx(Object *cpt, int32 id, int32 fxNo, int32 b, int32 c, int32 d, int32 z, int32 x) { _sound->fnStopFx(fxNo); //_sound->removeFromQueue(fxNo); return SCRIPT_CONT; } int Logic::fnPlayMusic(Object *cpt, int32 id, int32 tuneId, int32 loopFlag, int32 c, int32 d, int32 z, int32 x) { if (tuneId == 153) return SCRIPT_CONT; if (loopFlag == LOOPED) _scriptVars[CURRENT_MUSIC] = tuneId; // so it gets restarted when saving & reloading else _scriptVars[CURRENT_MUSIC] = 0; _music->startMusic(tuneId, loopFlag); return SCRIPT_CONT; } int Logic::fnStopMusic(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { _scriptVars[CURRENT_MUSIC] = 0; _music->fadeDown(); return SCRIPT_CONT; } int Logic::fnInnerSpace(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { error("fnInnerSpace() not working"); return SCRIPT_STOP; // for compilers that don't support NORETURN } int Logic::fnSetScreen(Object *cpt, int32 id, int32 target, int32 screen, int32 c, int32 d, int32 z, int32 x) { _objMan->fetchObject(target)->o_screen = screen; return SCRIPT_CONT; } int Logic::fnPreload(Object *cpt, int32 id, int32 resId, int32 b, int32 c, int32 d, int32 z, int32 x) { _resMan->resOpen(resId); _resMan->resClose(resId); return SCRIPT_CONT; } int Logic::fnCheckCD(Object *cpt, int32 id, int32 screen, int32 b, int32 c, int32 d, int32 z, int32 x) { // only a dummy, here. // the check is done in the mainloop return SCRIPT_CONT; } int Logic::fnRestartGame(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { SwordEngine::_systemVars.forceRestart = true; cpt->o_logic = LOGIC_quit; return SCRIPT_STOP; } int Logic::fnQuitGame(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { if (SwordEngine::_systemVars.isDemo) { GUI::MessageDialog dialog(_("This is the end of the Broken Sword 1 Demo"), _("OK"), NULL); dialog.runModal(); Engine::quitGame(); } else error("fnQuitGame() called"); return fnQuit(cpt, id, 0, 0, 0, 0, 0, 0); } int Logic::fnDeathScreen(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { if (_scriptVars[FINALE_OPTION_FLAG] == 4) // successful end of game! SwordEngine::_systemVars.controlPanelMode = CP_THEEND; else SwordEngine::_systemVars.controlPanelMode = CP_DEATHSCREEN; cpt->o_logic = LOGIC_quit; return SCRIPT_STOP; } int Logic::fnSetParallax(Object *cpt, int32 id, int32 screen, int32 resId, int32 c, int32 d, int32 z, int32 x) { _screen->fnSetParallax(screen, resId); return SCRIPT_CONT; } int Logic::fnTdebug(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { debug(1, "Script TDebug id %d code %d, %d", id, a, b); return SCRIPT_CONT; } int Logic::fnRedFlash(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { _screen->fnFlash(FLASH_RED); return SCRIPT_CONT; } int Logic::fnBlueFlash(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { _screen->fnFlash(FLASH_BLUE); return SCRIPT_CONT; } int Logic::fnYellow(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { _screen->fnFlash(BORDER_YELLOW); return SCRIPT_CONT; } int Logic::fnGreen(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { _screen->fnFlash(BORDER_GREEN); return SCRIPT_CONT; } int Logic::fnPurple(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { _screen->fnFlash(BORDER_PURPLE); return SCRIPT_CONT; } int Logic::fnBlack(Object *cpt, int32 id, int32 a, int32 b, int32 c, int32 d, int32 z, int32 x) { _screen->fnFlash(BORDER_BLACK); return SCRIPT_CONT; } uint16 Logic::inRange(uint16 a, uint16 b, uint16 c) { return (a > b) ? a : (((b > c) ? c : b)); } void Logic::startPosCallFn(uint8 fnId, uint32 param1, uint32 param2, uint32 param3) { Object *obj = NULL; switch (fnId) { case opcPlaySequence: fnPlaySequence(NULL, 0, param1, 0, 0, 0, 0, 0); break; case opcAddObject: fnAddObject(NULL, 0, param1, 0, 0, 0, 0, 0); break; case opcRemoveObject: fnRemoveObject(NULL, 0, param1, 0, 0, 0, 0, 0); break; case opcMegaSet: obj = _objMan->fetchObject(param1); fnMegaSet(obj, param1, param2, param3, 0, 0, 0, 0); break; case opcNoSprite: obj = _objMan->fetchObject(param1); fnNoSprite(obj, param1, param2, param3, 0, 0, 0, 0); break; default: error("Illegal fnCallfn argument %d", fnId); } } void Logic::runStartScript(const uint8 *data) { // Here data is a static resource defined in staticres.cpp // It is always in little endian uint16 varId = 0; uint8 fnId = 0; uint32 param1 = 0; while (*data != opcSeqEnd) { switch (*data++) { case opcCallFn: fnId = *data++; param1 = *data++; startPosCallFn(fnId, param1, 0, 0); break; case opcCallFnLong: fnId = *data++; startPosCallFn(fnId, READ_LE_UINT32(data), READ_LE_UINT32(data + 4), READ_LE_UINT32(data + 8)); data += 12; break; case opcSetVar8: varId = READ_LE_UINT16(data); _scriptVars[varId] = data[2]; data += 3; break; case opcSetVar16: varId = READ_LE_UINT16(data); _scriptVars[varId] = READ_LE_UINT16(data + 2); data += 4; break; case opcSetVar32: varId = READ_LE_UINT16(data); _scriptVars[varId] = READ_LE_UINT32(data + 2); data += 6; break; case opcGeorge: _scriptVars[CHANGE_X] = READ_LE_UINT16(data + 0); _scriptVars[CHANGE_Y] = READ_LE_UINT16(data + 2); _scriptVars[CHANGE_DIR] = data[4]; _scriptVars[CHANGE_PLACE] = READ_LE_UINT24(data + 5); data += 8; break; case opcRunStart: data = _startData[*data]; break; case opcRunHelper: data = _helperData[*data]; break; default: error("Unexpected opcode in StartScript"); } } } void Logic::startPositions(uint32 pos) { bool spainVisit2 = false; if ((pos >= 956) && (pos <= 962)) { spainVisit2 = true; pos -= 900; } if ((pos > 80) || (_startData[pos] == NULL)) error("Starting in Section %d is not supported", pos); Logic::_scriptVars[CHANGE_STANCE] = STAND; Logic::_scriptVars[GEORGE_CDT_FLAG] = GEO_TLK_TABLE; runStartScript(_startData[pos]); if (spainVisit2) runStartScript(_helperData[HELP_SPAIN2]); if (pos == 0) pos = 1; Object *compact = _objMan->fetchObject(PLAYER); fnEnterSection(compact, PLAYER, pos, 0, 0, 0, 0, 0); // (automatically opens the compact resource for that section) SwordEngine::_systemVars.controlPanelMode = CP_NORMAL; SwordEngine::_systemVars.wantFade = true; } const uint32 Logic::_scriptVarInit[NON_ZERO_SCRIPT_VARS][2] = { { 42, 448}, { 43, 378}, { 51, 1}, { 92, 1}, { 147, 71}, { 201, 1}, { 209, 1}, { 215, 1}, { 242, 2}, { 244, 1}, { 246, 3}, { 247, 1}, { 253, 1}, { 297, 1}, { 398, 1}, { 508, 1}, { 605, 1}, { 606, 1}, { 701, 1}, { 709, 1}, { 773, 1}, { 843, 1}, { 907, 1}, { 923, 1}, { 966, 1}, { 988, 2}, {1058, 1}, {1059, 2}, {1060, 3}, {1061, 4}, {1062, 5}, {1063, 6}, {1064, 7}, {1065, 8}, {1066, 9}, {1067, 10}, {1068, 11}, {1069, 12}, {1070, 13}, {1071, 14}, {1072, 15}, {1073, 16}, {1074, 17}, {1075, 18}, {1076, 19}, {1077, 20}, {1078, 21}, {1079, 22}, {1080, 23}, {1081, 24}, {1082, 25}, {1083, 26}, {1084, 27}, {1085, 28}, {1086, 29}, {1087, 30}, {1088, 31}, {1089, 32}, {1090, 33}, {1091, 34}, {1092, 35}, {1093, 36}, {1094, 37}, {1095, 38}, {1096, 39}, {1097, 40}, {1098, 41}, {1099, 42}, {1100, 43}, {1101, 44}, {1102, 48}, {1103, 45}, {1104, 47}, {1105, 49}, {1106, 50}, {1107, 52}, {1108, 54}, {1109, 56}, {1110, 57}, {1111, 58}, {1112, 59}, {1113, 60}, {1114, 61}, {1115, 62}, {1116, 63}, {1117, 64}, {1118, 65}, {1119, 66}, {1120, 67}, {1121, 68}, {1122, 69}, {1123, 71}, {1124, 72}, {1125, 73}, {1126, 74} }; } // End of namespace Sword1