/* 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$ * */ // Scripting module script function component #include "saga/saga.h" #include "saga/gfx.h" #include "saga/actor.h" #include "saga/animation.h" #include "saga/console.h" #include "saga/events.h" #include "saga/font.h" #include "saga/interface.h" #include "saga/music.h" #include "saga/itedata.h" #include "saga/puzzle.h" #include "saga/render.h" #include "saga/sound.h" #include "saga/sndres.h" #include "saga/rscfile.h" #include "saga/script.h" #include "saga/objectmap.h" #include "saga/scene.h" #include "saga/isomap.h" #include "saga/sagaresnames.h" #include "common/config-manager.h" namespace Saga { #define OPCODE(x) {&Script::x, #x} void Script::setupScriptFuncList(void) { static const ScriptFunctionDescription ITEscriptFunctionsList[ITE_SCRIPT_FUNCTION_MAX] = { OPCODE(sfPutString), OPCODE(sfWait), OPCODE(sfTakeObject), OPCODE(sfIsCarried), OPCODE(sfStatusBar), OPCODE(sfMainMode), OPCODE(sfScriptWalkTo), OPCODE(sfScriptDoAction), OPCODE(sfSetActorFacing), OPCODE(sfStartBgdAnim), OPCODE(sfStopBgdAnim), OPCODE(sfLockUser), OPCODE(sfPreDialog), OPCODE(sfKillActorThreads), OPCODE(sfFaceTowards), OPCODE(sfSetFollower), OPCODE(sfScriptGotoScene), OPCODE(sfSetObjImage), OPCODE(sfSetObjName), OPCODE(sfGetObjImage), OPCODE(sfGetNumber), OPCODE(sfScriptOpenDoor), OPCODE(sfScriptCloseDoor), OPCODE(sfSetBgdAnimSpeed), OPCODE(SF_cycleColors), OPCODE(sfDoCenterActor), OPCODE(sfStartBgdAnimSpeed), OPCODE(sfScriptWalkToAsync), OPCODE(sfEnableZone), OPCODE(sfSetActorState), OPCODE(sfScriptMoveTo), OPCODE(sfSceneEq), OPCODE(sfDropObject), OPCODE(sfFinishBgdAnim), OPCODE(sfSwapActors), OPCODE(sfSimulSpeech), OPCODE(sfScriptWalk), OPCODE(sfCycleFrames), OPCODE(sfSetFrame), OPCODE(sfSetPortrait), OPCODE(sfSetProtagPortrait), OPCODE(sfChainBgdAnim), OPCODE(sfScriptSpecialWalk), OPCODE(sfPlaceActor), OPCODE(sfCheckUserInterrupt), OPCODE(sfScriptWalkRelative), OPCODE(sfScriptMoveRelative), OPCODE(sfSimulSpeech2), OPCODE(sfPlacard), OPCODE(sfPlacardOff), OPCODE(sfSetProtagState), OPCODE(sfResumeBgdAnim), OPCODE(sfThrowActor), OPCODE(sfWaitWalk), OPCODE(sfScriptSceneID), OPCODE(sfChangeActorScene), OPCODE(sfScriptClimb), OPCODE(sfSetDoorState), OPCODE(sfSetActorZ), OPCODE(sfScriptText), OPCODE(sfGetActorX), OPCODE(sfGetActorY), OPCODE(sfEraseDelta), OPCODE(sfPlayMusic), OPCODE(sfPickClimbOutPos), OPCODE(sfTossRif), OPCODE(sfShowControls), OPCODE(sfShowMap), OPCODE(sfPuzzleWon), OPCODE(sfEnableEscape), OPCODE(sfPlaySound), OPCODE(sfPlayLoopedSound), OPCODE(sfGetDeltaFrame), OPCODE(sfShowProtect), OPCODE(sfProtectResult), OPCODE(sfRand), OPCODE(sfFadeMusic), OPCODE(sfPlayVoice) }; static const ScriptFunctionDescription IHNMscriptFunctionsList[IHNM_SCRIPT_FUNCTION_MAX] = { OPCODE(sfNull), OPCODE(sfWait), OPCODE(sfTakeObject), OPCODE(sfIsCarried), OPCODE(sfStatusBar), OPCODE(sfMainMode), OPCODE(sfScriptWalkTo), OPCODE(sfScriptDoAction), OPCODE(sfSetActorFacing), OPCODE(sfStartBgdAnim), OPCODE(sfStopBgdAnim), OPCODE(sfLockUser), OPCODE(sfPreDialog), OPCODE(sfKillActorThreads), OPCODE(sfFaceTowards), OPCODE(sfSetFollower), OPCODE(sfScriptGotoScene), OPCODE(sfSetObjImage), OPCODE(sfSetObjName), OPCODE(sfGetObjImage), OPCODE(sfGetNumber), OPCODE(sfScriptOpenDoor), OPCODE(sfScriptCloseDoor), OPCODE(sfSetBgdAnimSpeed), OPCODE(SF_cycleColors), OPCODE(sfDoCenterActor), OPCODE(sfStartBgdAnimSpeed), OPCODE(sfScriptWalkToAsync), OPCODE(sfEnableZone), OPCODE(sfSetActorState), OPCODE(sfScriptMoveTo), OPCODE(sfSceneEq), OPCODE(sfDropObject), OPCODE(sfFinishBgdAnim), OPCODE(sfSwapActors), OPCODE(sfSimulSpeech), OPCODE(sfScriptWalk), OPCODE(sfCycleFrames), OPCODE(sfSetFrame), OPCODE(sfSetPortrait), OPCODE(sfSetProtagPortrait), OPCODE(sfChainBgdAnim), OPCODE(sfScriptSpecialWalk), OPCODE(sfPlaceActor), OPCODE(sfCheckUserInterrupt), OPCODE(sfScriptWalkRelative), OPCODE(sfScriptMoveRelative), OPCODE(sfSimulSpeech2), OPCODE(sfPsychicProfile), OPCODE(sfPsychicProfileOff), OPCODE(sfSetProtagState), OPCODE(sfResumeBgdAnim), OPCODE(sfThrowActor), OPCODE(sfWaitWalk), OPCODE(sfScriptSceneID), OPCODE(sfChangeActorScene), OPCODE(sfScriptClimb), OPCODE(sfSetDoorState), OPCODE(sfSetActorZ), OPCODE(sfScriptText), OPCODE(sfGetActorX), OPCODE(sfGetActorY), OPCODE(sfEraseDelta), OPCODE(sfPlayMusic), OPCODE(sfNull), OPCODE(sfEnableEscape), OPCODE(sfPlaySound), OPCODE(sfPlayLoopedSound), OPCODE(sfGetDeltaFrame), OPCODE(sfNull), OPCODE(sfNull), OPCODE(sfRand), OPCODE(sfFadeMusic), OPCODE(sfNull), OPCODE(sfSetChapterPoints), OPCODE(sfSetPortraitBgColor), OPCODE(sfScriptStartCutAway), OPCODE(sfReturnFromCutAway), OPCODE(sfEndCutAway), OPCODE(sfGetMouseClicks), OPCODE(sfResetMouseClicks), OPCODE(sfWaitFrames), OPCODE(sfScriptFade), OPCODE(sfScriptStartVideo), OPCODE(sfScriptReturnFromVideo), OPCODE(sfScriptEndVideo), OPCODE(sfSetActorZ), OPCODE(sf87), OPCODE(sf88), OPCODE(sf89), OPCODE(sfVstopFX), OPCODE(sfVstopLoopedFX), OPCODE(sf92), // only used in the demo version of IHNM OPCODE(sfDemoIsInteractive), OPCODE(sfVsetTrack), OPCODE(sfGetPoints), OPCODE(sfSetGlobalFlag), OPCODE(sfClearGlobalFlag), OPCODE(sfTestGlobalFlag), OPCODE(sfSetPoints), OPCODE(sfSetSpeechBox), OPCODE(sfDebugShowData), OPCODE(sfWaitFramesEsc), OPCODE(sfQueueMusic), OPCODE(sfDisableAbortSpeeches) }; if (_vm->getGameType() == GType_IHNM) _scriptFunctionsList = IHNMscriptFunctionsList; else _scriptFunctionsList = ITEscriptFunctionsList; } // Script function #0 (0x00) // Print a debugging message void Script::sfPutString(SCRIPTFUNC_PARAMS) { const char *str; str = thread->_strings->getString(thread->pop()); _vm->_console->DebugPrintf("sfPutString: %s\n",str); debug(0, "sfPutString: %s", str); } // Script function #1 (0x01) blocking // Param1: time in ticks void Script::sfWait(SCRIPTFUNC_PARAMS) { int16 time; time = thread->pop(); if (!_skipSpeeches) { thread->waitDelay(_vm->ticksToMSec(time)); // put thread to sleep } } // Script function #2 (0x02) void Script::sfTakeObject(SCRIPTFUNC_PARAMS) { uint16 objectId = thread->pop(); ObjectData *obj; obj = _vm->_actor->getObj(objectId); if (obj->_sceneNumber != ITE_SCENE_INV) { obj->_sceneNumber = ITE_SCENE_INV; // WORKAROUND for two incorrect object sprites in the IHNM demo // (the mirror and the icon in Ted's part). Set them correctly here if (_vm->getGameId() == GID_IHNM_DEMO) { if (obj->_spriteListResourceId == 4 && objectId == 16408) obj->_spriteListResourceId = 24; if (obj->_spriteListResourceId == 3 && objectId == 16409) obj->_spriteListResourceId = 25; } _vm->_interface->addToInventory(objectId); } } // Script function #3 (0x03) // Check if an object is carried. void Script::sfIsCarried(SCRIPTFUNC_PARAMS) { uint16 objectId = thread->pop(); CommonObjectData *object; if (_vm->_actor->validObjId(objectId)) { object = _vm->_actor->getObj(objectId); thread->_returnValue = (object->_sceneNumber == ITE_SCENE_INV) ? 1 : 0; } else { thread->_returnValue = 0; } } // Script function #4 (0x04) nonblocking // Set the command display to the specified text string // Param1: dialogue index of string void Script::sfStatusBar(SCRIPTFUNC_PARAMS) { int16 stringIndex = thread->pop(); _vm->_interface->setStatusText(thread->_strings->getString(stringIndex)); } // Script function #5 (0x05) void Script::sfMainMode(SCRIPTFUNC_PARAMS) { _vm->_actor->_centerActor = _vm->_actor->_protagonist; showVerb(); _vm->_interface->activate(); _vm->_interface->setMode(kPanelMain); if (_vm->getGameType() == GType_ITE) setPointerVerb(); } // Script function #6 (0x06) blocking // Param1: actor id // Param2: actor x // Param3: actor y void Script::sfScriptWalkTo(SCRIPTFUNC_PARAMS) { uint16 actorId; Location actorLocation; ActorData *actor; actorId = thread->pop(); actorLocation.x = thread->pop(); actorLocation.y = thread->pop(); actor = _vm->_actor->getActor(actorId); actorLocation.z = actor->_location.z; actor->_flags &= ~kFollower; if (_vm->_actor->actorWalkTo(actorId, actorLocation)) { thread->waitWalk(actor); } } // Script function #7 (0x07) // Param1: actor id // Param2: action // Param3: theObject // Param4: withObject void Script::sfScriptDoAction(SCRIPTFUNC_PARAMS) { uint16 objectId; uint16 action; uint16 theObject; uint16 withObject; int16 scriptEntryPointNumber; int16 moduleNumber; ActorData *actor; ObjectData *obj; const HitZone *hitZone; Event event; objectId = thread->pop(); action = thread->pop(); theObject = thread->pop(); withObject = thread->pop(); switch (objectTypeId(objectId)) { case kGameObjectObject: obj = _vm->_actor->getObj(objectId); scriptEntryPointNumber = obj->_scriptEntrypointNumber; if (scriptEntryPointNumber <= 0) { return; } moduleNumber = 0; break; case kGameObjectActor: actor = _vm->_actor->getActor(objectId); scriptEntryPointNumber = actor->_scriptEntrypointNumber; if (scriptEntryPointNumber <= 0) { return; } if (actor->_flags & (kProtagonist | kFollower)) { moduleNumber = 0; } else { moduleNumber = _vm->_scene->getScriptModuleNumber(); } break; case kGameObjectHitZone: case kGameObjectStepZone: if (objectTypeId(objectId) == kGameObjectHitZone) hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId)); else hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId)); if (hitZone == NULL) return; scriptEntryPointNumber = hitZone->getScriptNumber(); moduleNumber = _vm->_scene->getScriptModuleNumber(); break; default: error("Script::sfScriptDoAction wrong object type 0x%X", objectId); } event.type = kEvTOneshot; event.code = kScriptEvent; event.op = kEventExecNonBlocking; event.time = 0; event.param = moduleNumber; event.param2 = scriptEntryPointNumber; event.param3 = action; // Action event.param4 = theObject; // Object event.param5 = withObject; // With Object event.param6 = objectId; _vm->_events->queue(&event); } // Script function #8 (0x08) nonblocking // Param1: actor id // Param2: actor orientation void Script::sfSetActorFacing(SCRIPTFUNC_PARAMS) { int16 actorId; int actorDirection; ActorData *actor; actorId = thread->pop(); actorDirection = thread->pop(); actor = _vm->_actor->getActor(actorId); actor->_facingDirection = actor->_actionDirection = actorDirection; actor->_targetObject = ID_NOTHING; } // Script function #9 (0x09) void Script::sfStartBgdAnim(SCRIPTFUNC_PARAMS) { int16 animId = thread->pop(); int16 cycles = thread->pop(); _vm->_anim->setCycles(animId, cycles); _vm->_anim->setFrameTime(animId, _vm->ticksToMSec(kRepeatSpeedTicks)); _vm->_anim->play(animId, 0); debug(1, "sfStartBgdAnim(%d, %d)", animId, cycles); } // Script function #10 (0x0A) void Script::sfStopBgdAnim(SCRIPTFUNC_PARAMS) { int16 animId = thread->pop(); _vm->_anim->stop(animId); debug(1, "sfStopBgdAnim(%d)", animId); } // Script function #11 (0x0B) nonblocking // If the parameter is true, the user interface is disabled while script // continues to run. If the parameter is false, the user interface is // reenabled. // Param1: boolean void Script::sfLockUser(SCRIPTFUNC_PARAMS) { int16 lock; lock = thread->pop(); if (lock) { _vm->_interface->deactivate(); } else { _vm->_interface->activate(); } } // Script function #12 (0x0C) // Disables mouse input, etc. void Script::sfPreDialog(SCRIPTFUNC_PARAMS) { _vm->_interface->deactivate(); _vm->_interface->converseClear(); if (_vm->_interface->isInMainMode()) _vm->_interface->setMode(kPanelConverse); else _vm->_interface->converseDisplayText(); _vm->_interface->setMode(kPanelNull); } // Script function #13 (0x0D) void Script::sfKillActorThreads(SCRIPTFUNC_PARAMS) { ScriptThread *anotherThread; ScriptThreadList::iterator threadIterator; int16 actorId; actorId = thread->pop(); for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) { anotherThread = threadIterator.operator->(); if ((anotherThread != thread) && (anotherThread->_threadVars[kThreadVarActor] == actorId)) { anotherThread->_flags &= ~kTFlagWaiting; anotherThread->_flags |= kTFlagAborted; } } } // Script function #14 (0x0E) // Param1: actor id // Param2: object id void Script::sfFaceTowards(SCRIPTFUNC_PARAMS) { int16 actorId; int16 targetObject; ActorData *actor; actorId = thread->pop(); targetObject = thread->pop(); actor = _vm->_actor->getActor(actorId); actor->_targetObject = targetObject; } // Script function #15 (0x0F) // Param1: actor id // Param2: target object void Script::sfSetFollower(SCRIPTFUNC_PARAMS) { int16 actorId; int16 targetObject; ActorData *actor; actorId = thread->pop(); targetObject = thread->pop(); debug(1, "sfSetFollower(%d, %d) [%d]", actorId, targetObject, _vm->_actor->actorIdToIndex(actorId)); actor = _vm->_actor->getActor(actorId); actor->_targetObject = targetObject; if (targetObject != ID_NOTHING) { actor->_flags |= kFollower; actor->_actorFlags &= ~kActorNoFollow; } else { actor->_flags &= ~kFollower; } } // Script function #16 (0x10) void Script::sfScriptGotoScene(SCRIPTFUNC_PARAMS) { int16 sceneNumber; int16 entrance; int16 transition = 0; // IHNM sceneNumber = thread->pop(); entrance = thread->pop(); if (_vm->getGameType() == GType_IHNM) { transition = thread->pop(); _vm->_gfx->setCursor(kCursorBusy); } if ((_vm->getGameType() == GType_ITE && sceneNumber < 0) || (_vm->getGameType() == GType_IHNM && sceneNumber == 0)) { // TODO: set creditsFlag to true for IHNM _vm->shutDown(); return; } if (_vm->getGameType() == GType_IHNM) { // WORKAROUND for the briefly appearing actors at the beginning of each chapter // This will stop the actors being drawn in those specific scenes until the scene background has been drawn if ((_vm->_scene->currentChapterNumber() == 1 && _vm->_scene->currentSceneNumber() == 6) || (_vm->_scene->currentChapterNumber() == 2 && _vm->_scene->currentSceneNumber() == 31) || (_vm->_scene->currentChapterNumber() == 3 && _vm->_scene->currentSceneNumber() == 58) || (_vm->_scene->currentChapterNumber() == 4 && _vm->_scene->currentSceneNumber() == 68) || (_vm->_scene->currentChapterNumber() == 5 && _vm->_scene->currentSceneNumber() == 91) || (_vm->_scene->currentChapterNumber() == 7 && _vm->_scene->currentSceneNumber() == 145)) _vm->_actor->showActors(false); // Stop showing actors before the background is drawn // Since it doesn't look like the IHNM scripts remove the // cutaway after the intro, this is probably the best place to do it _vm->_anim->clearCutaway(); } // It is possible to leave scene when converse panel is on, // particulalrly it may happen at Moneychanger tent. This // prevent this from happening. if (_vm->_interface->getMode() == kPanelConverse) { _vm->_interface->setMode(kPanelMain); } // changeScene calls loadScene which calls setVerb. setVerb resets all pending objects and object flags if (sceneNumber == -1 && _vm->getGameType() == GType_IHNM) { // TODO: This is used to return back to the character selection screen in IHNM. // However, it seems more than this is needed, AM's speech is wrong and no actors // are shown _vm->_scene->changeScene(154, entrance, kTransitionFade, 8); } else { _vm->_scene->changeScene(sceneNumber, entrance, (sceneNumber == ITE_SCENE_ENDCREDIT1) ? kTransitionFade : kTransitionNoFade); } if (_vm->_interface->getMode() == kPanelPlacard || _vm->_interface->getMode() == kPanelCutaway || _vm->_interface->getMode() == kPanelVideo) { _vm->_gfx->showCursor(true); _vm->_interface->setMode(kPanelMain); } _pendingVerb = _vm->_script->getVerbType(kVerbNone); _currentObject[0] = _currentObject[1] = ID_NOTHING; showVerb(); // calls setStatusText("") if (_vm->getGameType() == GType_IHNM) _vm->_gfx->setCursor(kCursorNormal); } // Script function #17 (0x11) // Param1: object id // Param2: sprite index void Script::sfSetObjImage(SCRIPTFUNC_PARAMS) { uint16 objectId; uint16 spriteId; ObjectData *obj; objectId = thread->pop(); spriteId = thread->pop(); obj = _vm->_actor->getObj(objectId); obj->_spriteListResourceId = OBJ_SPRITE_BASE + spriteId; _vm->_interface->refreshInventory(); } // Script function #18 (0x12) // Param1: object id // Param2: name index void Script::sfSetObjName(SCRIPTFUNC_PARAMS) { uint16 objectId; uint16 nameIdx; ObjectData *obj; objectId = thread->pop(); nameIdx = thread->pop(); obj = _vm->_actor->getObj(objectId); obj->_nameIndex = nameIdx; } // Script function #19 (0x13) // Param1: object id void Script::sfGetObjImage(SCRIPTFUNC_PARAMS) { uint16 objectId; ObjectData *obj; objectId = thread->pop(); obj = _vm->_actor->getObj(objectId); if (_vm->getGameType() == GType_IHNM) thread->_returnValue = obj->_spriteListResourceId; else thread->_returnValue = obj->_spriteListResourceId - OBJ_SPRITE_BASE; } // Script function #20 (0x14) void Script::sfGetNumber(SCRIPTFUNC_PARAMS) { if (_vm->_interface->_statusTextInputState == kStatusTextInputFirstRun) { _vm->_interface->enterStatusString(); thread->wait(kWaitTypeStatusTextInput); disContinue = true; } else { if (_vm->_interface->_statusTextInputState == kStatusTextInputAborted) { thread->_returnValue = -1; } else { thread->_returnValue = atoi(_vm->_interface->_statusTextInputString); } _vm->_interface->_statusTextInputState = kStatusTextInputFirstRun; } } // Script function #21 (0x15) // Param1: door # void Script::sfScriptOpenDoor(SCRIPTFUNC_PARAMS) { int16 doorNumber; doorNumber = thread->pop(); if (_vm->_scene->getFlags() & kSceneFlagISO) { _vm->_isoMap->setTileDoorState(doorNumber, 1); } else { _vm->_scene->setDoorState(doorNumber, 0); } } // Script function #22 (0x16) // Param1: door # void Script::sfScriptCloseDoor(SCRIPTFUNC_PARAMS) { int16 doorNumber; doorNumber = thread->pop(); if (_vm->_scene->getFlags() & kSceneFlagISO) { _vm->_isoMap->setTileDoorState(doorNumber, 0); } else { _vm->_scene->setDoorState(doorNumber, 0xff); } } // Script function #23 (0x17) void Script::sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS) { int16 animId = thread->pop(); int16 speed = thread->pop(); _vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed)); debug(1, "sfSetBgdAnimSpeed(%d, %d)", animId, speed); } // Script function #24 (0x18) void Script::SF_cycleColors(SCRIPTFUNC_PARAMS) { SF_stub("SF_cycleColors", thread, nArgs); error("Please, report this to sev"); } // Script function #25 (0x19) // Param1: actor id void Script::sfDoCenterActor(SCRIPTFUNC_PARAMS) { int16 actorId; actorId = thread->pop(); _vm->_actor->_centerActor = _vm->_actor->getActor(actorId); } // Script function #26 (0x1A) nonblocking // Starts the specified animation void Script::sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS) { int16 animId = thread->pop(); int16 cycles = thread->pop(); int16 speed = thread->pop(); _vm->_anim->setCycles(animId, cycles); _vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed)); _vm->_anim->play(animId, 0); debug(1, "sfStartBgdAnimSpeed(%d, %d, %d)", animId, cycles, speed); } // Script function #27 (0x1B) nonblocking // Param1: actor id // Param2: actor x // Param3: actor y void Script::sfScriptWalkToAsync(SCRIPTFUNC_PARAMS) { int16 actorId; Location actorLocation; ActorData *actor; actorId = thread->pop(); actorLocation.x = thread->pop(); actorLocation.y = thread->pop(); actor = _vm->_actor->getActor(actorId); actorLocation.z = actor->_location.z; actor->_flags &= ~kFollower; _vm->_actor->actorWalkTo(actorId, actorLocation); } // Script function #28 (0x1C) void Script::sfEnableZone(SCRIPTFUNC_PARAMS) { uint16 objectId = thread->pop(); int16 flag = thread->pop(); HitZone *hitZone; if (objectTypeId(objectId) == 0) return; else if (objectTypeId(objectId) == kGameObjectHitZone) hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId)); else hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId)); if (hitZone == NULL) return; if (flag) { hitZone->setFlag(kHitZoneEnabled); } else { hitZone->clearFlag(kHitZoneEnabled); _vm->_actor->_protagonist->_lastZone = NULL; } } // Script function #29 (0x1D) // Param1: actor id // Param2: current action void Script::sfSetActorState(SCRIPTFUNC_PARAMS) { int16 actorId; int currentAction; ActorData *actor; actorId = thread->pop(); currentAction = thread->pop(); actor = _vm->_actor->getActor(actorId); if ((currentAction >= kActionWalkToPoint) && (currentAction <= kActionWalkToPoint)) { wakeUpActorThread(kWaitTypeWalk, actor); } actor->_currentAction = currentAction; actor->_actorFlags &= ~kActorBackwards; } // Script function #30 (0x1E) nonblocking // Param1: actor id // Param2: actor pos x // Param3: actor pos y void Script::sfScriptMoveTo(SCRIPTFUNC_PARAMS) { int16 objectId; Location location; ActorData *actor; ObjectData *obj; objectId = thread->pop(); location.x = thread->pop(); location.y = thread->pop(); if (_vm->_actor->validActorId(objectId)) { actor = _vm->_actor->getActor(objectId); actor->_location.x = location.x; actor->_location.y = location.y; } else { if (_vm->_actor->validObjId(objectId)) { obj = _vm->_actor->getObj(objectId); obj->_location.x = location.x; obj->_location.y = location.y; } } } // Script function #31 (0x21) // Param1: sceneNumber void Script::sfSceneEq(SCRIPTFUNC_PARAMS) { int16 sceneNumber = thread->pop(); if (_vm->_scene->getSceneResourceId(sceneNumber) == _vm->_scene->currentSceneResourceId()) thread->_returnValue = 1; else thread->_returnValue = 0; } // Script function #32 (0x20) void Script::sfDropObject(SCRIPTFUNC_PARAMS) { uint16 objectId; uint16 spriteId; int16 x; int16 y; ObjectData *obj; objectId = thread->pop(); spriteId = thread->pop(); x = thread->pop(); y = thread->pop(); obj = _vm->_actor->getObj(objectId); if (obj->_sceneNumber == ITE_SCENE_INV) { _vm->_interface->removeFromInventory(objectId); } obj->_sceneNumber = _vm->_scene->currentSceneNumber(); // WORKAROUND for the compact disk in Ellen's chapter // Change the scene number of the compact disk so that it's not shown. It will be shown // once Ellen says that there's something different (i.e. after speaking with AM) // See Actor::actorSpeech for the other part of this workaround if (_vm->getGameType() == GType_IHNM && _vm->_scene->currentChapterNumber() == 3 && _vm->_scene->currentSceneNumber() == 59 && obj->_id == 16385) obj->_sceneNumber = -1; if (_vm->getGameType() == GType_IHNM) { if (_vm->getGameId() != GID_IHNM_DEMO) { obj->_spriteListResourceId = spriteId; } else { // Don't update the object's _spriteListResourceId in the IHNM demo, as this function is // called incorrectly there (with spriteId == 0, which resets the object sprites) } } else { obj->_spriteListResourceId = OBJ_SPRITE_BASE + spriteId; } obj->_location.x = x; obj->_location.y = y; } // Script function #33 (0x21) void Script::sfFinishBgdAnim(SCRIPTFUNC_PARAMS) { int16 animId = thread->pop(); _vm->_anim->finish(animId); debug(1, "sfFinishBgdAnim(%d)", animId); } // Script function #34 (0x22) // Param1: actor id 1 // Param2: actor id 2 void Script::sfSwapActors(SCRIPTFUNC_PARAMS) { int16 actorId1; int16 actorId2; ActorData *actor1; ActorData *actor2; actorId1 = thread->pop(); actorId2 = thread->pop(); actor1 = _vm->_actor->getActor(actorId1); actor2 = _vm->_actor->getActor(actorId2); SWAP(actor1->_location, actor2->_location); if (actor1->_flags & kProtagonist) { actor1->_flags &= ~kProtagonist; actor2->_flags |= kProtagonist; _vm->_actor->_protagonist = _vm->_actor->_centerActor = actor2; if (_vm->getGameType() == GType_IHNM) _vm->_scene->setProtag(actorId2); } else if (actor2->_flags & kProtagonist) { actor2->_flags &= ~kProtagonist; actor1->_flags |= kProtagonist; _vm->_actor->_protagonist = _vm->_actor->_centerActor = actor1; if (_vm->getGameType() == GType_IHNM) _vm->_scene->setProtag(actorId1); } } // Script function #35 (0x23) // Param1: string rid // Param2: actorscount // Param3: actor id1 ///.... // Param3: actor idN void Script::sfSimulSpeech(SCRIPTFUNC_PARAMS) { int16 stringId; int16 actorsCount; int i; uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX]; const char *string; int16 sampleResourceId = -1; stringId = thread->pop(); actorsCount = thread->pop(); if (actorsCount > ACTOR_SPEECH_ACTORS_MAX) error("sfSimulSpeech actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount); for (i = 0; i < actorsCount; i++) actorsIds[i] = thread->pop(); string = thread->_strings->getString(stringId); if (thread->_voiceLUT->voices) { if (_vm->getGameType() == GType_IHNM && stringId >= 338) { sampleResourceId = -1; } else { sampleResourceId = thread->_voiceLUT->voices[stringId]; if (sampleResourceId <= 0 || sampleResourceId > 4000) sampleResourceId = -1; } } _vm->_actor->simulSpeech(string, actorsIds, actorsCount, 0, sampleResourceId); thread->wait(kWaitTypeSpeech); } // Script function #36 (0x24) ? // Param1: actor id // Param2: actor x // Param3: actor y // Param4: actor walk flag void Script::sfScriptWalk(SCRIPTFUNC_PARAMS) { int16 actorId; Location actorLocation; ActorData *actor; uint16 walkFlags; actorId = thread->pop(); actorLocation.x = thread->pop(); actorLocation.y = thread->pop(); walkFlags = thread->pop(); actor = _vm->_actor->getActor(actorId); actorLocation.z = actor->_location.z; _vm->_actor->realLocation(actorLocation, ID_NOTHING, walkFlags); actor->_flags &= ~kFollower; if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) { thread->waitWalk(actor); } if (walkFlags & kWalkBackPedal) { actor->_actorFlags |= kActorBackwards; } actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask); } // Script function #37 (0x25) nonblocking // Param1: actor id // Param2: flags telling how to cycle the frames // Param3: cycle frame number // Param4: cycle delay void Script::sfCycleFrames(SCRIPTFUNC_PARAMS) { int16 actorId; int16 flags; int cycleFrameSequence; int cycleDelay; ActorData *actor; actorId = thread->pop(); flags = thread->pop(); cycleFrameSequence = thread->pop(); cycleDelay = thread->pop(); actor = _vm->_actor->getActor(actorId); if (flags & kCyclePong) { actor->_currentAction = kActionPongFrames; } else { actor->_currentAction = kActionCycleFrames; } actor->_actorFlags &= ~(kActorContinuous | kActorRandom | kActorBackwards); if (!(flags & kCycleOnce)) { actor->_actorFlags |= kActorContinuous; } if (flags & kCycleRandom) { actor->_actorFlags |= kActorRandom; } if (flags & kCycleReverse) { if (_vm->getGameType() == GType_IHNM && _vm->_scene->currentChapterNumber() == 2 && _vm->_scene->currentSceneNumber() == 41) { // Prevent Benny from walking backwards after talking to the child via the monitor. This occurs in the // original as well, and is fixed by not setting the kActorBackwards flag at this point } else { actor->_actorFlags |= kActorBackwards; } } actor->_cycleFrameSequence = cycleFrameSequence; actor->_cycleTimeCount = 0; actor->_cycleDelay = cycleDelay; actor->_actionCycle = 0; } // Script function #38 (0x26) nonblocking // Param1: actor id // Param2: frame type // Param3: frame offset void Script::sfSetFrame(SCRIPTFUNC_PARAMS) { int16 actorId; int frameType; int frameOffset; ActorData *actor; ActorFrameRange *frameRange; actorId = thread->pop(); frameType = thread->pop(); frameOffset = thread->pop(); actor = _vm->_actor->getActor(actorId); frameRange = _vm->_actor->getActorFrameRange(actorId, frameType); actor->_frameNumber = frameRange->frameIndex + frameOffset; if (actor->_currentAction != kActionFall) { actor->_currentAction = kActionFreeze; } } // Script function #39 (0x27) // Sets the right-hand portrait void Script::sfSetPortrait(SCRIPTFUNC_PARAMS) { int16 param = thread->pop(); _vm->_interface->setRightPortrait(param); } // Script function #40 (0x28) // Sets the left-hand portrait void Script::sfSetProtagPortrait(SCRIPTFUNC_PARAMS) { int16 param = thread->pop(); _vm->_interface->setLeftPortrait(param); } // Script function #41 (0x29) nonblocking // Links the specified animations for playback // Param1: ? // Param2: total linked frame count // Param3: animation id link target // Param4: animation id link source void Script::sfChainBgdAnim(SCRIPTFUNC_PARAMS) { int16 animId1 = thread->pop(); int16 animId = thread->pop(); int16 cycles = thread->pop(); int16 speed = thread->pop(); if (speed >= 0) { _vm->_anim->setCycles(animId, cycles); _vm->_anim->stop(animId); _vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed)); } _vm->_anim->link(animId1, animId); debug(1, "sfChainBgdAnim(%d, %d, %d, %d)", animId1, animId, cycles, speed); } // Script function #42 (0x2A) // Param1: actor id // Param2: actor x // Param3: actor y // Param4: frame seq void Script::sfScriptSpecialWalk(SCRIPTFUNC_PARAMS) { int16 actorId; int16 walkFrameSequence; Location actorLocation; ActorData *actor; actorId = thread->pop(); actorLocation.x = thread->pop(); actorLocation.y = thread->pop(); walkFrameSequence = thread->pop(); actor = _vm->_actor->getActor(actorId); actorLocation.z = actor->_location.z; _vm->_actor->actorWalkTo(actorId, actorLocation); actor->_walkFrameSequence = walkFrameSequence; } // Script function #43 (0x2B) nonblocking // Param1: actor id // Param2: actor x // Param3: actor y // Param4: actor direction // Param5: actor action // Param6: actor frame number void Script::sfPlaceActor(SCRIPTFUNC_PARAMS) { int16 actorId; Location actorLocation; int actorDirection; int frameType; int frameOffset; ActorData *actor; ActorFrameRange *frameRange; actorId = thread->pop(); actorLocation.x = thread->pop(); actorLocation.y = thread->pop(); actorDirection = thread->pop(); frameType = thread->pop(); frameOffset = thread->pop(); debug(1, "sfPlaceActor(id = 0x%x, x=%d, y=%d, dir=%d, frameType=%d, frameOffset=%d)", actorId, actorLocation.x, actorLocation.y, actorDirection, frameType, frameOffset); actor = _vm->_actor->getActor(actorId); actor->_location.x = actorLocation.x; actor->_location.y = actorLocation.y; actor->_facingDirection = actor->_actionDirection = actorDirection; if (!actor->_frames) _vm->_actor->loadActorResources(actor); //? is not it already loaded ? if (frameType >= 0) { frameRange = _vm->_actor->getActorFrameRange(actorId, frameType); if (frameRange->frameCount <= frameOffset) { warning("Wrong frameOffset 0x%X", frameOffset); } actor->_frameNumber = frameRange->frameIndex + frameOffset; actor->_currentAction = kActionFreeze; } else { actor->_currentAction = kActionWait; } actor->_targetObject = ID_NOTHING; } // Script function #44 (0x2C) nonblocking // Checks to see if the user has interrupted a currently playing // game cinematic. Pushes a zero or positive value if the game // has not been interrupted. void Script::sfCheckUserInterrupt(SCRIPTFUNC_PARAMS) { thread->_returnValue = (_skipSpeeches == true); } // Script function #45 (0x2D) // Param1: actor id // Param2: object id // Param3: actor x // Param4: actor y // Param5: actor walk flag void Script::sfScriptWalkRelative(SCRIPTFUNC_PARAMS) { int16 actorId; int16 objectId; uint16 walkFlags; Location actorLocation; ActorData *actor; actorId = thread->pop(); objectId = thread->pop(); actorLocation.x = thread->pop(); actorLocation.y = thread->pop(); walkFlags = thread->pop(); actor = _vm->_actor->getActor(actorId); actorLocation.z = actor->_location.z; _vm->_actor->realLocation(actorLocation, objectId, walkFlags); actor->_flags &= ~kFollower; if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) { thread->waitWalk(actor); } if (walkFlags & kWalkBackPedal) { actor->_actorFlags |= kActorBackwards; } actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask); } // Script function #46 (0x2E) // Param1: actor id // Param2: object id // Param3: actor x // Param4: actor y // Param5: actor walk flag void Script::sfScriptMoveRelative(SCRIPTFUNC_PARAMS) { int16 actorId; int16 objectId; uint16 walkFlags; Location actorLocation; ActorData *actor; actorId = thread->pop(); objectId = thread->pop(); actorLocation.x = thread->pop(); actorLocation.y = thread->pop(); walkFlags = thread->pop(); actor = _vm->_actor->getActor(actorId); actorLocation.z = actor->_location.z; _vm->_actor->realLocation(actorLocation, objectId, walkFlags); actor->_location = actorLocation; actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask); } // Script function #47 (0x2F) void Script::sfSimulSpeech2(SCRIPTFUNC_PARAMS) { int16 stringId; int16 actorsCount; int16 speechFlags; int i; uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX]; const char *string; int16 sampleResourceId = -1; stringId = thread->pop(); actorsCount = thread->pop(); speechFlags = thread->pop(); if (actorsCount > ACTOR_SPEECH_ACTORS_MAX) error("sfSimulSpeech2 actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount); for (i = 0; i < actorsCount; i++) actorsIds[i] = thread->pop(); string = thread->_strings->getString(stringId); if (thread->_voiceLUT->voices) { sampleResourceId = thread->_voiceLUT->voices[stringId]; if (sampleResourceId <= 0 || sampleResourceId > 4000) sampleResourceId = -1; } _vm->_actor->simulSpeech(string, actorsIds, actorsCount, speechFlags, sampleResourceId); thread->wait(kWaitTypeSpeech); } // Script function #48 (0x30) // Param1: string rid void Script::sfPlacard(SCRIPTFUNC_PARAMS) { int stringId; Surface *backBuffer = _vm->_gfx->getBackBuffer(); static PalEntry cur_pal[PAL_ENTRIES]; PalEntry *pal; Event event; Event *q_event; thread->wait(kWaitTypePlacard); _vm->_interface->rememberMode(); _vm->_interface->setMode(kPanelPlacard); stringId = thread->pop(); event.type = kEvTOneshot; event.code = kCursorEvent; event.op = kEventHide; q_event = _vm->_events->queue(&event); _vm->_gfx->getCurrentPal(cur_pal); event.type = kEvTImmediate; event.code = kPalEvent; event.op = kEventPalToBlack; event.time = 0; event.duration = kNormalFadeDuration; event.data = cur_pal; q_event = _vm->_events->chain(q_event, &event); event.type = kEvTOneshot; event.code = kInterfaceEvent; event.op = kEventClearStatus; q_event = _vm->_events->chain(q_event, &event); event.type = kEvTOneshot; event.code = kGraphicsEvent; event.op = kEventSetFlag; event.param = RF_PLACARD; q_event = _vm->_events->chain(q_event, &event); event.type = kEvTOneshot; event.code = kGraphicsEvent; event.op = kEventFillRect; event.data = backBuffer; event.param = 138; event.param2 = 0; event.param3 = _vm->_scene->getHeight(); event.param4 = 0; event.param5 = _vm->getDisplayWidth(); q_event = _vm->_events->chain(q_event, &event); // Put the text in the center of the viewport, assuming it will fit on // one line. If we cannot make that assumption we'll need to extend // the text drawing function so that it can center text around a point. // It doesn't end up in exactly the same spot as the original did it, // but it's close enough for now at least. TextListEntry textEntry; textEntry.knownColor = kKnownColorBrightWhite; textEntry.effectKnownColor = kKnownColorBlack; textEntry.point.x = _vm->getDisplayWidth() / 2; textEntry.point.y = (_vm->_scene->getHeight() - _vm->_font->getHeight(kKnownFontMedium)) / 2; textEntry.font = kKnownFontMedium; textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered); textEntry.text = thread->_strings->getString(stringId); _placardTextEntry = _vm->_scene->_textList.addEntry(textEntry); event.type = kEvTOneshot; event.code = kTextEvent; event.op = kEventDisplay; event.data = _placardTextEntry; q_event = _vm->_events->chain(q_event, &event); _vm->_scene->getBGPal(pal); event.type = kEvTImmediate; event.code = kPalEvent; event.op = kEventBlackToPal; event.time = 0; event.duration = kNormalFadeDuration; event.data = pal; q_event = _vm->_events->chain(q_event, &event); event.type = kEvTOneshot; event.code = kScriptEvent; event.op = kEventThreadWake; event.param = kWaitTypePlacard; q_event = _vm->_events->chain(q_event, &event); } // Script function #49 (0x31) void Script::sfPlacardOff(SCRIPTFUNC_PARAMS) { thread->wait(kWaitTypePlacard); _vm->_scene->clearPlacard(); } void Script::sfPsychicProfile(SCRIPTFUNC_PARAMS) { int stringId; static PalEntry cur_pal[PAL_ENTRIES]; PalEntry *pal; Event event; Event *q_event; // FIXME: This still needs work: the text placement is incorrect thread->wait(kWaitTypePlacard); _vm->_interface->rememberMode(); _vm->_interface->setMode(kPanelPlacard); _vm->_gfx->savePalette(); stringId = thread->pop(); event.type = kEvTOneshot; event.code = kCursorEvent; event.op = kEventHide; q_event = _vm->_events->queue(&event); _vm->_gfx->getCurrentPal(cur_pal); event.type = kEvTImmediate; event.code = kPalEvent; event.op = kEventPalToBlack; event.time = 0; event.duration = kNormalFadeDuration; event.data = cur_pal; q_event = _vm->_events->chain(q_event, &event); event.type = kEvTOneshot; event.code = kInterfaceEvent; event.op = kEventClearStatus; q_event = _vm->_events->chain(q_event, &event); event.type = kEvTOneshot; event.code = kGraphicsEvent; event.op = kEventSetFlag; event.param = RF_PLACARD; q_event = _vm->_events->chain(q_event, &event); // Set the background and palette for the psychic profile event.type = kEvTOneshot; event.code = kPsychicProfileBgEvent; q_event = _vm->_events->chain(q_event, &event); // Put the text in the center of the viewport, assuming it will fit on // one line. If we cannot make that assumption we'll need to extend // the text drawing function so that it can center text around a point. // It doesn't end up in exactly the same spot as the original did it, // but it's close enough for now at least. // FIXME: This assumption is wrong for the psychic profile, the text // placement is incorrect TextListEntry textEntry; textEntry.knownColor = kKnownColorBlack; textEntry.point.x = _vm->getDisplayWidth() / 2; textEntry.point.y = (_vm->_scene->getHeight() - _vm->_font->getHeight(kKnownFontMedium)) / 2; textEntry.font = kKnownFontVerb; textEntry.flags = (FontEffectFlags)(kFontCentered); textEntry.text = thread->_strings->getString(stringId); _placardTextEntry = _vm->_scene->_textList.addEntry(textEntry); event.type = kEvTOneshot; event.code = kTextEvent; event.op = kEventDisplay; event.data = _placardTextEntry; q_event = _vm->_events->chain(q_event, &event); _vm->_scene->getBGPal(pal); event.type = kEvTImmediate; event.code = kPalEvent; event.op = kEventBlackToPal; event.time = 0; event.duration = kNormalFadeDuration; event.data = pal; q_event = _vm->_events->chain(q_event, &event); event.type = kEvTOneshot; event.code = kScriptEvent; event.op = kEventThreadWake; event.param = kWaitTypePlacard; q_event = _vm->_events->chain(q_event, &event); } void Script::sfPsychicProfileOff(SCRIPTFUNC_PARAMS) { thread->wait(kWaitTypePlacard); // This is called a while after the psychic profile is // opened, to close it automatically _vm->_scene->clearPsychicProfile(); } // Script function #50 (0x32) void Script::sfSetProtagState(SCRIPTFUNC_PARAMS) { int protagState = thread->pop(); _vm->_actor->setProtagState(protagState); } // Script function #51 (0x33) void Script::sfResumeBgdAnim(SCRIPTFUNC_PARAMS) { int16 animId = thread->pop(); int16 cycles = thread->pop(); _vm->_anim->resume(animId, cycles); debug(1, "sfResumeBgdAnimSpeed(%d, %d)", animId, cycles); } // Script function #52 (0x34) // Param1: actor id // Param2: x // Param3: y // Param4: unknown // Param5: actionCycle // Param6: flags void Script::sfThrowActor(SCRIPTFUNC_PARAMS) { int16 actorId; ActorData *actor; int16 flags; int32 actionCycle; Location location; actorId = thread->pop(); location.x = thread->pop(); location.y = thread->pop(); thread->pop(); actionCycle = thread->pop(); flags = thread->pop(); actor = _vm->_actor->getActor(actorId); location.z = actor->_location.z; actor->_currentAction = kActionFall; actor->_actionCycle = actionCycle; actor->_fallAcceleration = -20; actor->_fallVelocity = - (actor->_fallAcceleration * actor->_actionCycle) / 2; actor->_fallPosition = actor->_location.z << 4; actor->_finalTarget = location; actor->_actionCycle--; if (!(flags & kWalkAsync)) { thread->waitWalk(actor); } } // Script function #53 (0x35) // Param1: actor id // Param2: target object void Script::sfWaitWalk(SCRIPTFUNC_PARAMS) { int16 actorId; ActorData *actor; actorId = thread->pop(); actor = _vm->_actor->getActor(actorId); if ((actor->_currentAction == kActionWalkToPoint) || (actor->_currentAction == kActionWalkToLink) || (actor->_currentAction == kActionFall)) { thread->waitWalk(actor); } } // Script function #54 (0x36) void Script::sfScriptSceneID(SCRIPTFUNC_PARAMS) { thread->_returnValue = _vm->_scene->currentSceneNumber(); } // Script function #55 (0x37) // Param1: actor id // Param2: scene number void Script::sfChangeActorScene(SCRIPTFUNC_PARAMS) { int16 actorId; int32 sceneNumber; ActorData *actor; actorId = thread->pop(); sceneNumber = thread->pop(); actor = _vm->_actor->getActor(actorId); actor->_sceneNumber = sceneNumber; } // Script function #56 (0x38) // Param1: actor id // Param2: z // Param3: frame seq // Param4: flags void Script::sfScriptClimb(SCRIPTFUNC_PARAMS) { int16 actorId; int16 z; ActorData *actor; uint16 flags; int cycleFrameSequence; actorId = thread->pop(); z = thread->pop(); cycleFrameSequence = thread->pop(); flags = thread->pop(); actor = _vm->_actor->getActor(actorId); actor->_finalTarget.z = z; actor->_flags &= ~kFollower; actor->_actionCycle = 1; actor->_cycleFrameSequence = cycleFrameSequence; actor->_currentAction = kActionClimb; if (!(flags & kWalkAsync)) { thread->waitWalk(actor); } } // Script function #57 (0x39) // Param1: door # // Param2: door state void Script::sfSetDoorState(SCRIPTFUNC_PARAMS) { int16 doorNumber; int16 doorState; doorNumber = thread->pop(); doorState = thread->pop(); if (_vm->_scene->getFlags() & kSceneFlagISO) { _vm->_isoMap->setTileDoorState(doorNumber, doorState); } else { _vm->_scene->setDoorState(doorNumber, doorState); } } // Script function #58 (0x3A) // Param1: actor id // Param2: z void Script::sfSetActorZ(SCRIPTFUNC_PARAMS) { int16 objectId; ActorData *actor; ObjectData *obj; int16 z; objectId = thread->pop(); z = thread->pop(); if (_vm->_actor->validActorId(objectId)) { actor = _vm->_actor->getActor(objectId); actor->_location.z = z; } else { if (_vm->_actor->validObjId(objectId)) { obj = _vm->_actor->getObj(objectId); obj->_location.z = z; } } } // Script function #59 (0x3B) // Param1: stringId // Param2: flags // Param3: color // Param4: x // Param5: y void Script::sfScriptText(SCRIPTFUNC_PARAMS) { int16 stringId; int16 flags; Rect rect; int color; Point point; int width; const char*text; stringId = thread->pop(); flags = thread->pop(); color = thread->pop(); point.x = thread->pop(); point.y = thread->pop(); text = thread->_strings->getString(stringId); width = _vm->_font->getStringWidth(kKnownFontScript, text, 0, kFontOutline); rect.top = point.y - 6; rect.setHeight(12); rect.left = point.x - width / 2; rect.setWidth(width); _vm->_actor->setSpeechColor(color, _vm->KnownColor2ColorId(kKnownColorBlack)); _vm->_actor->nonActorSpeech(rect, &text, 1, -1, flags); } // Script function #60 (0x3C) // Param1: actor id void Script::sfGetActorX(SCRIPTFUNC_PARAMS) { int16 actorId; ActorData *actor; actorId = thread->pop(); actor = _vm->_actor->getActor(actorId); thread->_returnValue = actor->_location.x >> 2; } // Script function #61 (0x3D) // Param1: actor id void Script::sfGetActorY(SCRIPTFUNC_PARAMS) { int16 actorId; ActorData *actor; actorId = thread->pop(); actor = _vm->_actor->getActor(actorId); thread->_returnValue = actor->_location.y >> 2; } // Script function #62 (0x3E) void Script::sfEraseDelta(SCRIPTFUNC_PARAMS) { Surface *backGroundSurface; BGInfo backGroundInfo; backGroundSurface = _vm->_render->getBackGroundSurface(); _vm->_scene->getBGInfo(backGroundInfo); backGroundSurface->blit(backGroundInfo.bounds, backGroundInfo.buffer); } // Script function #63 (0x3F) void Script::sfPlayMusic(SCRIPTFUNC_PARAMS) { if (_vm->getGameType() == GType_ITE) { int16 param = thread->pop() + 9; if (param >= 9 && param <= 34) { _vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1); _vm->_music->play(param); } else { _vm->_music->stop(); } } else { int16 param1 = thread->pop(); int16 param2 = thread->pop(); if (param1 < 0) { _vm->_music->stop(); return; } if (param1 >= _vm->_music->_songTableLen) { warning("sfPlayMusic: Wrong song number (%d > %d)", param1, _vm->_music->_songTableLen - 1); } else { _vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1); _vm->_music->play(_vm->_music->_songTable[param1], param2 ? MUSIC_LOOP : MUSIC_NORMAL); if (!_vm->_scene->haveChapterPointsChanged()) { _vm->_scene->setCurrentMusicTrack(param1); _vm->_scene->setCurrentMusicRepeat(param2); } else { // Don't save this music track when saving in IHNM _vm->_scene->setChapterPointsChanged(false); } } } } // Script function #64 (0x40) void Script::sfPickClimbOutPos(SCRIPTFUNC_PARAMS) { int16 u, v, t; ActorData *protagonist = _vm->_actor->_protagonist; while (true) { u = (_vm->_rnd.getRandomNumber(63) & 63) + 40; v = (_vm->_rnd.getRandomNumber(63) & 63) + 40; t = _vm->_isoMap->getTileIndex(u, v, 6); if (t == 65) { protagonist->_location.u() = (u << 4) + 4; protagonist->_location.v() = (v << 4) + 4; protagonist->_location.z = 48; break; } } } // Script function #65 (0x41) void Script::sfTossRif(SCRIPTFUNC_PARAMS) { int16 uc , vc; uint16 direction; ActorData *protagonist = _vm->_actor->_protagonist; uc = protagonist->_location.u() >> 4; vc = protagonist->_location.v() >> 4; if (_vm->_isoMap->findNearestChasm(uc, vc, direction)) { uc <<= 4; vc <<= 4; protagonist->_facingDirection = direction; protagonist->_finalTarget.u() = uc; protagonist->_finalTarget.v() = vc; protagonist->_finalTarget.z = -40; protagonist->_currentAction = kActionFall; protagonist->_actionCycle = 24; protagonist->_fallAcceleration = - 20; protagonist->_fallVelocity = - (protagonist->_fallAcceleration * 16) / 2 - (44 / 12); protagonist->_fallPosition = protagonist->_location.z << 4; protagonist->_actionCycle--; } } // Script function #66 (0x42) void Script::sfShowControls(SCRIPTFUNC_PARAMS) { // It has zero implementation in Win rerelase, and in DOS // release it deals with video ports. } // Script function #67 (0x43) void Script::sfShowMap(SCRIPTFUNC_PARAMS) { _vm->_interface->setMode(kPanelMap); } // Script function #68 (0x44) void Script::sfPuzzleWon(SCRIPTFUNC_PARAMS) { thread->_returnValue = _vm->_puzzle->isSolved(); } // Script function #69 (0x45) void Script::sfEnableEscape(SCRIPTFUNC_PARAMS) { if (thread->pop()) _abortEnabled = true; else { _skipSpeeches = false; _abortEnabled = false; } } // Script function #70 (0x46) void Script::sfPlaySound(SCRIPTFUNC_PARAMS) { int16 param = thread->pop(); int res; if (param >= 0 && param < _vm->_sndRes->_fxTableLen) { res = _vm->_sndRes->_fxTable[param].res; if (_vm->getFeatures() & GF_CD_FX) res -= 14; _vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, false); } else { _vm->_sound->stopSound(); } } // Script function #71 (0x47) void Script::sfPlayLoopedSound(SCRIPTFUNC_PARAMS) { int16 param = thread->pop(); int res; if (param >= 0 && param < _vm->_sndRes->_fxTableLen) { res = _vm->_sndRes->_fxTable[param].res; if (_vm->getFeatures() & GF_CD_FX) res -= 14; _vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, true); } else { _vm->_sound->stopSound(); } } // Script function #72 (0x48) void Script::sfGetDeltaFrame(SCRIPTFUNC_PARAMS) { uint16 animId = (uint16)thread->pop(); thread->_returnValue = _vm->_anim->getCurrentFrame(animId); } // Script function #73 (0x49) void Script::sfShowProtect(SCRIPTFUNC_PARAMS) { if (_vm->_copyProtection) { thread->wait(kWaitTypeRequest); _vm->_interface->setMode(kPanelProtect); } } // Script function #74 (0x4A) void Script::sfProtectResult(SCRIPTFUNC_PARAMS) { if (_vm->_copyProtection) { thread->_returnValue = _vm->_interface->getProtectHash(); } else { int protectHash; //cheating protectHash = thread->pop(); thread->push(protectHash); thread->_returnValue = protectHash; } } // Script function #75 (0x4b) void Script::sfRand(SCRIPTFUNC_PARAMS) { int16 param; param = thread->pop(); thread->_returnValue = _vm->_rnd.getRandomNumber(param - 1); } // Script function #76 (0x4c) void Script::sfFadeMusic(SCRIPTFUNC_PARAMS) { _vm->_music->setVolume(0, 1000); } // Script function #77 (0x4d) void Script::sfPlayVoice(SCRIPTFUNC_PARAMS) { int16 param = thread->pop(); warning("sfPlayVoice(%d)", param); if (param > 0) { _vm->_sndRes->playVoice(param + 3712); } else { _vm->_sound->stopSound(); } } void Script::finishDialog(int strID, int replyID, int flags, int bitOffset) { byte *addr; const char *str; if (_conversingThread) { _vm->_interface->setMode(kPanelNull); if (_vm->getGameType() == GType_IHNM) { str = _conversingThread->_strings->getString(strID); if (*str != '[') { int sampleResourceId = -1; sampleResourceId = _conversingThread->_voiceLUT->voices[strID]; if (sampleResourceId < 0 || sampleResourceId > 4000) sampleResourceId = -1; _vm->_actor->actorSpeech(_vm->_actor->_protagonist->_id, &str, 1, sampleResourceId, 0); } } _conversingThread->_flags &= ~kTFlagWaiting; _conversingThread->push(replyID); if (flags & kReplyOnce) { addr = _conversingThread->_staticBase + (bitOffset >> 3); *addr |= (1 << (bitOffset & 7)); } } _conversingThread = NULL; wakeUpThreads(kWaitTypeDialogBegin); } void Script::sfSetChapterPoints(SCRIPTFUNC_PARAMS) { int16 ethics = thread->pop(); int16 barometer = thread->pop(); int chapter = _vm->_scene->currentChapterNumber(); _vm->_ethicsPoints[chapter] = ethics; _vm->_spiritualBarometer = ethics * 256 / barometer; _vm->_scene->setChapterPointsChanged(true); // don't save this music when saving in IHNM } void Script::sfSetPortraitBgColor(SCRIPTFUNC_PARAMS) { int16 red = thread->pop(); int16 green = thread->pop(); int16 blue = thread->pop(); _vm->_interface->setPortraitBgColor(red, green, blue); } void Script::sfScriptStartCutAway(SCRIPTFUNC_PARAMS) { int16 cut; int16 fade; cut = thread->pop(); thread->pop(); // Not used fade = thread->pop(); _vm->_anim->setCutAwayMode(kPanelCutaway); _vm->_anim->playCutaway(cut, fade != 0); } void Script::sfReturnFromCutAway(SCRIPTFUNC_PARAMS) { _vm->_anim->returnFromCutaway(); thread->wait(kWaitTypeWakeUp); } void Script::sfEndCutAway(SCRIPTFUNC_PARAMS) { _vm->_anim->endCutaway(); } void Script::sfGetMouseClicks(SCRIPTFUNC_PARAMS) { thread->_returnValue = _vm->getMouseClickCount(); } void Script::sfResetMouseClicks(SCRIPTFUNC_PARAMS) { _vm->resetMouseClickCount(); } // Used in IHNM only // Param1: frames void Script::sfWaitFrames(SCRIPTFUNC_PARAMS) { int16 frames; frames = thread->pop(); // HACK for the nightfall scene in Benny's chapter // sfWaitFrames is supposed to wait for fadein and fadeout during that cutaway, but we // don't support it yet (function sfScriptFade). This is a temporary hack to avoid // having ScummVM wait for ever in that cutaway // FIXME: Remove this hack once the palette fading is properly handled if (_vm->_scene->currentChapterNumber() == 2 && _vm->_scene->currentSceneNumber() == 41 && _vm->_anim->hasCutaway()) return; if (!_skipSpeeches) thread->waitFrames(_vm->_frameCount + frames); } void Script::sfScriptFade(SCRIPTFUNC_PARAMS) { thread->pop(); // first pal entry, ignored (already handled by Gfx::palToBlack) thread->pop(); // last pal entry, ignored (already handled by Gfx::palToBlack) int16 startingBrightness = thread->pop(); int16 endingBrightness = thread->pop(); // delay between pal changes is always 10 (not used) static PalEntry cur_pal[PAL_ENTRIES]; Event event; short delta = (startingBrightness < endingBrightness) ? +1 : -1; _vm->_gfx->getCurrentPal(cur_pal); // TODO: This is still wrong, probably a new event type needs to be added (kEventPalFade) warning("TODO: sfScriptFade"); return; if (startingBrightness > 255) startingBrightness = 255; if (startingBrightness < 0 ) startingBrightness = 0; if (endingBrightness > 255) endingBrightness = 255; if (endingBrightness < 0) endingBrightness = 0; event.type = kEvTImmediate; event.code = kPalEvent; event.op = kEventPalToBlack; event.time = 0; event.duration = kNormalFadeDuration - ((endingBrightness - startingBrightness) * delta); event.data = cur_pal; _vm->_events->queue(&event); } void Script::sfScriptStartVideo(SCRIPTFUNC_PARAMS) { int16 vid; int16 fade; vid = thread->pop(); fade = thread->pop(); _vm->_anim->setCutAwayMode(kPanelVideo); _vm->_anim->startVideo(vid, fade != 0); } void Script::sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS) { _vm->_anim->returnFromVideo(); } void Script::sfScriptEndVideo(SCRIPTFUNC_PARAMS) { _vm->_anim->endVideo(); } void Script::sf87(SCRIPTFUNC_PARAMS) { SF_stub("sf87", thread, nArgs); } void Script::sf88(SCRIPTFUNC_PARAMS) { SF_stub("sf88", thread, nArgs); } void Script::sf89(SCRIPTFUNC_PARAMS) { SF_stub("sf89", thread, nArgs); } void Script::sfVstopFX(SCRIPTFUNC_PARAMS) { _vm->_sound->stopSound(); } void Script::sfVstopLoopedFX(SCRIPTFUNC_PARAMS) { _vm->_sound->stopSound(); } void Script::sf92(SCRIPTFUNC_PARAMS) { SF_stub("sf92", thread, nArgs); // This opcode is empty in the full version of IHNM, but it's not empty in the demo } void Script::sfDemoIsInteractive(SCRIPTFUNC_PARAMS) { thread->_returnValue = 0; } void Script::sfVsetTrack(SCRIPTFUNC_PARAMS) { int16 chapter = thread->pop(); int16 sceneNumber = thread->pop(); int16 actorsEntrance = thread->pop(); debug(2, "sfVsetTrrack(%d, %d, %d)", chapter, sceneNumber, actorsEntrance); _vm->_scene->changeScene(sceneNumber, actorsEntrance, kTransitionFade, chapter); } void Script::sfGetPoints(SCRIPTFUNC_PARAMS) { int16 index = thread->pop(); if (index >= 0 && index < ARRAYSIZE(_vm->_ethicsPoints)) thread->_returnValue = _vm->_ethicsPoints[index]; else thread->_returnValue = 0; } void Script::sfSetGlobalFlag(SCRIPTFUNC_PARAMS) { int16 flag = thread->pop(); if (flag >= 0 && flag < 32) _vm->_globalFlags |= (1 << flag); } void Script::sfClearGlobalFlag(SCRIPTFUNC_PARAMS) { int16 flag = thread->pop(); if (flag >= 0 && flag < 32) _vm->_globalFlags &= ~(1 << flag); } void Script::sfTestGlobalFlag(SCRIPTFUNC_PARAMS) { int16 flag = thread->pop(); if (flag >= 0 && flag < 32 && _vm->_globalFlags & (1 << flag)) thread->_returnValue = 1; else thread->_returnValue = 0; } void Script::sfSetPoints(SCRIPTFUNC_PARAMS) { int16 index = thread->pop(); int16 points = thread->pop(); if (index >= 0 && index < ARRAYSIZE(_vm->_ethicsPoints)) _vm->_ethicsPoints[index] = points; } void Script::sfSetSpeechBox(SCRIPTFUNC_PARAMS) { int16 param1 = thread->pop(); int16 param2 = thread->pop(); int16 param3 = thread->pop(); int16 param4 = thread->pop(); _vm->_actor->_speechBoxScript.left = param1; _vm->_actor->_speechBoxScript.top = param2; _vm->_actor->_speechBoxScript.setWidth(param3 - param1); _vm->_actor->_speechBoxScript.setHeight(param4 - param2); } void Script::sfDebugShowData(SCRIPTFUNC_PARAMS) { int16 param = thread->pop(); char buf[50]; snprintf(buf, 50, "Reached breakpoint %d", param); _vm->_interface->setStatusText(buf); } void Script::sfWaitFramesEsc(SCRIPTFUNC_PARAMS) { thread->_returnValue = _vm->_framesEsc; } void Script::sfQueueMusic(SCRIPTFUNC_PARAMS) { int16 param1 = thread->pop(); int16 param2 = thread->pop(); Event event; if (param1 < 0) { _vm->_music->stop(); return; } if (param1 >= _vm->_music->_songTableLen) { warning("sfQueueMusic: Wrong song number (%d > %d)", param1, _vm->_music->_songTableLen - 1); } else { _vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1); event.type = kEvTOneshot; event.code = kMusicEvent; event.param = _vm->_music->_songTable[param1]; event.param2 = param2 ? MUSIC_LOOP : MUSIC_NORMAL; event.op = kEventPlay; event.time = _vm->ticksToMSec(500); // I find the delay in the original to be too long, so I've set it to // wait for half the time, which sounds better when chapter points // change // FIXME: If this is too short for other cases apart from chapter // point change, set it back to 1000 _vm->_events->queue(&event); if (!_vm->_scene->haveChapterPointsChanged()) { _vm->_scene->setCurrentMusicTrack(param1); _vm->_scene->setCurrentMusicRepeat(param2); } else { // Don't save this music track when saving in IHNM _vm->_scene->setChapterPointsChanged(false); } } } void Script::sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS) { int value = thread->pop(); _vm->_interface->disableAbortSpeeches(value != 0); } void Script::sfNull(SCRIPTFUNC_PARAMS) { for (int i = 0; i < nArgs; i++) thread->pop(); } void Script::SF_stub(const char *name, ScriptThread *thread, int nArgs) { char buf[256], buf1[100]; snprintf(buf, 256, "STUB: %s(", name); for (int i = 0; i < nArgs; i++) { snprintf(buf1, 100, "%d", thread->pop()); strncat(buf, buf1, 256); if (i + 1 < nArgs) strncat(buf, ", ", 256); } debug(0, "%s)", buf); } } // End of namespace Saga