/* Copyright (C) 1994-1998 Revolution Software Ltd. * Copyright (C) 2003-2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ */ #include "common/stdafx.h" #include "common/file.h" #include "common/system.h" #include "sword2/sword2.h" #include "sword2/defs.h" #include "sword2/console.h" #include "sword2/controls.h" #include "sword2/interpreter.h" #include "sword2/logic.h" #include "sword2/maketext.h" #include "sword2/memory.h" #include "sword2/resman.h" #include "sword2/router.h" #include "sword2/sound.h" #include "sword2/driver/animation.h" #include "sword2/driver/d_draw.h" #include "sword2/driver/render.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 *) _vm->_memory->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 return _vm->initBackground(params[0], params[1]); } /** * 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 setSpriteStatus(params[0], BACK_SPRITE); return IR_CONT; } int32 Logic::fnSortSprite(int32 *params) { // params: 0 pointer to object's graphic structure setSpriteStatus(params[0], SORT_SPRITE); return IR_CONT; } int32 Logic::fnForeSprite(int32 *params) { // params: 0 pointer to object's graphic structure setSpriteStatus(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->registerMouse((ObjectMouse *) _vm->_memory->decodePtr(params[0])); 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 // 0 means normal forward anim return animate(params, false); } int32 Logic::fnRandom(int32 *params) { // params: 0 min // 1 max _scriptVars[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 if (_scriptVars[IN_SUBJECT] == 0) { // This is the start of the new subject list. Set the default // repsonse id to zero in case we're never passed one. _defaultResponseId = 0; } if (params[0] == -1) { // Id -1 is used for setting the default response, i.e. the // response when someone uses an object on a person and he // doesn't know anything about it. See fnChoose() below. _defaultResponseId = params[1]; } else { debug(5, "fnAddSubject res %d, uid %d", params[0], params[1]); _subjectList[_scriptVars[IN_SUBJECT]].res = params[0]; _subjectList[_scriptVars[IN_SUBJECT]].ref = params[1]; _scriptVars[IN_SUBJECT]++; } 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 _scriptVars[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. uint i; _scriptVars[AUTO_SELECTED] = 0; if (_scriptVars[OBJECT_HELD]) { // The player used an object on a person. In this case it // triggered a conversation menu. Act as if the user tried to // talk to the person about that object. If the person doesn't // know anything about it, use the default response. uint32 response = _defaultResponseId; for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { if (_subjectList[i].res == _scriptVars[OBJECT_HELD]) { response = _subjectList[i].ref; break; } } // The user won't be holding the object any more, and the // conversation menu will be closed. _scriptVars[OBJECT_HELD] = 0; _scriptVars[IN_SUBJECT] = 0; return IR_CONT | (response << 3); } if (_scriptVars[CHOOSER_COUNT_FLAG] == 0 && _scriptVars[IN_SUBJECT] == 1 && _subjectList[0].res == EXIT_ICON) { // This is the first time the chooser is coming up in this // conversation, there is only one subject and that's the // EXIT icon. // // In other words, the player doesn't have anything to talk // about. Skip it. // The conversation menu will be closed. We set AUTO_SELECTED // because the speech script depends on it. _scriptVars[AUTO_SELECTED] = 1; _scriptVars[IN_SUBJECT] = 0; return IR_CONT | (_subjectList[0].ref << 3); } byte *icon; if (!_choosing) { // This is a new conversation menu. if (!_scriptVars[IN_SUBJECT]) error("fnChoose with no subjects"); for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { icon = _vm->_resman->openResource(_subjectList[i].res) + sizeof(StandardHeader) + RDMENU_ICONWIDE * RDMENU_ICONDEEP; _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, i, icon); _vm->_resman->closeResource(_subjectList[i].res); } for (; i < 15; i++) _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, (uint8) i, NULL); _vm->_graphics->showMenu(RDMENU_BOTTOM); _vm->setMouse(NORMAL_MOUSE_ID); _choosing = true; return IR_REPEAT; } // The menu is there - we're just waiting for a click. We only care // about left clicks. MouseEvent *me = _vm->mouseEvent(); if (!me || !(me->buttons & RD_LEFTBUTTONDOWN) || _vm->_mouseY < 400) return IR_REPEAT; // Check for click on a menu. int hit = _vm->menuClick(_scriptVars[IN_SUBJECT]); if (hit < 0) return IR_REPEAT; // Hilight the clicked icon by greying the others. for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { if ((int) i != hit) { icon = _vm->_resman->openResource(_subjectList[i].res) + sizeof(StandardHeader); _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, i, icon); _vm->_resman->closeResource(_subjectList[i].res); } } // For non-speech scripts that manually call the chooser _scriptVars[RESULT] = _subjectList[hit].res; // The conversation menu will be closed _choosing = false; _scriptVars[IN_SUBJECT] = 0; _vm->setMouse(0); return IR_CONT | (_subjectList[hit].ref << 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) ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); ObjectGraphic *ob_graph = (ObjectGraphic *) _vm->_memory->decodePtr(params[1]); ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); int16 target_x = (int16) params[4]; int16 target_y = (int16) params[5]; uint8 target_dir = (uint8) params[6]; ObjectWalkdata *ob_walkdata; // If this is the start of the walk, calculate the route. if (!ob_logic->looping) { // If we're already there, don't even bother allocating // memory and calling the router, just quit back & continue // the script! This avoids an embarassing mega stand frame // appearing for one cycle when we're already in position for // an anim eg. repeatedly clicking on same object to repeat // an anim - no mega frame will appear in between runs of the // anim. if (ob_mega->feet_x == target_x && ob_mega->feet_y == target_y && ob_mega->current_dir == target_dir) { _scriptVars[RESULT] = 0; return IR_CONT; } assert(params[6] >= 0 && params[6] <= 8); ob_walkdata = (ObjectWalkdata *) _vm->_memory->decodePtr(params[3]); ob_mega->walk_pc = 0; // Set up mem for _walkData in route_slots[] & set mega's // 'route_slot_id' accordingly _router->allocateRouteMem(); int32 route = _router->routeFinder(ob_mega, ob_walkdata, target_x, target_y, target_dir); // 0 = can't make route to target // 1 = created route // 2 = zero route but may need to turn if (route == 1 || route == 2) { // so script fnWalk loop continues until end of // walk-anim ob_logic->looping = 1; // need to animate the route now, so don't set result // or return yet! // started walk ob_mega->currently_walking = 1; // (see fnGetPlayerSaveData() in save_rest.cpp } else { _router->freeRouteMem(); _scriptVars[RESULT] = 1; return IR_CONT; } // Walk is about to start, so set the mega's graphic resource ob_graph->anim_resource = ob_mega->megaset_res; } else if (_scriptVars[EXIT_FADING] && _vm->_graphics->getFadeStatus() == RDFADE_BLACK) { // Double clicked an exit so quit the walk when screen is black // ok, thats it - back to script and change screen ob_logic->looping = 0; _router->freeRouteMem(); // Must clear in-case on the new screen there's a walk // instruction (which would get cut short) _scriptVars[EXIT_CLICK_ID] = 0; // finished walk ob_mega->currently_walking = 0; // see fnGetPlayerSaveData() in save_rest.cpp _scriptVars[RESULT] = 0; // continue the script so that RESULT can be checked! return IR_CONT; } // get pointer to walkanim & current frame position WalkData *walkAnim = _router->getRouteMem(); int32 walk_pc = ob_mega->walk_pc; // If stopping the walk early, overwrite the next step with a // slow-out, then finish if (checkEventWaiting()) { if (walkAnim[walk_pc].step == 0 && walkAnim[walk_pc + 1].step == 1) { // At the beginning of a step ob_walkdata = (ObjectWalkdata *) _vm->_memory->decodePtr(params[3]); _router->earlySlowOut(ob_mega, ob_walkdata); } } // Get new frame of walk ob_graph->anim_pc = walkAnim[walk_pc].frame; ob_mega->current_dir = walkAnim[walk_pc].dir; ob_mega->feet_x = walkAnim[walk_pc].x; ob_mega->feet_y = walkAnim[walk_pc].y; // Check if NEXT frame is in fact the end-marker of the walk sequence // so we can return to script just as the final (stand) frame of the // walk is set - so that if followed by an anim, the anim's first // frame replaces the final stand-frame of the walk (see below) // '512' is end-marker if (walkAnim[walk_pc + 1].frame == 512) { ob_logic->looping = 0; _router->freeRouteMem(); // finished walk ob_mega->currently_walking = 0; // (see fnGetPlayerSaveData() in save_rest.cpp // if George's walk has been interrupted to run a new action // script for instance or Nico's walk has been interrupted by // player clicking on her to talk // There used to be code here for checking if two megas were // colliding, but that code had been commented out, and it // was only run if a function that always returned zero // returned non-zero. if (checkEventWaiting()) { startEvent(); _scriptVars[RESULT] = 1; return IR_TERMINATE; } else { _scriptVars[RESULT] = 0; // CONTINUE the script so that RESULT can be checked! // Also, if an anim command follows the fnWalk command, // the 1st frame of the anim (which is always a stand // frame itself) can replace the final stand frame of // the walk, to hide the slight difference between the // shrinking on the mega frames and the pre-shrunk anim // start-frame. return IR_CONT; } } // Increment the walkanim frame number and come back next cycle ob_mega->walk_pc++; return IR_REPEAT; } /** * 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 int32 pars[7]; // Walkdata is needed for earlySlowOut if player clicks elsewhere // during the walk. pars[0] = params[0]; pars[1] = params[1]; pars[2] = params[2]; pars[3] = params[3]; ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); // If this is the start of the walk, read anim file to get start coords if (!ob_logic->looping) { byte *anim_file = _vm->_resman->openResource(params[4]); AnimHeader *anim_head = _vm->fetchAnimHeader( anim_file ); pars[4] = anim_head->feetStartX; pars[5] = anim_head->feetStartY; pars[6] = anim_head->feetStartDir; _vm->_resman->closeResource(params[4]); // If start coords not yet set in anim header, use the standby // coords (which should be set beforehand in the script). if (pars[4] == 0 && pars[5] == 0) { byte buf[NAME_LEN]; pars[4] = _standbyX; pars[5] = _standbyY; pars[6] = _standbyDir; debug(3, "WARNING: fnWalkToAnim(%s) used standby coords", _vm->fetchObjectName(params[4], buf)); } assert(pars[6] >= 0 && pars[6] <= 7); } return fnWalk(pars); } /** * Turn mega to the specified direction. Just needs to call fnWalk() with * current feet coords, so router can produce anim of turn frames. */ 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 int32 pars[7]; pars[0] = params[0]; pars[1] = params[1]; pars[2] = params[2]; pars[3] = params[3]; ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); // If this is the start of the turn, get the mega's current feet // coords + the required direction if (!ob_logic->looping) { assert(params[4] >= 0 && params[4] <= 7); ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); pars[4] = ob_mega->feet_x; pars[5] = ob_mega->feet_y; pars[6] = params[4]; } return fnWalk(pars); } /** * 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 assert(params[4] >= 0 && params[4] <= 7); ObjectGraphic *ob_graph = (ObjectGraphic *) _vm->_memory->decodePtr(params[0]); ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[1]); // set up the stand frame & set the mega's new direction ob_mega->feet_x = params[2]; ob_mega->feet_y = params[3]; ob_mega->current_dir = params[4]; // mega-set animation file ob_graph->anim_resource = ob_mega->megaset_res; // dir + first stand frame (always frame 96) ob_graph->anim_pc = params[4] + 96; return IR_CONT; } /** * Stand mega into the specified direction at current feet coords. * Just needs to call fnStandAt() 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 ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[1]); int32 pars[5]; pars[0] = params[0]; pars[1] = params[1]; pars[2] = ob_mega->feet_x; pars[3] = ob_mega->feet_y; pars[4] = params[2]; return fnStandAt(pars); } /** * 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 byte *anim_file = _vm->_resman->openResource(params[2]); AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); int32 pars[5]; pars[0] = params[0]; pars[1] = params[1]; pars[2] = anim_head->feetEndX; pars[3] = anim_head->feetEndY; pars[4] = anim_head->feetEndDir; // If start coords not available either use the standby coords (which // should be set beforehand in the script) if (pars[2] == 0 && pars[3] == 0) { byte buf[NAME_LEN]; pars[2] = _standbyX; pars[3] = _standbyY; pars[4] = _standbyDir; debug(3, "WARNING: fnStandAfterAnim(%s) used standby coords", _vm->fetchObjectName(params[2], buf)); } assert(pars[4] >= 0 && pars[4] <= 7); _vm->_resman->closeResource(params[2]); return fnStandAt(pars); } 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 *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); if (ob_logic->looping == 0) { ob_logic->looping = 1; ob_logic->pause = params[1]; } if (ob_logic->pause) { ob_logic->pause--; return IR_REPEAT; } ob_logic->looping = 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 // 0 means normal forward anim return megaTableAnimate(params, false); } int32 Logic::fnAddMenuObject(int32 *params) { // params: 0 pointer to a MenuObject structure to copy down _vm->addMenuObject((MenuObject *) _vm->_memory->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 if (_scriptVars[TALK_FLAG] == 0) { // See fnChooser & speech scripts _scriptVars[CHOOSER_COUNT_FLAG] = 0; } fnNoHuman(params); return IR_CONT; } /** * End a conversation. */ int32 Logic::fnEndConversation(int32 *params) { // params: none _vm->_graphics->hideMenu(RDMENU_BOTTOM); if (_vm->_mouseY > 399) { // Will wait for cursor to move off the bottom menu _vm->_mouseMode = MOUSE_holding; } // In case DC forgets _scriptVars[TALK_FLAG] = 0; 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); StandardHeader *head = (StandardHeader *) anim_file; assert(head->fileType == ANIMATION_FILE); // set up pointer to the animation header AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); // set up anim resource in graphic object ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->decodePtr(params[0]); ob_graphic->anim_resource = res; ob_graphic->anim_pc = params[2] ? anim_head->noAnimFrames - 1 : 0; // Close the anim file and drop out of script _vm->_resman->closeResource(ob_graphic->anim_resource); 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 *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); int32 pars[2]; if (ob_logic->looping == 0) { pars[0] = params[1]; pars[1] = params[2]; fnRandom(pars); pars[1] = _scriptVars[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 return _vm->registerFrame(params); } int32 Logic::fnNoSprite(int32 *params) { // params: 0 pointer to object's graphic structure setSpriteStatus(params[0], NO_SPRITE); return IR_CONT; } int32 Logic::fnSendSync(int32 *params) { // params: 0 sync's recipient // 1 sync value for (int i = 0; i < MAX_syncs; i++) { if (_syncList[i].id == 0) { debug(5, "%d sends sync %d to %d", _scriptVars[ID], params[1], params[0]); _syncList[i].id = params[0]; _syncList[i].sync = params[1]; return IR_CONT; } } // The original code didn't even check for this condition, so maybe // it should be a fatal error? warning("No free sync slot"); return IR_CONT; } int32 Logic::fnUpdatePlayerStats(int32 *params) { // engine needs to know certain info about the player // params: 0 pointer to mega structure ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[0]); _vm->_thisScreen.player_feet_x = ob_mega->feet_x; _vm->_thisScreen.player_feet_y = ob_mega->feet_y; // for the script _scriptVars[PLAYER_FEET_X] = ob_mega->feet_x; _scriptVars[PLAYER_FEET_Y] = ob_mega->feet_y; _scriptVars[PLAYER_CUR_DIR] = ob_mega->current_dir; _scriptVars[SCROLL_OFFSET_X] = _vm->_thisScreen.scroll_offset_x; debug(5, "fnUpdatePlayerStats: %d %d", ob_mega->feet_x, ob_mega->feet_y); 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 ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); // floor is always lowest priority ob_mouse->x1 = 0; ob_mouse->y1 = 0; ob_mouse->x2 = _vm->_thisScreen.screen_wide - 1; ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; ob_mouse->priority = 9; ob_mouse->pointer = NORMAL_MOUSE_ID; return IR_CONT; } int32 Logic::fnPassMega(int32 *params) { // makes an engine local copy of passed graphic_structure and // 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, _vm->_memory->decodePtr(params[0]), sizeof(ObjectMega)); return IR_CONT; } /** * Turn mega to face point (x,y) on the floor * Just needs to call fnWalk() with current feet coords & direction computed * by whatTarget() */ 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 int32 pars[7]; pars[0] = params[0]; pars[1] = params[1]; pars[2] = params[2]; pars[3] = params[3]; ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); // If this is the start of the turn, get the mega's current feet // coords + the required direction if (!ob_logic->looping) { ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); pars[4] = ob_mega->feet_x; pars[5] = ob_mega->feet_y; pars[6] = whatTarget(ob_mega->feet_x, ob_mega->feet_y, params[4], params[5]); } return fnWalk(pars); } /** * 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->noHuman(); _vm->clearPointerText(); // must be normal mouse situation or a largely neutral situation - // special menus use noHuman // dont hide menu in conversations if (_scriptVars[TALK_FLAG] == 0) _vm->_graphics->hideMenu(RDMENU_BOTTOM); if (_vm->_mouseMode == MOUSE_system_menu) { // close menu _vm->_mouseMode = MOUSE_normal; _vm->_graphics->hideMenu(RDMENU_TOP); } return IR_CONT; } int32 Logic::fnAddHuman(int32 *params) { // params: none // for logic scripts _scriptVars[MOUSE_AVAILABLE] = 1; // off if (_vm->_mouseStatus) { _vm->_mouseStatus = false; // on _vm->_mouseTouching = 1; // forces engine to choose a cursor } // clear this to reset no-second-click system _scriptVars[CLICKED_ID] = 0; // this is now done outside the OBJECT_HELD check in case it's set to // zero before now! // unlock the mouse from possible large object lock situtations - see // syphon in rm 3 _vm->_mouseModeLocked = false; if (_scriptVars[OBJECT_HELD]) { // was dragging something around // need to clear this again _scriptVars[OBJECT_HELD] = 0; // and these may also need clearing, just in case _vm->_examiningMenuIcon = false; Logic::_scriptVars[COMBINE_BASE] = 0; _vm->setLuggage(0); } // if mouse is over menu area if (_vm->_mouseY > 399) { if (_vm->_mouseMode != MOUSE_holding) { // VITAL - reset things & rebuild the menu _vm->_mouseMode = MOUSE_normal; _vm->setMouse(NORMAL_MOUSE_ID); } else _vm->setMouse(NORMAL_MOUSE_ID); } // enabled/disabled from console; status printed with on-screen debug // info if (_vm->_debugger->_testingSnR) { uint8 black[4] = { 0, 0, 0, 0 }; uint8 white[4] = { 255, 255, 255, 0 }; // testing logic scripts by simulating an instant Save & // Restore _vm->_graphics->setPalette(0, 1, white, RDPAL_INSTANT); // stops all fx & clears the queue - eg. when leaving a // location _vm->_sound->clearFxQueue(); // Trash all object resources so they load in fresh & restart // their logic scripts _vm->_resman->killAllObjects(false); _vm->_graphics->setPalette(0, 1, black, RDPAL_INSTANT); } return IR_CONT; } /** * Wait for a target to become waiting, i.e. not busy. */ int32 Logic::fnWeWait(int32 *params) { // params: 0 target StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[0]); assert(head->fileType == GAME_OBJECT); // Run the target's get-speech-state script int32 target = params[0]; char *raw_script_ad = (char *) head; uint32 null_pc = 5; runScript(raw_script_ad, raw_script_ad, &null_pc); _vm->_resman->closeResource(target); if (_scriptVars[RESULT] == 0) { // The target is busy. Try again. _vm->_debugger->_speechScriptWaiting = target; 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 StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[1]); assert(head->fileType == GAME_OBJECT); // Run the target's get-speech-state script int32 target = params[1]; char *raw_script_ad = (char *) head; uint32 null_pc = 5; runScript(raw_script_ad, raw_script_ad, &null_pc); _vm->_resman->closeResource(target); ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); if (_scriptVars[RESULT] == 1 && !_scriptVars[INS_COMMAND] && ob_logic->looping == 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", target); _vm->_debugger->_speechScriptWaiting = target; ob_logic->looping = 1; _scriptVars[SPEECH_ID] = params[1]; _scriptVars[INS_COMMAND] = params[2]; _scriptVars[INS1] = params[3]; _scriptVars[INS2] = params[4]; _scriptVars[INS3] = params[5]; _scriptVars[INS4] = params[6]; _scriptVars[INS5] = params[7]; return IR_REPEAT; } if (ob_logic->looping == 0) { // The command has not been sent yet. Keep waiting. _vm->_debugger->_speechScriptWaiting = target; return IR_REPEAT; } if (_scriptVars[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", target); _vm->_debugger->_speechScriptWaiting = target; return IR_REPEAT; } debug(5, "fnTheyDoWeWait: %d finished", target); ob_logic->looping = 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 StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[0]); assert (head->fileType == GAME_OBJECT); // Run the target's get-speech-state script int32 target = params[0]; char *raw_script_ad = (char *) head; uint32 null_pc = 5; runScript(raw_script_ad, raw_script_ad, &null_pc); _vm->_resman->closeResource(target); if (_scriptVars[RESULT] == 1 && !_scriptVars[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", target); _vm->_debugger->_speechScriptWaiting = 0; _scriptVars[SPEECH_ID] = params[0]; _scriptVars[INS_COMMAND] = params[1]; _scriptVars[INS1] = params[2]; _scriptVars[INS2] = params[3]; _scriptVars[INS3] = params[4]; _scriptVars[INS4] = params[5]; _scriptVars[INS5] = params[6]; return IR_CONT; } // The target is busy. Come back again next cycle. _vm->_debugger->_speechScriptWaiting = target; 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 distance int32 pars[7]; pars[0] = params[0]; pars[1] = params[1]; pars[2] = params[2]; pars[3] = params[3]; ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); // If this is the start of the walk, calculate the route. if (!ob_logic->looping) { StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[4]); assert(head->fileType == GAME_OBJECT); // Call the base script. This is the graphic/mouse service // call, and will set _engineMega to the ObjectMega of mega we // want to route to. char *raw_script_ad = (char *) head; uint32 null_pc = 3; runScript(raw_script_ad, raw_script_ad, &null_pc); _vm->_resman->closeResource(params[4]); // Stand exactly beside the mega, ie. at same y-coord pars[5] = _engineMega.feet_y; ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); // Apply scale factor to walk distance. Ay+B gives 256 * scale // ie. 256 * 256 * true_scale for even better accuracy, ie. // scale = (Ay + B) / 256 int scale = (ob_mega->scale_a * ob_mega->feet_y + ob_mega->scale_b) / 256; int mega_separation = (params[5] * scale) / 256; debug(4, "Target is at (%d, %d), separation %d", _engineMega.feet_x, _engineMega.feet_y, mega_separation); if (_engineMega.feet_x < ob_mega->feet_x) { // Target is left of us, so aim to stand to their // right. Face down_left pars[4] = _engineMega.feet_x + mega_separation; pars[6] = 5; } else { // Ok, must be right of us so aim to stand to their // left. Face down_right. pars[4] = _engineMega.feet_x - mega_separation; pars[6] = 3; } } return fnWalk(pars); } 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->_graphics->getFadeStatus() == RDFADE_NONE) _vm->_graphics->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 *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[S_OB_LOGIC]); ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->decodePtr(params[S_OB_GRAPHIC]); // FIRST TIME ONLY: create the text, load the wav, set up the anim, // etc. if (!ob_logic->looping) { // 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->_gui->_subtitles && !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 (_scriptVars[SYSTEM_TESTING_TEXT]) { if (!_vm->_resman->checkValid(text_res)) { // Not a valid resource number - invalid (null // resource) _scriptVars[RESULT] = 1; return IR_CONT; } StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(text_res); if (head->fileType != TEXT_FILE) { // Invalid - not a text resource _vm->_resman->closeResource(text_res); _scriptVars[RESULT] = 1; return IR_CONT; } if (!_vm->checkTextLine((byte *) head, local_text)) { // Line number out of range _vm->_resman->closeResource(text_res); _scriptVars[RESULT] = 2; return IR_CONT; } _vm->_resman->closeResource(text_res); _scriptVars[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 (_scriptVars[SYSTEM_TESTING_TEXT]) { // If actor number is 0 and text line is just a 'dash' // character if (_officialTextNumber == 0 && text[2] == '-' && text[3] == 0) { _scriptVars[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. ob_logic->looping = 1; _leftClickDelay = 6; _rightClickDelay = 3; if (_scriptVars[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->fetchObjectName(_scriptVars[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 *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[S_OB_MEGA]); int32 *anim_table = (int32 *) _vm->_memory->decodePtr(params[S_DIR_TABLE]); _animId = anim_table[ob_mega->current_dir]; } else { // No animation choosen _animId = 0; } if (_animId) { // Set the talker's graphic to the first frame of this // speech anim for now. _speechAnimType = _scriptVars[SPEECHANIMFLAG]; ob_graphic->anim_resource = _animId; ob_graphic->anim_pc = 0; } // Default back to looped lip synced anims. _scriptVars[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->_gui->_subtitles || !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. ob_graphic->anim_pc++; byte *anim_file = _vm->_resman->openResource(ob_graphic->anim_resource); AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); if (!_speechAnimType) { // ANIM IS TO BE LIP-SYNC'ED & REPEATING if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames)) { // End of animation - restart from frame 0 ob_graphic->anim_pc = 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). ob_graphic->anim_pc = 0; } } else { // ANIM IS TO PLAY ONCE ONLY if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames) - 1) { // Reached the last frame of the anim. Hold // anim on this last frame _animId = 0; } } _vm->_resman->closeResource(ob_graphic->anim_resource); } 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 // So that we can go to the options panel while text & speech is // being tested if (_scriptVars[SYSTEM_TESTING_TEXT] == 0 || _vm->_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 (_scriptVars[SYSTEM_TESTING_TEXT]) { // and RB used to click past text if (me->buttons & RD_RIGHTBUTTONDOWN) { // then we want the previous line again _scriptVars[SYSTEM_WANT_PREVIOUS_LINE] = 1; } else { // LB just want next line again _scriptVars[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; ob_graphic->anim_pc = 0; } speechRunning = false; // no longer in a script function loop ob_logic->looping = 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; _scriptVars[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 */ #define LEVEL (_curObjectHub->logic_level) int32 Logic::fnTotalRestart(int32 *params) { // mega runs this to restart its base logic again - like being cached // in again // params: none LEVEL = 0; _curObjectHub->script_pc[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 *ob_speech = (ObjectSpeech *) _vm->_memory->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 (ob_speech->command) { 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] = ob_speech->ins1; // encoded text number pars[5] = ob_speech->ins2; // wav res id pars[6] = ob_speech->ins3; // anim res id pars[7] = ob_speech->ins4; // anim table res id pars[8] = ob_speech->ins5; // animation mode - 0 lip synced, 1 just straight animation if (fnISpeak(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 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] = ob_speech->ins1; // direction to turn to if (fnTurn(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 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] = ob_speech->ins1; // target if (fnFaceMega(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 1; } return IR_REPEAT; case INS_anim: pars[0] = params[2]; // ob_logic pars[1] = params[0]; // ob_graphic pars[2] = ob_speech->ins1; // anim res if (fnAnim(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 1; } return IR_REPEAT; case INS_reverse_anim: pars[0] = params[2]; // ob_logic pars[1] = params[0]; // ob_graphic pars[2] = ob_speech->ins1; // anim res if (fnReverseAnim(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 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] = ob_speech->ins1; // pointer to anim table if (fnMegaTableAnim(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 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] = ob_speech->ins1; // pointer to anim table if (fnReverseMegaTableAnim(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 1; } return IR_REPEAT; case INS_no_sprite: fnNoSprite(params); // ob_graphic ob_speech->command = 0; ob_speech->wait_state = 1; return IR_REPEAT ; case INS_sort: fnSortSprite(params); // ob_graphic ob_speech->command = 0; ob_speech->wait_state = 1; return IR_REPEAT; case INS_foreground: fnForeSprite(params); // ob_graphic ob_speech->command = 0; ob_speech->wait_state = 1; return IR_REPEAT; case INS_background: fnBackSprite(params); // ob_graphic ob_speech->command = 0; ob_speech->wait_state = 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] = ob_speech->ins1; // target x pars[5] = ob_speech->ins2; // target y pars[6] = ob_speech->ins3; // target direction if (fnWalk(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 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] = ob_speech->ins1; // anim resource if (fnWalkToAnim(pars) != IR_REPEAT) { ob_speech->command = 0; ob_speech->wait_state = 1; } return IR_REPEAT; case INS_stand_after_anim: pars[0] = params[0]; // ob_graphic pars[1] = params[3]; // ob_mega pars[2] = ob_speech->ins1; // anim resource fnStandAfterAnim(pars); ob_speech->command = 0; ob_speech->wait_state = 1; return IR_REPEAT; case INS_set_frame: pars[0] = params[0]; // ob_graphic pars[1] = ob_speech->ins1; // anim_resource pars[2] = ob_speech->ins2; // FIRST_FRAME or LAST_FRAME fnSetFrame(pars); ob_speech->command = 0; ob_speech->wait_state = 1; return IR_REPEAT; case INS_quit: // That's it - we're finished with this ob_speech->command = 0; // ob_speech->wait_state = 0; return IR_CONT; default: // Unimplemented command - just cancel ob_speech->command = 0; ob_speech->wait_state = 1; break; } if (_scriptVars[SPEECH_ID] == _scriptVars[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", _scriptVars[INS_COMMAND]); _scriptVars[SPEECH_ID] = 0; ob_speech->command = _scriptVars[INS_COMMAND]; ob_speech->ins1 = _scriptVars[INS1]; ob_speech->ins2 = _scriptVars[INS2]; ob_speech->ins3 = _scriptVars[INS3]; ob_speech->ins4 = _scriptVars[INS4]; ob_speech->ins5 = _scriptVars[INS5]; ob_speech->wait_state = 0; _scriptVars[INS_COMMAND] = 0; } else { // No new command. We could run a blink anim (or // something) here. ob_speech->wait_state = 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 *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[0]); ob_mega->scale_a = params[1]; ob_mega->scale_b = params[2]; return IR_CONT; } int32 Logic::fnStartEvent(int32 *params) { // params: none startEvent(); return IR_TERMINATE; } int32 Logic::fnCheckEventWaiting(int32 *params) { // params: none _scriptVars[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 StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[1]); assert(head->fileType == GAME_OBJECT); ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); if (!ob_logic->looping) { // This is the first time, so set up the time-out. ob_logic->looping = params[2]; } // Run the target's get-speech-state script int32 target = params[1]; char *raw_script_ad = (char *) head; uint32 null_pc = 5; runScript(raw_script_ad, raw_script_ad, &null_pc); _vm->_resman->closeResource(target); if (_scriptVars[RESULT] == 1) { // The target is waiting, i.e. not busy _vm->_debugger->_speechScriptWaiting = 0; ob_logic->looping = 0; _scriptVars[RESULT] = 0; return IR_CONT; } ob_logic->looping--; if (!ob_logic->looping) { // Time's up. debug(5, "fnTimedWait: Timed out waiting for %d", target); _vm->_debugger->_speechScriptWaiting = 0; // Clear the event that hasn't been picked up - in theory, // none of this should ever happen. killAllIdsEvents(target); _scriptVars[RESULT] = 1; return IR_CONT; } // Target is busy. Keep trying. _vm->_debugger->_speechScriptWaiting = target; 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(); 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 *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[0]); ob_mega->megaset_res = 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 _scriptVars[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(); _scriptVars[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", _scriptVars[ID]); int slot = getSync(); if (slot == -1) return IR_REPEAT; debug(5, "fnWaitSync: %d got sync %d", _scriptVars[ID], _syncList[slot].sync); _scriptVars[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 // 1 means reverse anim return megaTableAnimate(params, 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 // 1 means reverse anim return animate(params, 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 // DON'T EVER KILL GEORGE! if (_scriptVars[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] == _scriptVars[ID]) return IR_CONT; } assert(_kills < OBJECT_KILL_LIST_SIZE); // no room at the inn _objectKillList[_kills++] = _scriptVars[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) assert(params[2] >= 0 && params[2] <= 7); _standbyX = (int16) params[0]; _standbyY = (int16) params[1]; _standbyDir = (uint8) params[2]; return IR_CONT; } int32 Logic::fnBackPar0Sprite(int32 *params) { // params: 0 pointer to object's graphic structure setSpriteStatus(params[0], BGP0_SPRITE); return IR_CONT; } int32 Logic::fnBackPar1Sprite(int32 *params) { // params: 0 pointer to object's graphic structure setSpriteStatus(params[0], BGP1_SPRITE); return IR_CONT; } int32 Logic::fnForePar0Sprite(int32 *params) { // params: 0 pointer to object's graphic structure setSpriteStatus(params[0], FGP0_SPRITE); return IR_CONT; } int32 Logic::fnForePar1Sprite(int32 *params) { // params: 0 pointer to object's graphic structure setSpriteStatus(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 _vm->_thisScreen.feet_x = params[0]; _vm->_thisScreen.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 byte *anim_file = _vm->_resman->openResource(params[2]); AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); int32 pars[5]; pars[0] = params[0]; pars[1] = params[1]; pars[2] = anim_head->feetStartX; pars[3] = anim_head->feetStartY; pars[4] = anim_head->feetStartDir; // If start coords not available use the standby coords (which should // be set beforehand in the script) if (pars[2] == 0 && pars[3] == 0) { byte buf[NAME_LEN]; pars[2] = _standbyX; pars[3] = _standbyY; pars[4] = _standbyDir; debug(3, "WARNING: fnStandAtAnim(%s) used standby coords", _vm->fetchObjectName(params[2], buf)); } assert(pars[4] >= 0 && pars[4] <= 7); _vm->_resman->closeResource(params[2]); return fnStandAt(pars); } #define SCROLL_MOUSE_WIDTH 20 int32 Logic::fnSetScrollLeftMouse(int32 *params) { // params: 0 pointer to object's mouse structure ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); // Highest priority ob_mouse->x1 = 0; ob_mouse->y1 = 0; ob_mouse->x2 = _vm->_thisScreen.scroll_offset_x + SCROLL_MOUSE_WIDTH; ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; ob_mouse->priority = 0; if (_vm->_thisScreen.scroll_offset_x > 0) { // not fully scrolled to the left ob_mouse->pointer = SCROLL_LEFT_MOUSE_ID; } else { // so the mouse area doesn't get registered ob_mouse->pointer = 0; } return IR_CONT; } int32 Logic::fnSetScrollRightMouse(int32 *params) { // params: 0 pointer to object's mouse structure ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); // Highest priority ob_mouse->x1 = _vm->_thisScreen.scroll_offset_x + _vm->_graphics->_screenWide - SCROLL_MOUSE_WIDTH; ob_mouse->y1 = 0; ob_mouse->x2 = _vm->_thisScreen.screen_wide - 1; ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; ob_mouse->priority = 0; if (_vm->_thisScreen.scroll_offset_x < _vm->_thisScreen.max_scroll_offset_x) { // not fully scrolled to the right ob_mouse->pointer = SCROLL_RIGHT_MOUSE_ID; } else { // so the mouse area doesn't get registered ob_mouse->pointer = 0; } 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->_graphics->setPalette(0, 1, black, RDPAL_INSTANT); break; case WHITE: _vm->_graphics->setPalette(0, 1, white, RDPAL_INSTANT); break; case RED: _vm->_graphics->setPalette(0, 1, red, RDPAL_INSTANT); break; case GREEN: _vm->_graphics->setPalette(0, 1, green, RDPAL_INSTANT); break; case BLUE: _vm->_graphics->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->_graphics->setPalette(0, 1, white, RDPAL_INSTANT); break; case RED: _vm->_graphics->setPalette(0, 1, red, RDPAL_INSTANT); break; case GREEN: _vm->_graphics->setPalette(0, 1, green, RDPAL_INSTANT); break; case BLUE: _vm->_graphics->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->_graphics->updateDisplay(); _vm->_system->delayMillis(250); _vm->_graphics->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 *logic_ptr = _vm->_memory->decodePtr(params[0]); byte *graphic_ptr = _vm->_memory->decodePtr(params[1]); byte *mega_ptr = _vm->_memory->decodePtr(params[2]); // Copy from savegame header to player object memcpy(logic_ptr, &_vm->_saveGameHeader.logic, sizeof(ObjectLogic)); memcpy(graphic_ptr, &_vm->_saveGameHeader.graphic, sizeof(ObjectGraphic)); memcpy(mega_ptr, &_vm->_saveGameHeader.mega, sizeof(ObjectMega)); // Any walk-data must be cleared - the player will be set to stand if // he was walking when saved. ObjectMega *ob_mega = (ObjectMega *) mega_ptr; if (ob_mega->currently_walking) { ob_mega->currently_walking = 0; int32 pars[3]; pars[0] = params[1]; // ob_graphic; pars[1] = params[2]; // ob_mega pars[2] = ob_mega->current_dir; fnStand(pars); // Reset looping flag (which would have been 1 during fnWalk) ObjectLogic *ob_logic = (ObjectLogic *) logic_ptr; ob_logic->looping = 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 header memcpy(&_vm->_saveGameHeader.logic, _vm->_memory->decodePtr(params[0]), sizeof(ObjectLogic)); memcpy(&_vm->_saveGameHeader.graphic, _vm->_memory->decodePtr(params[1]), sizeof(ObjectGraphic)); memcpy(&_vm->_saveGameHeader.mega, _vm->_memory->decodePtr(params[2]), sizeof(ObjectMega)); 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 (_scriptVars[ID] != 8) { // 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 *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); if (checkEventWaiting()) { ob_logic->looping = 0; startEvent(); return IR_TERMINATE; } return fnPause(params); } int32 Logic::fnClearEvent(int32 *params) { // params: none clearEvent(_scriptVars[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 int32 pars[7]; pars[0] = params[0]; pars[1] = params[1]; pars[2] = params[2]; pars[3] = params[3]; ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); // If this is the start of the walk, decide where to walk to. if (!ob_logic->looping) { StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[4]); assert(head->fileType == GAME_OBJECT); // Call the base script. This is the graphic/mouse service // call, and will set _engineMega to the ObjectMega of mega we // want to turn to face. char *raw_script_ad = (char *) head; uint32 null_pc = 3; runScript(raw_script_ad, raw_script_ad, &null_pc); _vm->_resman->closeResource(params[4]); ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); pars[3] = params[3]; pars[4] = ob_mega->feet_x; pars[5] = ob_mega->feet_y; pars[6] = whatTarget(ob_mega->feet_x, ob_mega->feet_y, _engineMega.feet_x, _engineMega.feet_y); } return fnWalk(pars); } 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 *) _vm->_memory->decodePtr(params[0])); // add the appropriate file extension & play it strcpy(filename, (const char *) _vm->_memory->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 && !_scriptVars[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->_graphics->clearScene(); // zero the entire palette in case we're about to fade up! byte pal[4 * 256]; memset(pal, 0, sizeof(pal)); _vm->_graphics->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 setSpriteShading(params[0], SHADED_SPRITE); return IR_CONT; } int32 Logic::fnUnshadedSprite(int32 *params) { // params: 0 pointer to object's graphic structure setSpriteShading(params[0], UNSHADED_SPRITE); return IR_CONT; } int32 Logic::fnFadeUp(int32 *params) { // params: none _vm->_graphics->waitForFade(); if (_vm->_graphics->getFadeStatus() == RDFADE_BLACK) _vm->_graphics->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->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 _vm->setLuggage(params[0]); _scriptVars[OBJECT_HELD] = params[0]; _vm->_currentLuggageResource = params[0]; // mode locked - no menu available _vm->_mouseModeLocked = true; 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 int32 size; uint32 *globals; size = _vm->_resman->fetchLen(1); size -= sizeof(StandardHeader); debug(5, "globals size: %d", size); globals = (uint32 *) ((byte *) _vm->_resman->openResource(1) + sizeof(StandardHeader)); // 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) _vm->_thisScreen.scroll_flag = 2; return IR_CONT; } 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->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 assert(_vm->_curMouse < TOTAL_mouse_list); // current object id - used for checking pointer_text when mouse area // registered (in fnRegisterMouse and fnRegisterFrame) _vm->_mouseList[_vm->_curMouse].id = _scriptVars[ID]; _vm->_mouseList[_vm->_curMouse].pointer_text = 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 threshold = params[0] * 12; // in game cycles // if the actual delay is at or above the given threshold if (_vm->_playerActivityDelay >= threshold) { // reset activity delay counter, now that we've got a // positive check _vm->_playerActivityDelay = 0; _scriptVars[RESULT] = 1; } else _scriptVars[RESULT] = 0; 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->_playerActivityDelay = 0; 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 _scriptVars[RESULT] = _vm->_sound->musicTimeRemaining(); return IR_CONT; } // FIXME: // // The original credits used a different font. I think it's stored in the // font.clu file, but I don't know how to interpret it. // // The original used the entire screen. This version cuts off the top and // bottom of the screen, because that's where the menus would usually be. // // The original had some sort of smoke effect at the bottom of the screen. enum { LINE_LEFT, LINE_CENTER, LINE_RIGHT }; struct CreditsLine { char *str; byte type; int top; int height; byte *sprite; }; #define CREDITS_FONT_HEIGHT 25 #define CREDITS_LINE_SPACING 20 int32 Logic::fnPlayCredits(int32 *params) { uint32 loopingMusicId = _vm->_sound->getLoopingMusicId(); // 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 (_scriptVars[DEMO]) { _vm->closeGame(); return IR_STOP; } // Prepare for the credits by fading down, stoping the music, etc. _vm->setMouse(0); _vm->_sound->muteFx(true); _vm->_sound->muteSpeech(true); _vm->_graphics->waitForFade(); _vm->_graphics->fadeDown(); _vm->_graphics->waitForFade(); _vm->_graphics->closeMenuImmediately(); // There are three files which I believe are involved in showing the // credits: // // credits.bmp - The "Smacker" logo, stored as follows: // // width 2 bytes, little endian // height 2 bytes, little endian // palette 3 * 256 bytes // data width * height bytes // // Note that the maximum colour component in the palette is 0x3F. // This is the same resolution as the _paletteMatch table. I doubt // that this is a coincidence, but let's use the image palette // directly anyway, just to be safe. // // credits.clu - The credits text // // This is simply a text file with CRLF line endings. // '^' is not shown, but used to mark the center of the line. // '@' is used as a placeholder for the "Smacker" logo. At least // when it appears alone. // Remaining lines are centered. // // fonts.clu - The credits font? // // FIXME: At this time I don't know how to interpret fonts.clu. For // now, let's just the standard speech font instead. SpriteInfo spriteInfo; File f; int i; // Read the "Smacker" logo uint16 logoWidth = 0; uint16 logoHeight = 0; byte *logoData = NULL; byte palette[256 * 4]; if (f.open("credits.bmp")) { logoWidth = f.readUint16LE(); logoHeight = f.readUint16LE(); for (i = 0; i < 256; i++) { palette[i * 4 + 0] = f.readByte() << 2; palette[i * 4 + 1] = f.readByte() << 2; palette[i * 4 + 2] = f.readByte() << 2; palette[i * 4 + 3] = 0; } logoData = (byte *) malloc(logoWidth * logoHeight); f.read(logoData, logoWidth * logoHeight); f.close(); } else { warning("Can't find credits.bmp"); memset(palette, 0, sizeof(palette)); palette[14 * 4 + 0] = 252; palette[14 * 4 + 1] = 252; palette[14 * 4 + 2] = 252; palette[14 * 4 + 3] = 0; } _vm->_graphics->setPalette(0, 256, palette, RDPAL_INSTANT); // Read the credits text // This should be plenty CreditsLine creditsLines[350]; for (i = 0; i < ARRAYSIZE(creditsLines); i++) { creditsLines[i].str = NULL; creditsLines[i].sprite = NULL; } if (!f.open("credits.clu")) { warning("Can't find credits.clu"); return IR_CONT; } int lineTop = 400; int lineCount = 0; int paragraphStart = 0; bool hasCenterMark = false; while (1) { if (lineCount >= ARRAYSIZE(creditsLines)) { warning("Too many credits lines"); break; } char buffer[80]; char *line = f.gets(buffer, sizeof(buffer)); if (!line || *line == 0) { if (!hasCenterMark) { for (i = paragraphStart; i < lineCount; i++) creditsLines[i].type = LINE_CENTER; } paragraphStart = lineCount; hasCenterMark = false; if (paragraphStart == lineCount) lineTop += CREDITS_LINE_SPACING; if (!line) break; continue; } char *center_mark = strchr(line, '^'); if (center_mark) { // The current paragraph has at least one center mark. hasCenterMark = true; if (center_mark != line) { // The center mark is somewhere inside the // line. Split it into left and right side. *center_mark = 0; creditsLines[lineCount].top = lineTop; creditsLines[lineCount].height = CREDITS_FONT_HEIGHT; creditsLines[lineCount].type = LINE_LEFT; creditsLines[lineCount].str = strdup(line); lineCount++; if (lineCount >= ARRAYSIZE(creditsLines)) { warning("Too many credits lines"); break; } *center_mark = '^'; } line = center_mark; } creditsLines[lineCount].top = lineTop; if (*line == '^') { creditsLines[lineCount].type = LINE_RIGHT; line++; } else creditsLines[lineCount].type = LINE_LEFT; if (strcmp(line, "@") == 0) { creditsLines[lineCount].height = logoHeight; lineTop += logoHeight; } else { creditsLines[lineCount].height = CREDITS_FONT_HEIGHT; lineTop += CREDITS_LINE_SPACING; } creditsLines[lineCount].str = strdup(line); lineCount++; } f.close(); // We could easily add some ScummVM stuff to the credits, if we wanted // to. On the other hand, anyone with the attention span to actually // read all the credits probably already knows. :-) // Start the music and roll the credits // The credits music (which can also be heard briefly in the "carib" // cutscene) is played once. int32 pars[2]; pars[0] = 309; pars[1] = FX_SPOT; fnPlayMusic(pars); _vm->_graphics->clearScene(); _vm->_graphics->fadeUp(0); spriteInfo.scale = 0; spriteInfo.scaledWidth = 0; spriteInfo.scaledHeight = 0; spriteInfo.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION | RDSPR_TRANS; spriteInfo.blend = 0; int startLine = 0; int scrollPos = 0; bool abortCredits = false; int scrollSteps = lineTop + CREDITS_FONT_HEIGHT; uint32 musicStart = _vm->getMillis(); // Ideally the music should last just a tiny bit longer than the // credits. Note that musicTimeRemaining() will return 0 if the music // is muted, so we need a sensible fallback for that case. uint32 musicLength = MAX((int32) (1000 * (_vm->_sound->musicTimeRemaining() - 3)), 25 * (int32) scrollSteps); while (scrollPos < scrollSteps && !_vm->_quit) { bool foundStartLine = false; _vm->_graphics->clearScene(); for (i = startLine; i < lineCount; i++) { // Free any sprites that have scrolled off the screen if (creditsLines[i].top + creditsLines[i].height < scrollPos) { if (creditsLines[i].sprite) { free(creditsLines[i].sprite); creditsLines[i].sprite = NULL; debug(2, "Freeing sprite '%s'", creditsLines[i].str); } if (creditsLines[i].str) { free(creditsLines[i].str); creditsLines[i].str = NULL; } } else if (creditsLines[i].top < scrollPos + 400) { if (!foundStartLine) { startLine = i; foundStartLine = true; } if (!creditsLines[i].sprite) { debug(2, "Creating sprite '%s'", creditsLines[i].str); creditsLines[i].sprite = _vm->_fontRenderer->makeTextSprite((byte *) creditsLines[i].str, 600, 14, _vm->_speechFontId, 0); } FrameHeader *frame = (FrameHeader *) creditsLines[i].sprite; spriteInfo.y = creditsLines[i].top - scrollPos; spriteInfo.w = frame->width; spriteInfo.h = frame->height; spriteInfo.data = creditsLines[i].sprite + sizeof(FrameHeader); switch (creditsLines[i].type) { case LINE_LEFT: spriteInfo.x = RENDERWIDE / 2 - 5 - frame->width; break; case LINE_RIGHT: spriteInfo.x = RENDERWIDE / 2 + 5; break; case LINE_CENTER: if (strcmp(creditsLines[i].str, "@") == 0) { spriteInfo.data = logoData; spriteInfo.x = (RENDERWIDE - logoWidth) / 2; spriteInfo.w = logoWidth; spriteInfo.h = logoHeight; } else spriteInfo.x = (RENDERWIDE - frame->width) / 2; break; } if (spriteInfo.data) _vm->_graphics->drawSprite(&spriteInfo); } else break; } _vm->_graphics->updateDisplay(); KeyboardEvent *ke = _vm->keyboardEvent(); if (ke && ke->keycode == 27) { if (!abortCredits) { abortCredits = true; _vm->_graphics->fadeDown(); } } if (abortCredits && _vm->_graphics->getFadeStatus() == RDFADE_BLACK) break; _vm->sleepUntil(musicStart + (musicLength * scrollPos) / scrollSteps); scrollPos++; } // We're done. Clean up and try to put everything back where it was // before the credits. for (i = 0; i < lineCount; i++) { if (creditsLines[i].str) free(creditsLines[i].str); if (creditsLines[i].sprite) free(creditsLines[i].sprite); } if (logoData) free(logoData); if (!abortCredits) { // The music should either have stopped or be about to stop, so // wait for it to really happen. while (_vm->_sound->musicTimeRemaining() && !_vm->_quit) { _vm->_graphics->updateDisplay(false); _vm->_system->delayMillis(100); } } if (_vm->_quit) return IR_CONT; _vm->_sound->muteFx(false); _vm->_sound->muteSpeech(false); if (loopingMusicId) { pars[0] = loopingMusicId; pars[1] = FX_LOOP; fnPlayMusic(pars); } else fnStopMusic(NULL); _vm->_thisScreen.new_palette = 99; if (!_vm->_mouseStatus || _choosing) _vm->setMouse(NORMAL_MOUSE_ID); if (_scriptVars[DEAD]) _vm->buildSystemMenu(); return IR_CONT; } int32 Logic::fnSetScrollSpeedNormal(int32 *params) { // params: none _vm->_scrollFraction = 16; return IR_CONT; } int32 Logic::fnSetScrollSpeedSlow(int32 *params) { // params: none _vm->_scrollFraction = 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->_graphics->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 // can reset this now _scriptVars[COMBINE_BASE] = 0; // so that the icon in 'object_held' is coloured while the rest are // grey _vm->_examiningMenuIcon = true; _vm->buildMenu(); _vm->_examiningMenuIcon = false; return IR_CONT; } int32 Logic::fnChangeShadows(int32 *params) { // params: none // if last screen was using a shading mask (see below) if (_vm->_thisScreen.mask_flag) { uint32 rv = _vm->_graphics->closeLightMask(); if (rv) error("Driver Error %.8x", rv); _vm->_thisScreen.mask_flag = false; } return IR_CONT; } } // End of namespace Sword2