diff options
Diffstat (limited to 'engines/sword2/function.cpp')
-rw-r--r-- | engines/sword2/function.cpp | 2479 |
1 files changed, 2479 insertions, 0 deletions
diff --git a/engines/sword2/function.cpp b/engines/sword2/function.cpp new file mode 100644 index 0000000000..e749e7b497 --- /dev/null +++ b/engines/sword2/function.cpp @@ -0,0 +1,2479 @@ +/* Copyright (C) 1994-1998 Revolution Software Ltd. + * Copyright (C) 2003-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "common/stdafx.h" +#include "common/system.h" +#include "common/file.h" + +#include "sword2/sword2.h" +#include "sword2/defs.h" +#include "sword2/build_display.h" +#include "sword2/console.h" +#include "sword2/interpreter.h" +#include "sword2/logic.h" +#include "sword2/maketext.h" +#include "sword2/mouse.h" +#include "sword2/resman.h" +#include "sword2/router.h" +#include "sword2/sound.h" +#include "sword2/animation.h" + +namespace Sword2 { + +int32 Logic::fnTestFunction(int32 *params) { + // params: 0 address of a flag + return IR_CONT; +} + +int32 Logic::fnTestFlags(int32 *params) { + // params: 0 value of flag + return IR_CONT; +} + +int32 Logic::fnRegisterStartPoint(int32 *params) { + // params: 0 id of startup script to call - key + // 1 pointer to ascii message + + int32 key = params[0]; + char *name = (char *)decodePtr(params[1]); + + _vm->registerStartPoint(key, name); + return IR_CONT; +} + +int32 Logic::fnInitBackground(int32 *params) { + // this screen defines the size of the back buffer + + // params: 0 res id of normal background layer - cannot be 0 + // 1 1 yes 0 no for a new palette + + _vm->_screen->initBackground(params[0], params[1]); + return IR_CONT; +} + +/** + * This function is used by start scripts. + */ + +int32 Logic::fnSetSession(int32 *params) { + // params: 0 id of new run list + + expressChangeSession(params[0]); + return IR_CONT; +} + +int32 Logic::fnBackSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteStatus(decodePtr(params[0]), BACK_SPRITE); + return IR_CONT; +} + +int32 Logic::fnSortSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteStatus(decodePtr(params[0]), SORT_SPRITE); + return IR_CONT; +} + +int32 Logic::fnForeSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteStatus(decodePtr(params[0]), FORE_SPRITE); + return IR_CONT; +} + +int32 Logic::fnRegisterMouse(int32 *params) { + // this call would be made from an objects service script 0 + // the object would be one with no graphic but with a mouse - i.e. a + // floor or one whose mouse area is manually defined rather than + // intended to fit sprite shape + + // params: 0 pointer to ObjectMouse or 0 for no write to mouse + // list + + _vm->_mouse->registerMouse(decodePtr(params[0]), NULL); + return IR_CONT; +} + +int32 Logic::fnAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 resource id of animation file + + // Normal forward animation + return _router->doAnimate( + decodePtr(params[0]), + decodePtr(params[1]), + params[2], false); +} + +int32 Logic::fnRandom(int32 *params) { + // params: 0 min + // 1 max + + writeVar(RESULT, _vm->_rnd.getRandomNumberRng(params[0], params[1])); + return IR_CONT; +} + +int32 Logic::fnPreLoad(int32 *params) { + // Forces a resource into memory before it's "officially" opened for + // use. eg. if an anim needs to run on smoothly from another, + // "preloading" gets it into memory in advance to avoid the cacheing + // delay that normally occurs before the first frame. + + // params: 0 resource to preload + + _vm->_resman->openResource(params[0]); + _vm->_resman->closeResource(params[0]); + return IR_CONT; +} + +int32 Logic::fnAddSubject(int32 *params) { + // params: 0 id + // 1 daves reference number + _vm->_mouse->addSubject(params[0], params[1]); + return IR_CONT; +} + +int32 Logic::fnInteract(int32 *params) { + // Run targets action on a subroutine. Called by player on his base + // level 0 idle, for example. + + // params: 0 id of target from which we derive action script + // reference + + writeVar(PLAYER_ACTION, 0); // must clear this + logicUp((params[0] << 16) | 2); // 3rd script of clicked on id + + // Out, up and around again - pc is saved for current level to be + // returned to. + return IR_GOSUB; +} + +int32 Logic::fnChoose(int32 *params) { + // params: none + + // This opcode is used to open the conversation menu. The human is + // switched off so there will be no normal mouse engine. + + // The player's choice is piggy-backed on the standard opcode return + // values, to be used with the CP_JUMP_ON_RETURNED opcode. As far as I + // can tell, this is the only function that uses that feature. + + uint32 response = _vm->_mouse->chooseMouse(); + + if (response == (uint32)-1) + return IR_REPEAT; + + return IR_CONT | (response << 3); +} + +/** + * Walk mega to (x,y,dir). Set RESULT to 0 if it succeeded. Otherwise, set + * RESULT to 1. + */ + +int32 Logic::fnWalk(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 target x-coord + // 5 target y-coord + // 6 target direction (8 means end walk on ANY direction) + + return _router->doWalk( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2]), + decodePtr(params[3]), + params[4], params[5], params[6]); +} + +/** + * Walk mega to start position of anim + */ + +int32 Logic::fnWalkToAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 anim resource id + + return _router->walkToAnim( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2]), + decodePtr(params[3]), + params[4]); +} + +/** + * Turn mega to the specified direction. + */ + +int32 Logic::fnTurn(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 target direction + + return _router->doFace( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2]), + decodePtr(params[3]), + params[4]); +} + +/** + * Stand mega at (x,y,dir) + * Sets up the graphic object, but also needs to set the new 'current_dir' in + * the mega object, so the router knows in future + */ + +int32 Logic::fnStandAt(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 pointer to object's mega structure + // 2 target x-coord + // 3 target y-coord + // 4 target direction + + _router->standAt( + decodePtr(params[0]), + decodePtr(params[1]), + params[2], params[3], params[4]); + return IR_CONT; +} + +/** + * Stand mega into the specified direction at current feet coords. + * Just needs to call standAt() with current feet coords. + */ + +int32 Logic::fnStand(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 pointer to object's mega structure + // 2 target direction + byte *ob_mega = decodePtr(params[1]); + + ObjectMega obMega(ob_mega); + + _router->standAt( + decodePtr(params[0]), + ob_mega, obMega.getFeetX(), obMega.getFeetY(), params[2]); + return IR_CONT; +} + +/** + * stand mega at end position of anim + */ + +int32 Logic::fnStandAfterAnim(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 pointer to object's mega structure + // 2 anim resource id + + _router->standAfterAnim( + decodePtr(params[0]), + decodePtr(params[1]), + params[2]); + return IR_CONT; +} + +int32 Logic::fnPause(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 number of game-cycles to pause + + // NB. Pause-value of 0 causes script to continue, 1 causes a 1-cycle + // quit, 2 gives 2 cycles, etc. + + ObjectLogic obLogic(decodePtr(params[0])); + + if (obLogic.getLooping() == 0) { + obLogic.setLooping(1); + obLogic.setPause(params[1]); + } + + if (obLogic.getPause()) { + obLogic.setPause(obLogic.getPause() - 1); + return IR_REPEAT; + } + + obLogic.setLooping(0); + return IR_CONT; +} + +int32 Logic::fnMegaTableAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to animation table + + // Normal forward anim + return _router->megaTableAnimate( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2]), + decodePtr(params[3]), + false); +} + +int32 Logic::fnAddMenuObject(int32 *params) { + // params: 0 pointer to a MenuObject structure to copy down + + _vm->_mouse->addMenuObject(decodePtr(params[0])); + return IR_CONT; +} + +/** + * Start a conversation. + * + * Note that fnStartConversation() might accidentally be called every time the + * script loops back for another chooser, but we only want to reset the chooser + * count flag the first time this function is called, i.e. when the talk flag + * is zero. + */ + +int32 Logic::fnStartConversation(int32 *params) { + // params: none + + _vm->_mouse->startConversation(); + return IR_CONT; +} + +/** + * End a conversation. + */ + +int32 Logic::fnEndConversation(int32 *params) { + // params: none + + _vm->_mouse->endConversation(); + return IR_CONT; +} + +int32 Logic::fnSetFrame(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 resource id of animation file + // 2 frame flag (0=first 1=last) + + int32 res = params[1]; + assert(res); + + // open the resource (& check it's valid) + byte *anim_file = _vm->_resman->openResource(res); + + assert(_vm->_resman->fetchType(res) == ANIMATION_FILE); + + // set up pointer to the animation header + AnimHeader anim_head; + + anim_head.read(_vm->fetchAnimHeader(anim_file)); + + // set up anim resource in graphic object + ObjectGraphic obGraph(decodePtr(params[0])); + + obGraph.setAnimResource(res); + obGraph.setAnimPc(params[2] ? anim_head.noAnimFrames - 1 : 0); + + // Close the anim file and drop out of script + _vm->_resman->closeResource(obGraph.getAnimResource()); + return IR_CONT; +} + +int32 Logic::fnRandomPause(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 minimum number of game-cycles to pause + // 2 maximum number of game-cycles to pause + + ObjectLogic obLogic(decodePtr(params[0])); + int32 pars[2]; + + if (obLogic.getLooping() == 0) { + pars[0] = params[1]; + pars[1] = params[2]; + fnRandom(pars); + pars[1] = readVar(RESULT); + } + + pars[0] = params[0]; + return fnPause(pars); +} + +int32 Logic::fnRegisterFrame(int32 *params) { + // this call would be made from an objects service script 0 + + // params: 0 pointer to mouse structure or NULL for no write to + // mouse list (non-zero means write sprite-shape to + // mouse list) + // 1 pointer to graphic structure + // 2 pointer to mega structure or NULL if not a mega + + _vm->_screen->registerFrame( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2])); + return IR_CONT; +} + +int32 Logic::fnNoSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteStatus(decodePtr(params[0]), NO_SPRITE); + return IR_CONT; +} + +int32 Logic::fnSendSync(int32 *params) { + // params: 0 sync's recipient + // 1 sync value + + sendSync(params[0], params[1]); + return IR_CONT; +} + +int32 Logic::fnUpdatePlayerStats(int32 *params) { + // engine needs to know certain info about the player + + // params: 0 pointer to mega structure + + ObjectMega obMega(decodePtr(params[0])); + + ScreenInfo *screenInfo = _vm->_screen->getScreenInfo(); + + screenInfo->player_feet_x = obMega.getFeetX(); + screenInfo->player_feet_y = obMega.getFeetY(); + + // for the script + writeVar(PLAYER_FEET_X, obMega.getFeetX()); + writeVar(PLAYER_FEET_Y, obMega.getFeetY()); + writeVar(PLAYER_CUR_DIR, obMega.getCurDir()); + writeVar(SCROLL_OFFSET_X, screenInfo->scroll_offset_x); + + debug(5, "fnUpdatePlayerStats: %d %d", + obMega.getFeetX(), obMega.getFeetY()); + + return IR_CONT; +} + +int32 Logic::fnPassGraph(int32 *params) { + // makes an engine local copy of passed ObjectGraphic - run script 4 + // of an object to request this used by fnTurnTo(id) etc + // + // remember, we cannot simply read a compact any longer but instead + // must request it from the object itself + + // params: 0 pointer to an ObjectGraphic structure + + warning("fnPassGraph() is a no-op now"); + return IR_CONT; +} + +int32 Logic::fnInitFloorMouse(int32 *params) { + // params: 0 pointer to object's mouse structure + + byte *ob_mouse = decodePtr(params[0]); + ScreenInfo *screenInfo = _vm->_screen->getScreenInfo(); + + // floor is always lowest priority + + ObjectMouse mouse; + + mouse.x1 = 0; + mouse.y1 = 0; + mouse.x2 = screenInfo->screen_wide - 1; + mouse.y2 = screenInfo->screen_deep - 1; + mouse.priority = 9; + mouse.pointer = NORMAL_MOUSE_ID; + + mouse.write(ob_mouse); + return IR_CONT; +} + +int32 Logic::fnPassMega(int32 *params) { + // makes an engine local copy of passed mega_structure - run script 4 + // of an object to request this used by fnTurnTo(id) etc + // + // remember, we cannot simply read a compact any longer but instead + // must request it from the object itself + + // params: 0 pointer to a mega structure + + memcpy(_engineMega, decodePtr(params[0]), ObjectMega::size()); + return IR_CONT; +} + +/** + * Turn mega to face point (x,y) on the floor + */ + +int32 Logic::fnFaceXY(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 target x-coord + // 5 target y-coord + + return _router->faceXY( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2]), + decodePtr(params[3]), + params[4], params[5]); +} + +/** + * Causes no more objects in this logic loop to be processed. The logic engine + * will restart at the beginning of the new list. The current screen will not + * be drawn! + */ + +int32 Logic::fnEndSession(int32 *params) { + // params: 0 id of new run-list + + // terminate current and change to next run-list + expressChangeSession(params[0]); + + // stop the script - logic engine will now go around and the new + // screen will begin + return IR_STOP; +} + +int32 Logic::fnNoHuman(int32 *params) { + // params: none + + _vm->_mouse->noHuman(); + return IR_CONT; +} + +int32 Logic::fnAddHuman(int32 *params) { + // params: none + + _vm->_mouse->addHuman(); + return IR_CONT; +} + +/** + * Wait for a target to become waiting, i.e. not busy. + */ + +int32 Logic::fnWeWait(int32 *params) { + // params: 0 target + + assert(_vm->_resman->fetchType(params[0]) == GAME_OBJECT); + + // Run the target's get-speech-state script + runResScript(params[0], 5); + + if (readVar(RESULT) == 0) { + // The target is busy. Try again. + _vm->_debugger->_speechScriptWaiting = params[0]; + return IR_REPEAT; + } + + // The target is waiting, i.e. not busy. + + _vm->_debugger->_speechScriptWaiting = 0; + return IR_CONT; +} + +/** + * Wait for a target to become waiting, i.e. not busy, send a command to it, + * then wait for it to finish. + */ + +int32 Logic::fnTheyDoWeWait(int32 *params) { + // params: 0 pointer to ob_logic + // 1 target + // 2 command + // 3 ins1 + // 4 ins2 + // 5 ins3 + // 6 ins4 + // 7 ins5 + + assert(_vm->_resman->fetchType(params[1]) == GAME_OBJECT); + + // Run the target's get-speech-state script + runResScript(params[1], 5); + + ObjectLogic obLogic(decodePtr(params[0])); + + if (readVar(RESULT) == 1 && readVar(INS_COMMAND) == 0 && obLogic.getLooping() == 0) { + // The target is waiting, i.e. not busy, and there is no other + // command queued. We haven't sent the command yet, so do it. + + debug(5, "fnTheyDoWeWait: sending command to %d", params[1]); + + _vm->_debugger->_speechScriptWaiting = params[1]; + obLogic.setLooping(1); + + writeVar(SPEECH_ID, params[1]); + writeVar(INS_COMMAND, params[2]); + writeVar(INS1, params[3]); + writeVar(INS2, params[4]); + writeVar(INS3, params[5]); + writeVar(INS4, params[6]); + writeVar(INS5, params[7]); + + return IR_REPEAT; + } + + if (obLogic.getLooping() == 0) { + // The command has not been sent yet. Keep waiting. + _vm->_debugger->_speechScriptWaiting = params[1]; + return IR_REPEAT; + } + + if (readVar(RESULT) == 0) { + // The command has been sent, and the target is busy doing it. + // Wait for it to finish. + + debug(5, "fnTheyDoWeWait: Waiting for %d to finish", params[1]); + + _vm->_debugger->_speechScriptWaiting = params[1]; + return IR_REPEAT; + } + + debug(5, "fnTheyDoWeWait: %d finished", params[1]); + + obLogic.setLooping(0); + _vm->_debugger->_speechScriptWaiting = 0; + return IR_CONT; +} + +/** + * Wait for a target to become waiting, i.e. not busy, then send a command to + * it. + */ + +int32 Logic::fnTheyDo(int32 *params) { + // params: 0 target + // 1 command + // 2 ins1 + // 3 ins2 + // 4 ins3 + // 5 ins4 + // 6 ins5 + + assert(_vm->_resman->fetchType(params[0]) == GAME_OBJECT); + + // Run the target's get-speech-state script + runResScript(params[0], 5); + + if (readVar(RESULT) == 1 && !readVar(INS_COMMAND)) { + // The target is waiting, i.e. not busy, and there is no other + // command queued. Send the command. + + debug(5, "fnTheyDo: sending command to %d", params[0]); + + _vm->_debugger->_speechScriptWaiting = 0; + + writeVar(SPEECH_ID, params[0]); + writeVar(INS_COMMAND, params[1]); + writeVar(INS1, params[2]); + writeVar(INS2, params[3]); + writeVar(INS3, params[4]); + writeVar(INS4, params[5]); + writeVar(INS5, params[6]); + + return IR_CONT; + } + + // The target is busy. Come back again next cycle. + + _vm->_debugger->_speechScriptWaiting = params[0]; + return IR_REPEAT; +} + +/** + * Route to the left or right hand side of target id, if possible. + */ + +int32 Logic::fnWalkToTalkToMega(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 id of target mega to face + // 5 separation + + return _router->walkToTalkToMega( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2]), + decodePtr(params[3]), + params[4], params[5]); +} + +int32 Logic::fnFadeDown(int32 *params) { + // NONE means up! can only be called when screen is fully faded up - + // multiple calls wont have strange effects + + // params: none + + if (_vm->_screen->getFadeStatus() == RDFADE_NONE) + _vm->_screen->fadeDown(); + + return IR_CONT; +} + +enum { + S_OB_GRAPHIC = 0, + S_OB_SPEECH = 1, + S_OB_LOGIC = 2, + S_OB_MEGA = 3, + + S_TEXT = 4, + S_WAV = 5, + S_ANIM = 6, + S_DIR_TABLE = 7, + S_ANIM_MODE = 8 +}; + +/** + * It's the super versatile fnSpeak. Text and wavs can be selected in any + * combination. + * + * @note We can assume no human - there should be no human, at least! + */ + +int32 Logic::fnISpeak(int32 *params) { + // params: 0 pointer to ob_graphic + // 1 pointer to ob_speech + // 2 pointer to ob_logic + // 3 pointer to ob_mega + // 4 encoded text number + // 5 wav res id + // 6 anim res id + // 7 anim table res id + // 8 animation mode 0 lip synced, + // 1 just straight animation + + static bool cycle_skip = false; + static bool speechRunning; + + // Set up the pointers which we know we'll always need + + ObjectLogic obLogic(decodePtr(params[S_OB_LOGIC])); + ObjectGraphic obGraph(decodePtr(params[S_OB_GRAPHIC])); + + // FIRST TIME ONLY: create the text, load the wav, set up the anim, + // etc. + + if (obLogic.getLooping() == 0) { + // New fudge to wait for smacker samples to finish + // since they can over-run into the game + + if (_vm->_sound->getSpeechStatus() != RDSE_SAMPLEFINISHED) + return IR_REPEAT; + + // New fudge for 'fx' subtitles: If subtitles switched off, and + // we don't want to use a wav for this line either, then just + // quit back to script right now! + + if (!_vm->getSubtitles() && !wantSpeechForLine(params[S_WAV])) + return IR_CONT; + + // Drop out for 1st cycle to allow walks/anims to end and + // display last frame before system locks while speech loaded + + if (!cycle_skip) { + cycle_skip = true; + return IR_REPEAT; + } + + cycle_skip = false; + + _vm->_debugger->_textNumber = params[S_TEXT]; + + // Pull out the text line to get the official text number + // (for wav id). Once the wav id's go into all script text + // commands, we'll only need this for debugging. + + uint32 text_res = params[S_TEXT] / SIZE; + uint32 local_text = params[S_TEXT] & 0xffff; + + // For testing all text & speech! + // + // A script loop can send any text number to fnISpeak and it + // will only run the valid ones or return with 'result' equal + // to '1' or '2' to mean 'invalid text resource' and 'text + // number out of range' respectively + // + // See 'testing_routines' object in George's Player Character + // section of linc + + if (readVar(SYSTEM_TESTING_TEXT)) { + if (!_vm->_resman->checkValid(text_res)) { + // Not a valid resource number - invalid (null + // resource) + writeVar(RESULT, 1); + return IR_CONT; + } + + if (_vm->_resman->fetchType(text_res) != TEXT_FILE) { + // Invalid - not a text resource + _vm->_resman->closeResource(text_res); + writeVar(RESULT, 1); + return IR_CONT; + } + + if (!_vm->checkTextLine(_vm->_resman->openResource(text_res), local_text)) { + // Line number out of range + _vm->_resman->closeResource(text_res); + writeVar(RESULT, 2); + return IR_CONT; + } + + _vm->_resman->closeResource(text_res); + writeVar(RESULT, 0); + } + + byte *text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text); + _officialTextNumber = READ_LE_UINT16(text); + _vm->_resman->closeResource(text_res); + + // Prevent dud lines from appearing while testing text & speech + // since these will not occur in the game anyway + + if (readVar(SYSTEM_TESTING_TEXT)) { + // If actor number is 0 and text line is just a 'dash' + // character + if (_officialTextNumber == 0 && text[2] == '-' && text[3] == 0) { + writeVar(RESULT, 3); + return IR_CONT; + } + } + + // Set the 'looping_flag' and the text-click-delays. We can + // left-click past the text after half a second, and + // right-click past it after a quarter of a second. + + obLogic.setLooping(1); + _leftClickDelay = 6; + _rightClickDelay = 3; + + if (readVar(PLAYER_ID) != CUR_PLAYER_ID) + debug(5, "(%d) Nico: %s", _officialTextNumber, text + 2); + else { + byte buf[NAME_LEN]; + + debug(5, "(%d) %s: %s", _officialTextNumber, _vm->_resman->fetchName(readVar(ID), buf), text + 2); + } + + // Set up the speech animation + + if (params[S_ANIM]) { + // Just a straight anim. + _animId = params[6]; + } else if (params[S_DIR_TABLE]) { + // Use this direction table to derive the anim + // NB. ASSUMES WE HAVE A MEGA OBJECT!! + + ObjectMega obMega(decodePtr(params[S_OB_MEGA])); + byte *anim_table = decodePtr(params[S_DIR_TABLE]); + + _animId = READ_LE_UINT32(anim_table + 4 * obMega.getCurDir()); + } else { + // No animation choosen + _animId = 0; + } + + if (_animId) { + // Set the talker's graphic to the first frame of this + // speech anim for now. + + _speechAnimType = readVar(SPEECHANIMFLAG); + obGraph.setAnimResource(_animId); + obGraph.setAnimPc(0); + } + + // Default back to looped lip synced anims. + writeVar(SPEECHANIMFLAG, 0); + + // Set up _textX and _textY for speech panning and/or text + // sprite position. + + locateTalker(params); + + // Is it to be speech or subtitles or both? + + // Assume not running until know otherwise + speechRunning = false; + + // New fudge for 'fx' subtitles: If speech is selected, and + // this line is allowed speech (not if it's an fx subtitle!) + + if (!_vm->_sound->isSpeechMute() && wantSpeechForLine(_officialTextNumber)) { + // If the wavId parameter is zero because not yet + // compiled into speech command, we can still get it + // from the 1st 2 chars of the text line. + + if (!params[S_WAV]) + params[S_WAV] = (int32)_officialTextNumber; + + // Panning goes from -16 (left) to 16 (right) + int8 speech_pan = ((_textX - 320) * 16) / 320; + + if (speech_pan < -16) + speech_pan = -16; + else if (speech_pan > 16) + speech_pan = 16; + + uint32 rv = _vm->_sound->playCompSpeech(params[S_WAV], 16, speech_pan); + + if (rv == RD_OK) { + // Ok, we've got something to play. Set it + // playing now. (We might want to do this the + // next cycle, don't know yet.) + + speechRunning = true; + _vm->_sound->unpauseSpeech(); + } else { + debug(5, "ERROR: PlayCompSpeech(wav=%d (res=%d pos=%d)) returned %.8x", params[S_WAV], text_res, local_text, rv); + } + } + + if (_vm->getSubtitles() || !speechRunning) { + // We want subtitles, or the speech failed to load. + // Either way, we're going to show the text so create + // the text sprite. + + formText(params); + } + } + + // EVERY TIME: run a cycle of animation, if there is one + + if (_animId) { + // There is an animation - Increment the anim frame number. + obGraph.setAnimPc(obGraph.getAnimPc() + 1); + + byte *anim_file = _vm->_resman->openResource(obGraph.getAnimResource()); + AnimHeader anim_head; + + anim_head.read(_vm->fetchAnimHeader(anim_file)); + + if (!_speechAnimType) { + // ANIM IS TO BE LIP-SYNC'ED & REPEATING + + if (obGraph.getAnimPc() == (int32)anim_head.noAnimFrames) { + // End of animation - restart from frame 0 + obGraph.setAnimPc(0); + } else if (speechRunning && _vm->_sound->amISpeaking() == RDSE_QUIET) { + // The speech is running, but we're at a quiet + // bit. Restart from frame 0 (closed mouth). + obGraph.setAnimPc(0); + } + } else { + // ANIM IS TO PLAY ONCE ONLY + if (obGraph.getAnimPc() == (int32)anim_head.noAnimFrames - 1) { + // Reached the last frame of the anim. Hold + // anim on this last frame + _animId = 0; + } + } + + _vm->_resman->closeResource(obGraph.getAnimResource()); + } else if (_speechAnimType) { + // Placed here so we actually display the last frame of the + // anim. + _speechAnimType = 0; + } + + // EVERY TIME: FIND OUT IF WE NEED TO STOP THE SPEECH NOW... + + // If there is a wav then we're using that to end the speech naturally + + bool speechFinished = false; + + // If playing a sample + + if (speechRunning) { + // Has it finished? + if (_vm->_sound->getSpeechStatus() == RDSE_SAMPLEFINISHED) + speechFinished = true; + } else if (!speechRunning && _speechTime) { + // Counting down text time because there is no sample - this + // ends the speech + + // if no sample then we're using _speechTime to end speech + // naturally + + _speechTime--; + if (!_speechTime) + speechFinished = true; + } + + // Ok, all is running along smoothly - but a click means stop + // unnaturally + + int mouseX, mouseY; + + _vm->_mouse->getPos(mouseX, mouseY); + + // So that we can go to the options panel while text & speech is + // being tested + if (readVar(SYSTEM_TESTING_TEXT) == 0 || mouseY > 0) { + MouseEvent *me = _vm->mouseEvent(); + + // Note that we now have TWO click-delays - one for LEFT + // button, one for RIGHT BUTTON + + if ((!_leftClickDelay && me && (me->buttons & RD_LEFTBUTTONDOWN)) || + (!_rightClickDelay && me && (me->buttons & RD_RIGHTBUTTONDOWN))) { + // Mouse click, after click_delay has expired -> end + // the speech. + + // if testing text & speech + if (readVar(SYSTEM_TESTING_TEXT)) { + // and RB used to click past text + if (me->buttons & RD_RIGHTBUTTONDOWN) { + // then we want the previous line again + writeVar(SYSTEM_WANT_PREVIOUS_LINE, 1); + } else { + // LB just want next line again + writeVar(SYSTEM_WANT_PREVIOUS_LINE, 0); + } + } + + speechFinished = true; + + // if speech sample playing, halt it prematurely + if (speechRunning) + _vm->_sound->stopSpeech(); + } + } + + // If we are finishing the speech this cycle, do the business + + // !speechAnimType, as we want an anim which is playing once to have + // finished. + + if (speechFinished && !_speechAnimType) { + // If there is text, kill it + if (_speechTextBlocNo) { + _vm->_fontRenderer->killTextBloc(_speechTextBlocNo); + _speechTextBlocNo = 0; + } + + // if there is a speech anim, end it on closed mouth frame + if (_animId) { + _animId = 0; + obGraph.setAnimPc(0); + } + + speechRunning = false; + + // no longer in a script function loop + obLogic.setLooping(0); + + _vm->_debugger->_textNumber = 0; + + // reset to zero, in case text line not even extracted (since + // this number comes from the text line) + _officialTextNumber = 0; + + writeVar(RESULT, 0); + return IR_CONT; + } + + // Speech still going, so decrement the click_delay if it's still + // active + + if (_leftClickDelay) + _leftClickDelay--; + + if (_rightClickDelay) + _rightClickDelay--; + + return IR_REPEAT; +} + +/** + * Reset the object and restart script 1 on level 0 + */ + +int32 Logic::fnTotalRestart(int32 *params) { + // mega runs this to restart its base logic again - like being cached + // in again + + // params: none + + _curObjectHub.setLogicLevel(0); + _curObjectHub.setScriptPc(0, 1); + + return IR_TERMINATE; +} + +int32 Logic::fnSetWalkGrid(int32 *params) { + // params: none + + warning("fnSetWalkGrid() is no longer a valid opcode"); + return IR_CONT; +} + +/** + * Receive and sequence the commands sent from the conversation script. We have + * to do this in a slightly tweeky manner as we can no longer have generic + * scripts. + */ + +enum { + INS_talk = 1, + INS_anim = 2, + INS_reverse_anim = 3, + INS_walk = 4, + INS_turn = 5, + INS_face = 6, + INS_trace = 7, + INS_no_sprite = 8, + INS_sort = 9, + INS_foreground = 10, + INS_background = 11, + INS_table_anim = 12, + INS_reverse_table_anim = 13, + INS_walk_to_anim = 14, + INS_set_frame = 15, + INS_stand_after_anim = 16, + INS_quit = 42 +}; + +int32 Logic::fnSpeechProcess(int32 *params) { + // params: 0 pointer to ob_graphic + // 1 pointer to ob_speech + // 2 pointer to ob_logic + // 3 pointer to ob_mega + // 4 pointer to ob_walkdata + + ObjectSpeech obSpeech(decodePtr(params[1])); + + while (1) { + int32 pars[9]; + + // Check which command we're waiting for, and call the + // appropriate function. Once we're done, clear the command + // and set wait_state to 1. + // + // Note: we could save a var and ditch wait_state and check + // 'command' for non zero means busy + // + // Note: I can't see that we ever check the value of wait_state + // but perhaps it accesses that memory location directly? + + switch (obSpeech.getCommand()) { + case 0: + break; + case INS_talk: + pars[0] = params[0]; // ob_graphic + pars[1] = params[1]; // ob_speech + pars[2] = params[2]; // ob_logic + pars[3] = params[3]; // ob_mega + pars[4] = obSpeech.getIns1(); // encoded text number + pars[5] = obSpeech.getIns2(); // wav res id + pars[6] = obSpeech.getIns3(); // anim res id + pars[7] = obSpeech.getIns4(); // anim table res id + pars[8] = obSpeech.getIns5(); // animation mode - 0 lip synced, 1 just straight animation + + if (fnISpeak(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_turn: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = params[4]; // ob_walkdata + pars[4] = obSpeech.getIns1(); // direction to turn to + + if (fnTurn(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_face: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = params[4]; // ob_walkdata + pars[4] = obSpeech.getIns1(); // target + + if (fnFaceMega(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = obSpeech.getIns1(); // anim res + + if (fnAnim(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_reverse_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = obSpeech.getIns1(); // anim res + + if (fnReverseAnim(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_table_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = obSpeech.getIns1(); // pointer to anim table + + if (fnMegaTableAnim(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_reverse_table_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = obSpeech.getIns1(); // pointer to anim table + + if (fnReverseMegaTableAnim(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_no_sprite: + fnNoSprite(params); // ob_graphic + + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + return IR_REPEAT ; + case INS_sort: + fnSortSprite(params); // ob_graphic + + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + return IR_REPEAT; + case INS_foreground: + fnForeSprite(params); // ob_graphic + + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + return IR_REPEAT; + case INS_background: + fnBackSprite(params); // ob_graphic + + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + return IR_REPEAT; + case INS_walk: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = params[4]; // ob_walkdata + pars[4] = obSpeech.getIns1(); // target x + pars[5] = obSpeech.getIns2(); // target y + pars[6] = obSpeech.getIns3(); // target direction + + if (fnWalk(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_walk_to_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = params[4]; // ob_walkdata + pars[4] = obSpeech.getIns1(); // anim resource + + if (fnWalkToAnim(pars) != IR_REPEAT) { + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + } + + return IR_REPEAT; + case INS_stand_after_anim: + pars[0] = params[0]; // ob_graphic + pars[1] = params[3]; // ob_mega + pars[2] = obSpeech.getIns1(); // anim resource + + fnStandAfterAnim(pars); + + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + return IR_REPEAT; + case INS_set_frame: + pars[0] = params[0]; // ob_graphic + pars[1] = obSpeech.getIns1(); // anim_resource + pars[2] = obSpeech.getIns2(); // FIRST_FRAME or LAST_FRAME + fnSetFrame(pars); + + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + return IR_REPEAT; + case INS_quit: + // That's it - we're finished with this + obSpeech.setCommand(0); + // obSpeech.setWaitState(0); + return IR_CONT; + default: + // Unimplemented command - just cancel + obSpeech.setCommand(0); + obSpeech.setWaitState(1); + break; + } + + if (readVar(SPEECH_ID) == readVar(ID)) { + // There's a new command for us! Grab the command - + // potentially we only have this cycle to do this - and + // set things up so that the command will be picked up + // on the next iteration of the while loop. + + debug(5, "fnSpeechProcess: Received new command %d", readVar(INS_COMMAND)); + + writeVar(SPEECH_ID, 0); + + obSpeech.setCommand(readVar(INS_COMMAND)); + obSpeech.setIns1(readVar(INS1)); + obSpeech.setIns2(readVar(INS2)); + obSpeech.setIns3(readVar(INS3)); + obSpeech.setIns4(readVar(INS4)); + obSpeech.setIns5(readVar(INS5)); + obSpeech.setWaitState(0); + + writeVar(INS_COMMAND, 0); + } else { + // No new command. We could run a blink anim (or + // something) here. + + obSpeech.setWaitState(1); + return IR_REPEAT; + } + } +} + +int32 Logic::fnSetScaling(int32 *params) { + // params: 0 pointer to object's mega structure + // 1 scale constant A + // 2 scale constant B + + // 256 * s = A * y + B + + // Where s is system scale, which itself is (256 * actual_scale) ie. + // s == 128 is half size + + ObjectMega obMega(decodePtr(params[0])); + + obMega.setScaleA(params[1]); + obMega.setScaleB(params[2]); + + return IR_CONT; +} + +int32 Logic::fnStartEvent(int32 *params) { + // params: none + + startEvent(); + return IR_TERMINATE; +} + +int32 Logic::fnCheckEventWaiting(int32 *params) { + // params: none + + writeVar(RESULT, checkEventWaiting()); + return IR_CONT; +} + +int32 Logic::fnRequestSpeech(int32 *params) { + // change current script - must be followed by a TERMINATE script + // directive + + // params: 0 id of target to catch the event and startup speech + // servicing + + // Full script id to interact with - megas run their own 7th script + sendEvent(params[0], (params[0] << 16) | 6); + return IR_CONT; +} + +int32 Logic::fnGosub(int32 *params) { + // params: 0 id of script + + // Hurray, script subroutines. Logic goes up - pc is saved for current + // level. + logicUp(params[0]); + return IR_GOSUB; +} + +/** + * Wait for a target to become waiting, i.e. not busy, or until we time out. + * This is useful when clicking on a target to talk to it, and it doesn't + * reply. This way, we won't lock up. + * + * If the target becomes waiting, RESULT is set to 0. If we time out, RESULT is + * set to 1. + */ + +int32 Logic::fnTimedWait(int32 *params) { + // params: 0 ob_logic + // 1 target + // 2 number of cycles before give up + + assert(_vm->_resman->fetchType(params[1]) == GAME_OBJECT); + + ObjectLogic obLogic(decodePtr(params[0])); + + if (obLogic.getLooping() == 0) { + // This is the first time, so set up the time-out. + obLogic.setLooping(params[2]); + } + + // Run the target's get-speech-state script + runResScript(params[1], 5); + + if (readVar(RESULT) == 1) { + // The target is waiting, i.e. not busy + + _vm->_debugger->_speechScriptWaiting = 0; + + obLogic.setLooping(0); + writeVar(RESULT, 0); + return IR_CONT; + } + + obLogic.setLooping(obLogic.getLooping() - 1); + + if (obLogic.getLooping() == 0) { + // Time's up. + + debug(5, "fnTimedWait: Timed out waiting for %d", params[1]); + _vm->_debugger->_speechScriptWaiting = 0; + + // Clear the event that hasn't been picked up - in theory, + // none of this should ever happen. + + killAllIdsEvents(params[1]); + writeVar(RESULT, 1); + return IR_CONT; + } + + // Target is busy. Keep trying. + + _vm->_debugger->_speechScriptWaiting = params[1]; + return IR_REPEAT; +} + +int32 Logic::fnPlayFx(int32 *params) { + // params: 0 sample resource id + // 1 type (FX_SPOT, FX_RANDOM, FX_LOOP) + // 2 delay (0..65535) + // 3 volume (0..16) + // 4 pan (-16..16) + + // example script: + // fnPlayFx (FXWATER, FX_LOOP, 0, 10, 15); + // // fx_water is just a local script flag + // fx_water = result; + // . + // . + // . + // fnStopFx (fx_water); + + int32 res = params[0]; + int32 type = params[1]; + int32 delay = params[2]; + int32 volume = params[3]; + int32 pan = params[4]; + + _vm->_sound->queueFx(res, type, delay, volume, pan); + return IR_CONT; +} + +int32 Logic::fnStopFx(int32 *params) { + // params: 0 position in queue + if (_vm->_sound->stopFx(params[0]) != RD_OK) + debug(5, "SFX ERROR: Trying to stop an inactive sound slot"); + + return IR_CONT; +} + +/** + * Start a tune playing, to play once or to loop until stopped or next one + * played. + */ + +int32 Logic::fnPlayMusic(int32 *params) { + // params: 0 tune id + // 1 loop flag (0 or 1) + + char filename[128]; + bool loopFlag; + uint32 rv; + + loopFlag = (params[1] == FX_LOOP); + + rv = _vm->_sound->streamCompMusic(params[0], loopFlag); + + if (rv) + debug(5, "ERROR: streamCompMusic(%s, %d, %d) returned error 0x%.8x", filename, params[0], loopFlag, rv); + + return IR_CONT; +} + +int32 Logic::fnStopMusic(int32 *params) { + // params: none + + _vm->_sound->stopMusic(false); + return IR_CONT; +} + +int32 Logic::fnSetValue(int32 *params) { + // temp. function! + + // used for setting far-referenced megaset resource field in mega + // object, from start script + + // params: 0 pointer to object's mega structure + // 1 value to set it to + + ObjectMega obMega(decodePtr(params[0])); + + obMega.setMegasetRes(params[1]); + return IR_CONT; +} + +int32 Logic::fnNewScript(int32 *params) { + // change current script - must be followed by a TERMINATE script + // directive + + // params: 0 id of script + + writeVar(PLAYER_ACTION, 0); // must clear this + logicReplace(params[0]); + return IR_TERMINATE; +} + +/** + * Like getSync(), but called from scripts. Sets the RESULT variable to + * the sync value, or 0 if none is found. + */ + +int32 Logic::fnGetSync(int32 *params) { + // params: none + + int slot = getSync(); + + writeVar(RESULT, (slot != -1) ? _syncList[slot].sync : 0); + return IR_CONT; +} + +/** + * Wait for sync to happen. Sets the RESULT variable to the sync value, once + * it has been found. + */ + +int32 Logic::fnWaitSync(int32 *params) { + // params: none + + debug(6, "fnWaitSync: %d waits", readVar(ID)); + + int slot = getSync(); + + if (slot == -1) + return IR_REPEAT; + + debug(5, "fnWaitSync: %d got sync %d", readVar(ID), _syncList[slot].sync); + writeVar(RESULT, _syncList[slot].sync); + return IR_CONT; +} + +int32 Logic::fnRegisterWalkGrid(int32 *params) { + // params: none + + warning("fnRegisterWalkGrid() is no longer a valid opcode"); + return IR_CONT; +} + +int32 Logic::fnReverseMegaTableAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to animation table + + // Reverse anim + return _router->megaTableAnimate( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2]), + decodePtr(params[3]), + true); +} + +int32 Logic::fnReverseAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 resource id of animation file + + // Reverse anim + return _router->doAnimate( + decodePtr(params[0]), + decodePtr(params[1]), + params[2], true); +} + +/** + * Mark this object for killing - to be killed when player leaves this screen. + * Object reloads and script restarts upon re-entry to screen, which causes + * this object's startup logic to be re-run every time we enter the screen. + * "Which is nice." + * + * @note Call ONCE from object's logic script, i.e. in startup code, so not + * re-called every time script frops off and restarts! + */ + +int32 Logic::fnAddToKillList(int32 *params) { + // params: none + uint32 id = readVar(ID); + + // DON'T EVER KILL GEORGE! + if (id == CUR_PLAYER_ID) + return IR_CONT; + + // Scan the list to see if it's already included + + for (uint32 i = 0; i < _kills; i++) { + if (_objectKillList[i] == id) + return IR_CONT; + } + + assert(_kills < OBJECT_KILL_LIST_SIZE); // no room at the inn + + _objectKillList[_kills++] = id; + + // "another one bites the dust" + + // When we leave the screen, all these object resources are to be + // cleaned out of memory and the kill list emptied by doing + // '_kills = 0', ensuring that all resources are in fact still in + // memory and, more importantly, closed before killing! + + return IR_CONT; +} + +/** + * Set the standby walk coords to be used by fnWalkToAnim() and + * fnStandAfterAnim() when the anim header's start/end coords are zero. + * Useful during development; can stay in final game anyway. + */ + +int32 Logic::fnSetStandbyCoords(int32 *params) { + // params: 0 x-coord + // 1 y-coord + // 2 direction (0..7) + + _router->setStandbyCoords(params[0], params[1], params[2]); + return IR_CONT; +} + +int32 Logic::fnBackPar0Sprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteStatus(decodePtr(params[0]), BGP0_SPRITE); + return IR_CONT; +} + +int32 Logic::fnBackPar1Sprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteStatus(decodePtr(params[0]), BGP1_SPRITE); + return IR_CONT; +} + +int32 Logic::fnForePar0Sprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteStatus(decodePtr(params[0]), FGP0_SPRITE); + return IR_CONT; +} + +int32 Logic::fnForePar1Sprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteStatus(decodePtr(params[0]), FGP1_SPRITE); + return IR_CONT; +} + +int32 Logic::fnSetPlayerActionEvent(int32 *params) { + // we want to intercept the player character and have him interact + // with an object - from script this code is the same as the mouse + // engine calls when you click on an object - here, a third party + // does the clicking IYSWIM + + // note - this routine used CUR_PLAYER_ID as the target + + // params: 0 id to interact with + + setPlayerActionEvent(CUR_PLAYER_ID, params[0]); + return IR_CONT; +} + +/** + * Set the special scroll offset variables + * + * Call when starting screens and to change the camera within screens + * + * call AFTER fnInitBackground() to override the defaults + */ + +int32 Logic::fnSetScrollCoordinate(int32 *params) { + // params: 0 feet_x value + // 1 feet_y value + + // Called feet_x and feet_y to retain intellectual compatibility with + // Sword1! + // + // feet_x & feet_y refer to the physical screen coords where the + // system will try to maintain George's feet + + ScreenInfo *screenInfo = _vm->_screen->getScreenInfo(); + + screenInfo->feet_x = params[0]; + screenInfo->feet_y = params[1]; + return IR_CONT; +} + +/** + * Stand mega at start position of anim + */ + +int32 Logic::fnStandAtAnim(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 pointer to object's mega structure + // 2 anim resource id + + _router->standAtAnim( + decodePtr(params[0]), + decodePtr(params[1]), + params[2]); + return IR_CONT; +} + +#define SCROLL_MOUSE_WIDTH 20 + +int32 Logic::fnSetScrollLeftMouse(int32 *params) { + // params: 0 pointer to object's mouse structure + + byte *ob_mouse = decodePtr(params[0]); + ScreenInfo *screenInfo = _vm->_screen->getScreenInfo(); + + // Highest priority + + ObjectMouse mouse; + + mouse.x1 = 0; + mouse.y1 = 0; + mouse.x2 = screenInfo->scroll_offset_x + SCROLL_MOUSE_WIDTH; + mouse.y2 = screenInfo->screen_deep - 1; + mouse.priority = 0; + + if (screenInfo->scroll_offset_x > 0) { + // not fully scrolled to the left + mouse.pointer = SCROLL_LEFT_MOUSE_ID; + } else { + // so the mouse area doesn't get registered + mouse.pointer = 0; + } + + mouse.write(ob_mouse); + return IR_CONT; +} + +int32 Logic::fnSetScrollRightMouse(int32 *params) { + // params: 0 pointer to object's mouse structure + + byte *ob_mouse = decodePtr(params[0]); + ScreenInfo *screenInfo = _vm->_screen->getScreenInfo(); + + // Highest priority + + ObjectMouse mouse; + + mouse.x1 = screenInfo->scroll_offset_x + _vm->_screen->getScreenWide() - SCROLL_MOUSE_WIDTH; + mouse.y1 = 0; + mouse.x2 = screenInfo->screen_wide - 1; + mouse.y2 = screenInfo->screen_deep - 1; + mouse.priority = 0; + + if (screenInfo->scroll_offset_x < screenInfo->max_scroll_offset_x) { + // not fully scrolled to the right + mouse.pointer = SCROLL_RIGHT_MOUSE_ID; + } else { + // so the mouse area doesn't get registered + mouse.pointer = 0; + } + + mouse.write(ob_mouse); + return IR_CONT; +} + +int32 Logic::fnColour(int32 *params) { + // set border colour - useful during script development + // eg. set to colour during a timer situation, then black when timed + // out + + // params 0: colour (see defines above) + +#ifdef SWORD2_DEBUG + // what colour? + switch (params[0]) { + case BLACK: + _vm->_screen->setPalette(0, 1, black, RDPAL_INSTANT); + break; + case WHITE: + _vm->_screen->setPalette(0, 1, white, RDPAL_INSTANT); + break; + case RED: + _vm->_screen->setPalette(0, 1, red, RDPAL_INSTANT); + break; + case GREEN: + _vm->_screen->setPalette(0, 1, green, RDPAL_INSTANT); + break; + case BLUE: + _vm->_screen->setPalette(0, 1, blue, RDPAL_INSTANT); + break; + } +#endif + + return IR_CONT; +} + +#ifdef SWORD2_DEBUG +#define BLACK 0 +#define WHITE 1 +#define RED 2 +#define GREEN 3 +#define BLUE 4 + +static uint8 black[4] = { 0, 0, 0, 0 }; +static uint8 white[4] = { 255, 255, 255, 0 }; +static uint8 red[4] = { 255, 0, 0, 0 }; +static uint8 green[4] = { 0, 255, 0, 0 }; +static uint8 blue[4] = { 0, 0, 255, 0 }; +#endif + +int32 Logic::fnFlash(int32 *params) { + // flash colour 0 (ie. border) - useful during script development + // eg. fnFlash(BLUE) where a text line is missed; RED when some code + // missing, etc + + // params: 0 colour to flash + +#ifdef SWORD2_DEBUG + // what colour? + switch (params[0]) { + case WHITE: + _vm->_screen->setPalette(0, 1, white, RDPAL_INSTANT); + break; + case RED: + _vm->_screen->setPalette(0, 1, red, RDPAL_INSTANT); + break; + case GREEN: + _vm->_screen->setPalette(0, 1, green, RDPAL_INSTANT); + break; + case BLUE: + _vm->_screen->setPalette(0, 1, blue, RDPAL_INSTANT); + break; + } + + // There used to be a busy-wait loop here, so I don't know how long + // the delay was meant to be. Probably doesn't matter much. + + _vm->_screen->updateDisplay(); + _vm->_system->delayMillis(250); + _vm->_screen->setPalette(0, 1, black, RDPAL_INSTANT); +#endif + + return IR_CONT; +} + +int32 Logic::fnPreFetch(int32 *params) { + // Go fetch resource in the background. + + // params: 0 resource to fetch [guess] + + return IR_CONT; +} + +/** + * Reverse of fnPassPlayerSaveData() - run script 8 of player object. + */ + +int32 Logic::fnGetPlayerSaveData(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + + byte *ob_logic = decodePtr(params[0]); + byte *ob_graph = decodePtr(params[1]); + byte *ob_mega = decodePtr(params[2]); + + // Copy from savegame buffers to player object + + memcpy(ob_logic, _saveLogic, ObjectLogic::size()); + memcpy(ob_graph, _saveGraphic, ObjectGraphic::size()); + memcpy(ob_mega, _saveMega, ObjectMega::size()); + + // Any walk-data must be cleared - the player will be set to stand if + // he was walking when saved. + + ObjectMega obMega(ob_mega); + + if (obMega.getIsWalking()) { + ObjectLogic obLogic(ob_logic); + + obMega.setIsWalking(0); + + int32 pars[3]; + + pars[0] = params[1]; // ob_graphic; + pars[1] = params[2]; // ob_mega + pars[2] = obMega.getCurDir(); + + fnStand(pars); + + // Reset looping flag (which would have been 1 during fnWalk) + obLogic.setLooping(0); + } + + return IR_CONT; +} + +/** + * Copies the 4 essential player structures into the savegame header - run + * script 7 of player object to request this. + * + * Remember, we cannot simply read a compact any longer but instead must + * request it from the object itself. + */ + +int32 Logic::fnPassPlayerSaveData(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + + // Copy from player object to savegame buffers + + memcpy(_saveLogic, decodePtr(params[0]), ObjectLogic::size()); + memcpy(_saveGraphic, decodePtr(params[1]), ObjectGraphic::size()); + memcpy(_saveMega, decodePtr(params[2]), ObjectMega::size()); + + return IR_CONT; +} + +int32 Logic::fnSendEvent(int32 *params) { + // we want to intercept the player character and have him interact + // with an object - from script + + // params: 0 id to receive event + // 1 script to run + + sendEvent(params[0], params[1]); + return IR_CONT; +} + +/** + * Add this walkgrid resource to the list of those used for routing in this + * location. Note that this is ignored if the resource is already in the list. + */ + +int32 Logic::fnAddWalkGrid(int32 *params) { + // params: 0 id of walkgrid resource + + // All objects that add walkgrids must be restarted whenever we + // re-enter a location. + + // DON'T EVER KILL GEORGE! + if (readVar(ID) != CUR_PLAYER_ID) { + // Need to call this in case it wasn't called in script! + fnAddToKillList(NULL); + } + + _router->addWalkGrid(params[0]); + fnPreLoad(params); + return IR_CONT; +} + +/** + * Remove this walkgrid resource from the list of those used for routing in + * this location. Note that this is ignored if the resource isn't actually + * in the list. + */ + +int32 Logic::fnRemoveWalkGrid(int32 *params) { + // params: 0 id of walkgrid resource + + _router->removeWalkGrid(params[0]); + return IR_CONT; +} + +// like fnCheckEventWaiting, but starts the event rather than setting RESULT +// to 1 + +int32 Logic::fnCheckForEvent(int32 *params) { + // params: none + + if (checkEventWaiting()) { + startEvent(); + return IR_TERMINATE; + } + + return IR_CONT; +} + +// combination of fnPause and fnCheckForEvent +// - ie. does a pause, but also checks for event each cycle + +int32 Logic::fnPauseForEvent(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 number of game-cycles to pause + + ObjectLogic obLogic(decodePtr(params[0])); + + if (checkEventWaiting()) { + obLogic.setLooping(0); + startEvent(); + return IR_TERMINATE; + } + + return fnPause(params); +} + +int32 Logic::fnClearEvent(int32 *params) { + // params: none + + clearEvent(readVar(ID)); + return IR_CONT; +} + +int32 Logic::fnFaceMega(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 id of target mega to face + + return _router->faceMega( + decodePtr(params[0]), + decodePtr(params[1]), + decodePtr(params[2]), + decodePtr(params[3]), + params[4]); +} + +int32 Logic::fnPlaySequence(int32 *params) { + // params: 0 pointer to null-terminated ascii filename + // 1 number of frames in the sequence, used for PSX. + + char filename[30]; + MovieTextObject *sequenceSpeechArray[MAX_SEQUENCE_TEXT_LINES + 1]; + + // The original code had some #ifdef blocks for skipping or muting the + // cutscenes - fondly described as "the biggest fudge in the history + // of computer games" - but at the very least we want to show the + // cutscene subtitles, so I removed them. + + debug(5, "fnPlaySequence(\"%s\");", (const char *)decodePtr(params[0])); + + // add the appropriate file extension & play it + + strcpy(filename, (const char *)decodePtr(params[0])); + + // Write to walkthrough file (zebug0.txt) + debug(5, "PLAYING SEQUENCE \"%s\"", filename); + + // now create the text sprites, if any + + if (_sequenceTextLines) + createSequenceSpeech(sequenceSpeechArray); + + // don't want to carry on streaming game music when smacker starts! + fnStopMusic(NULL); + + // pause sfx during sequence + _vm->_sound->pauseFx(); + + MoviePlayer player(_vm); + uint32 rv; + + if (_sequenceTextLines && !readVar(DEMO)) + rv = player.play(filename, sequenceSpeechArray, _smackerLeadIn, _smackerLeadOut); + else + rv = player.play(filename, NULL, _smackerLeadIn, _smackerLeadOut); + + // check the error return-value + if (rv) + debug(5, "MoviePlayer.play(\"%s\") returned 0x%.8x", filename, rv); + + // unpause sound fx again, in case we're staying in same location + _vm->_sound->unpauseFx(); + + _smackerLeadIn = 0; + _smackerLeadOut = 0; + + // now clear the text sprites, if any + + if (_sequenceTextLines) + clearSequenceSpeech(sequenceSpeechArray); + + // now clear the screen in case the Sequence was quitted (using ESC) + // rather than fading down to black + + _vm->_screen->clearScene(); + + // zero the entire palette in case we're about to fade up! + + byte pal[4 * 256]; + + memset(pal, 0, sizeof(pal)); + _vm->_screen->setPalette(0, 256, pal, RDPAL_INSTANT); + + debug(5, "fnPlaySequence FINISHED"); + return IR_CONT; +} + +int32 Logic::fnShadedSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteShading(decodePtr(params[0]), SHADED_SPRITE); + return IR_CONT; +} + +int32 Logic::fnUnshadedSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + _router->setSpriteShading(decodePtr(params[0]), UNSHADED_SPRITE); + return IR_CONT; +} + +int32 Logic::fnFadeUp(int32 *params) { + // params: none + + _vm->_screen->waitForFade(); + + if (_vm->_screen->getFadeStatus() == RDFADE_BLACK) + _vm->_screen->fadeUp(); + + return IR_CONT; +} + +int32 Logic::fnDisplayMsg(int32 *params) { + // Display a message to the user on the screen. + + // params: 0 Text number of message to be displayed. + + uint32 local_text = params[0] & 0xffff; + uint32 text_res = params[0] / SIZE; + + // Display message for three seconds. + + // +2 to skip the encoded text number in the first 2 chars; 3 is + // duration in seconds + + _vm->_screen->displayMsg(_vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text) + 2, 3); + _vm->_resman->closeResource(text_res); + + return IR_CONT; +} + +int32 Logic::fnSetObjectHeld(int32 *params) { + // params: 0 luggage icon to set + uint32 res = (uint32)params[0]; + + _vm->_mouse->setObjectHeld(res); + return IR_CONT; +} + +int32 Logic::fnAddSequenceText(int32 *params) { + // params: 0 text number + // 1 frame number to start the text displaying + // 2 frame number to stop the text dispalying + + assert(_sequenceTextLines < MAX_SEQUENCE_TEXT_LINES); + + _sequenceTextList[_sequenceTextLines].textNumber = params[0]; + _sequenceTextList[_sequenceTextLines].startFrame = params[1]; + _sequenceTextList[_sequenceTextLines].endFrame = params[2]; + _sequenceTextLines++; + return IR_CONT; +} + +int32 Logic::fnResetGlobals(int32 *params) { + // fnResetGlobals is used by the demo - so it can loop back & restart + // itself + + // params: none + + ScreenInfo *screenInfo = _vm->_screen->getScreenInfo(); + + byte *globals = _vm->_resman->openResource(1) + ResHeader::size(); + int32 size = _vm->_resman->fetchLen(1) - ResHeader::size(); + + debug(5, "globals size: %d", size); + + // blank each global variable + memset(globals, 0, size); + + _vm->_resman->closeResource(1); + + // all objects but george + _vm->_resman->killAllObjects(false); + + // FOR THE DEMO - FORCE THE SCROLLING TO BE RESET! + // - this is taken from fnInitBackground + + // switch on scrolling (2 means first time on screen) + screenInfo->scroll_flag = 2; + + // Used to be IR_CONT, but that's a bad idea. We may just have killed + // our own script resource -- continuing will cause a bad memory read + // access. + return IR_STOP; +} + +int32 Logic::fnSetPalette(int32 *params) { + // params: 0 resource number of palette file, or 0 if it's to be + // the palette from the current screen + + _vm->_screen->setFullPalette(params[0]); + return IR_CONT; +} + +// use this in the object's service script prior to registering the mouse area +// ie. before fnRegisterMouse or fnRegisterFrame +// - best if kept at very top of service script + +int32 Logic::fnRegisterPointerText(int32 *params) { + // params: 0 local id of text line to use as pointer text + + _vm->_mouse->registerPointerText(params[0]); + return IR_CONT; +} + +int32 Logic::fnFetchWait(int32 *params) { + // Fetches a resource in the background but prevents the script from + // continuing until the resource is in memory. + + // params: 0 resource to fetch [guess] + + return IR_CONT; +} + +int32 Logic::fnRelease(int32 *params) { + // Releases a resource from memory. Used for freeing memory for + // sprites that have just been used and will not be used again. + // Sometimes it is better to kick out a sprite straight away so that + // the memory can be used for more frequent animations. + + // params: 0 resource to release [guess] + + return IR_CONT; +} + +int32 Logic::fnPrepareMusic(int32 *params) { + // params: 1 id of music to prepare [guess] + return IR_CONT; +} + +int32 Logic::fnSoundFetch(int32 *params) { + // params: 0 id of sound to fetch [guess] + return IR_CONT; +} + +int32 Logic::fnSmackerLeadIn(int32 *params) { + // params: 0 id of lead-in music + + // ready for use in fnPlaySequence + _smackerLeadIn = params[0]; + return IR_CONT; +} + +int32 Logic::fnSmackerLeadOut(int32 *params) { + // params: 0 id of lead-out music + + // ready for use in fnPlaySequence + _smackerLeadOut = params[0]; + return IR_CONT; +} + +/** + * Stops all FX and clears the entire FX queue. + */ + +int32 Logic::fnStopAllFx(int32 *params) { + // params: none + + _vm->_sound->clearFxQueue(); + return IR_CONT; +} + +int32 Logic::fnCheckPlayerActivity(int32 *params) { + // Used to decide when to trigger music cues described as "no player + // activity for a while" + + // params: 0 threshold delay in seconds, ie. what we want to + // check the actual delay against + + uint32 seconds = (uint32)params[0]; + + _vm->_mouse->checkPlayerActivity(seconds); + return IR_CONT; +} + +int32 Logic::fnResetPlayerActivityDelay(int32 *params) { + // Use if you want to deliberately reset the "no player activity" + // counter for any reason + + // params: none + + _vm->_mouse->resetPlayerActivityDelay(); + return IR_CONT; +} + +int32 Logic::fnCheckMusicPlaying(int32 *params) { + // params: none + + // sets result to no. of seconds of current tune remaining + // or 0 if no music playing + + // in seconds, rounded up to the nearest second + writeVar(RESULT, _vm->_sound->musicTimeRemaining()); + return IR_CONT; +} + +int32 Logic::fnPlayCredits(int32 *params) { + // This function just quits the game if this is the playable demo, ie. + // credits are NOT played in the demo any more! + + // params: none + + if (readVar(DEMO)) { + _vm->closeGame(); + return IR_STOP; + } + + _vm->_screen->rollCredits(); + return IR_CONT; +} + +int32 Logic::fnSetScrollSpeedNormal(int32 *params) { + // params: none + + _vm->_screen->setScrollFraction(16); + return IR_CONT; +} + +int32 Logic::fnSetScrollSpeedSlow(int32 *params) { + // params: none + + _vm->_screen->setScrollFraction(32); + return IR_CONT; +} + +// Called from speech scripts to remove the chooser bar when it's not +// appropriate to keep it displayed + +int32 Logic::fnRemoveChooser(int32 *params) { + // params: none + + _vm->_mouse->hideMenu(RDMENU_BOTTOM); + return IR_CONT; +} + +/** + * Alter the volume and pan of a currently playing FX + */ + +int32 Logic::fnSetFxVolAndPan(int32 *params) { + // params: 0 id of fx (ie. the id returned in 'result' from + // fnPlayFx + // 1 new volume (0..16) + // 2 new pan (-16..16) + + debug(5, "fnSetFxVolAndPan(%d, %d, %d)", params[0], params[1], params[2]); + + _vm->_sound->setFxIdVolumePan(params[0], params[1], params[2]); + return IR_CONT; +} + +/** + * Alter the volume of a currently playing FX + */ + +int32 Logic::fnSetFxVol(int32 *params) { + // params: 0 id of fx (ie. the id returned in 'result' from + // fnPlayFx + // 1 new volume (0..16) + + _vm->_sound->setFxIdVolumePan(params[0], params[1]); + return IR_CONT; +} + +int32 Logic::fnRestoreGame(int32 *params) { + // params: none + return IR_CONT; +} + +int32 Logic::fnRefreshInventory(int32 *params) { + // Called from 'menu_look_or_combine' script in 'menu_master' object + // to update the menu to display a combined object while George runs + // voice-over. Note that 'object_held' must be set to the graphic of + // the combined object + + // params: none + + _vm->_mouse->refreshInventory(); + return IR_CONT; +} + +int32 Logic::fnChangeShadows(int32 *params) { + // params: none + ScreenInfo *screenInfo = _vm->_screen->getScreenInfo(); + + // if last screen was using a shading mask (see below) + if (screenInfo->mask_flag) { + uint32 rv = _vm->_screen->closeLightMask(); + if (rv) + error("Driver Error %.8x", rv); + screenInfo->mask_flag = false; + } + + return IR_CONT; +} + +} // End of namespace Sword2 |