/* ScummVM - Scumm Interpreter * 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 "sky/autoroute.h" #include "sky/compact.h" #include "sky/control.h" #include "sky/debug.h" #include "sky/disk.h" #include "sky/grid.h" #include "sky/logic.h" #include "sky/mouse.h" #include "sky/music/musicbase.h" #include "sky/text.h" #include "sky/screen.h" #include "sky/sky.h" #include "sky/sound.h" #include "sky/struc.h" namespace Sky { uint32 Logic::_scriptVariables[NUM_SKY_SCRIPTVARS]; const LogicTable Logic::_logicTable[] = { &Logic::nop, &Logic::logicScript, // 1 script processor &Logic::autoRoute, // 2 Make a route &Logic::arAnim, // 3 Follow a route &Logic::arTurn, // 4 Mega turns araound &Logic::alt, // 5 Set up new get-to script &Logic::anim, // 6 Follow a sequence &Logic::turn, // 7 Mega turning &Logic::cursor, // 8 id tracks the pointer &Logic::talk, // 9 count down and animate &Logic::listen, // 10 player waits for talking id &Logic::stopped, // 11 wait for id to move &Logic::choose, // 12 wait for player to click &Logic::frames, // 13 animate just frames &Logic::pause, // 14 Count down to 0 and go &Logic::waitSync, // 15 Set to l_script when sync!=0 &Logic::simpleAnim, // 16 Module anim without x,y's }; Logic::Logic(SkyCompact *skyCompact, Screen *skyScreen, Disk *skyDisk, Text *skyText, MusicBase *skyMusic, Mouse *skyMouse, Sound *skySound) { _skyCompact = skyCompact; _skyScreen = skyScreen; _skyDisk = skyDisk; _skyText = skyText; _skyMusic = skyMusic; _skySound = skySound; _skyMouse = skyMouse; _skyGrid = new Grid(_skyDisk, _skyCompact); _skyAutoRoute = new AutoRoute(_skyGrid, _skyCompact); memset(_objectList, 0, 30 * sizeof(uint32)); for (int i = 0; i < ARRAYSIZE(_moduleList); i++) _moduleList[i] = 0; _stackPtr = 0; _currentSection = 0xFF; //force music & sound reload initScriptVariables(); } void Logic::initScreen0(void) { fnEnterSection(0, 0, 0); _skyMusic->startMusic(2); SkyEngine::_systemVars.currentMusic = 2; } void Logic::parseSaveData(uint32 *data) { if (!SkyEngine::isDemo()) fnLeaveSection(_scriptVariables[CUR_SECTION], 0, 0); for (uint16 cnt = 0; cnt < NUM_SKY_SCRIPTVARS; cnt++) _scriptVariables[cnt] = READ_LE_UINT32(data++); fnEnterSection(_scriptVariables[CUR_SECTION], 0, 0); } bool Logic::checkProtection(void) { if (_scriptVariables[ENTER_DIGITS]) { if (_scriptVariables[CONSOLE_TYPE] == 5) // reactor code _scriptVariables[FS_COMMAND] = 240; else // copy protection _scriptVariables[FS_COMMAND] = 337; _scriptVariables[ENTER_DIGITS] = 0; return true; } else return false; } void Logic::engine() { do { uint16 *logicList = (uint16 *)_skyCompact->fetchCpt(_scriptVariables[LOGIC_LIST_NO]); while (uint16 id = *logicList++) { // 0 means end of list if (id == 0xffff) { // Change logic data address logicList = (uint16 *)_skyCompact->fetchCpt(*logicList); continue; } _scriptVariables[CUR_ID] = id; _compact = _skyCompact->fetchCpt(id); // check the id actually wishes to be processed if (!(_compact->status & (1 << 6))) continue; // ok, here we process the logic bit system if (_compact->status & (1 << 7)) _skyGrid->removeObjectFromWalk(_compact); Debug::logic(_compact->logic); (this->*_logicTable[_compact->logic]) (); if (_compact->status & (1 << 7)) _skyGrid->objectToWalk(_compact); // a sync sent to the compact is available for one cycle // only. that cycle has just ended so remove the sync. // presumably the mega has just reacted to it. _compact->sync = 0; } // usually this loop is run only once, it'll only be run a second time if the game // script just asked the user to enter a copy protection code. // this is done to prevent the copy protection screen from flashing up. // (otherwise it would be visible for 1/50 second) } while (checkProtection()); } void Logic::nop() {} /** * This function is basicly a wrapper around the real script engine. It runs * the script engine until a script has finished. * @see script() */ void Logic::logicScript() { /// Process the current mega's script /// If the script finishes then drop back a level for (;;) { uint16 mode = _compact->mode; // get pointer to current script uint16 *scriptNo = SkyCompact::getSub(_compact, mode); uint16 *offset = SkyCompact::getSub(_compact, mode + 2); *offset = script(*scriptNo, *offset); if (!*offset) // script finished _compact->mode -= 4; else if (_compact->mode == mode) return; } } void Logic::autoRoute() { _compact->downFlag = _skyAutoRoute->autoRoute(_compact); if ((_compact->downFlag == 2) && _skyCompact->cptIsId(_compact, CPT_JOEY) && (_compact->mode == 0) && (_compact->baseSub == JOEY_OUT_OF_LIFT)) { // workaround for script bug #1064113. Details unclear... _compact->downFlag = 0; } if (_compact->downFlag != 1) { // route ok _compact->grafixProgId = _compact->animScratchId; _compact->grafixProgPos = 0; } _compact->logic = L_SCRIPT; // continue the script logicScript(); return; } void Logic::arAnim() { /// Follow a route /// Mega should be in getToMode // only check collisions on character boundaries if ((_compact->xcood & 7) || (_compact->ycood & 7)) { mainAnim(); return; } // On character boundary. Have we been told to wait? // if not - are WE colliding? if (_compact->waitingFor == 0xffff) { // 1st cycle of re-route does mainAnim(); return; } if (_compact->waitingFor) { // ok, we've been told we've hit someone // we will wait until we are no longer colliding // with them. here we check to see if we are (still) colliding. // if we are then run the stop script. if not clear the flag // and continue. // remember - this could be the first ar cycle for some time, // we might have been told to wait months ago. if we are // waiting for one person then another hits us then // c_waiting_for will be replaced by the new mega - this is // fine because the later collision will almost certainly // take longer to clear than the earlier one. if (collide(_skyCompact->fetchCpt(_compact->waitingFor))) { stopAndWait(); return; } // we are not in fact hitting this person so clr & continue // it must have registered some time ago _compact->waitingFor = 0; // clear id flag } // ok, our turn to check for collisions uint16 *logicList = (uint16 *)_skyCompact->fetchCpt(_scriptVariables[LOGIC_LIST_NO]); Compact *cpt = 0; while (uint16 id = *logicList++) { // get an id if (id == 0xffff) { // address change? logicList = (uint16 *)_skyCompact->fetchCpt(*logicList); // get new logic list continue; } if (id == (uint16)(_scriptVariables[CUR_ID] & 0xffff)) // is it us? continue; _scriptVariables[HIT_ID] = id; // save target id for any possible c_mini_bump cpt = _skyCompact->fetchCpt(id); // let's have a closer look if (!(cpt->status & (1 << ST_COLLISION_BIT))) // can it collide? continue; if (cpt->screen != _compact->screen) // is it on our screen? continue; if (collide(cpt)) { // check for a hit // ok, we've hit a mega // is it moving... or something else? if (cpt->logic != L_AR_ANIM) { // check for following route // it is doing something else // we restart our get-to script // first tell it to wait for us - in case it starts moving // ( *it may have already hit us and stopped to wait ) _compact->waitingFor = 0xffff; // effect 1 cycle collision skip // tell it it is waiting for us cpt->waitingFor = (uint16)(_scriptVariables[CUR_ID] & 0xffff); // restart current script *SkyCompact::getSub(_compact, _compact->mode + 2) = 0; _compact->logic = L_SCRIPT; logicScript(); return; } script(_compact->miniBump, 0); return; } } // ok, there was no collisions // now check for interaction request // *note: the interaction is always set up as an action script if (_compact->request) { _compact->mode = C_ACTION_MODE; // put into action mode _compact->actionSub = _compact->request; _compact->actionSub_off = 0; _compact->request = 0; // trash request _compact->logic = L_SCRIPT; logicScript(); return; } // any flag? - or any change? // if change then re-run the current script, which must be // a position independent get-to ---- if (!_compact->atWatch) { // any flag set? mainAnim(); return; } // ok, there is an at watch - see if it's changed if (_compact->atWas == _scriptVariables[_compact->atWatch/4]) { // still the same? mainAnim(); return; } // changed so restart the current script // *not suitable for base initiated ARing *SkyCompact::getSub(_compact, _compact->mode + 2) = 0; _compact->logic = L_SCRIPT; logicScript(); } void Logic::mainAnim() { /// Extension of arAnim() _compact->waitingFor = 0; // clear possible zero-zero skip uint16 *sequence = _skyCompact->getGrafixPtr(_compact); if (!*sequence) { // ok, move to new anim segment sequence += 2; _compact->grafixProgPos += 2; if (!*sequence) { // end of route? // ok, sequence has finished // will start afresh if new sequence continues in last direction _compact->arAnimIndex = 0; _compact->downFlag = 0; // pass back ok to script _compact->logic = L_SCRIPT; logicScript(); return; } _compact->arAnimIndex = 0; // reset position } uint16 dir; while ((dir = _compact->dir) != *(sequence + 1)) { // ok, setup turning _compact->dir = *(sequence + 1); uint16 *tt = _skyCompact->getTurnTable(_compact, dir); if (tt[_compact->dir]) { _compact->turnProgId = tt[_compact->dir]; _compact->turnProgPos = 0; _compact->logic = L_AR_TURNING; arTurn(); return; } }; uint16 animId = *(uint16*)_skyCompact->getCompactElem(_compact, C_ANIM_UP + _compact->megaSet + dir * 4); uint16 *animList = (uint16*)_skyCompact->fetchCpt(animId); uint16 arAnimIndex = _compact->arAnimIndex; if (!animList[arAnimIndex / 2]) { arAnimIndex = 0; _compact->arAnimIndex = 0; // reset } _compact->arAnimIndex += S_LENGTH; *sequence -= animList[(S_COUNT + arAnimIndex)/2]; // reduce the distance to travel _compact->frame = animList[(S_FRAME + arAnimIndex)/2]; // new graphic frame _compact->xcood += animList[(S_AR_X + arAnimIndex)/2]; // update x coordinate _compact->ycood += animList[(S_AR_Y + arAnimIndex)/2]; // update y coordinate } void Logic::arTurn() { uint16 *turnData = (uint16*)_skyCompact->fetchCpt(_compact->turnProgId) + _compact->turnProgPos; _compact->frame = *turnData++; _compact->turnProgPos++; if (!*turnData) { // turn done? // Back to ar mode _compact->arAnimIndex = 0; _compact->logic = L_AR_ANIM; } } void Logic::alt() { /// change the current script _compact->logic = L_SCRIPT; *SkyCompact::getSub(_compact, _compact->mode) = _compact->alt; *SkyCompact::getSub(_compact, _compact->mode + 2) = 0; logicScript(); } void Logic::anim() { /// Follow an animation sequence uint16 *grafixProg = _skyCompact->getGrafixPtr(_compact); while (*grafixProg) { _compact->grafixProgPos += 3; // all types are 3 words. if (*grafixProg == LF_START_FX) { // do fx grafixProg++; uint16 sound = *grafixProg++; uint16 volume = *grafixProg++; // channel 0 fnStartFx(sound, 0, volume); } else if (*grafixProg >= LF_START_FX) { // do sync grafixProg++; Compact *cpt = _skyCompact->fetchCpt(*grafixProg++); cpt->sync = *grafixProg++; } else { // put coordinates and frame in _compact->xcood = *grafixProg++; _compact->ycood = *grafixProg++; _compact->frame = *grafixProg++ | _compact->offset; return; } } _compact->downFlag = 0; _compact->logic = L_SCRIPT; logicScript(); } void Logic::turn() { uint16 *turnData = (uint16*)_skyCompact->fetchCpt(_compact->turnProgId) + _compact->turnProgPos; if (*turnData) { _compact->frame = *turnData; _compact->turnProgPos++; return; } // turn_to_script: _compact->arAnimIndex = 0; _compact->logic = L_SCRIPT; logicScript(); } void Logic::cursor() { _skyText->logicCursor(_compact, _skyMouse->giveMouseX(), _skyMouse->giveMouseY()); } static uint16 clickTable[46] = { ID_FOSTER, ID_JOEY, ID_JOBS, ID_LAMB, ID_ANITA, ID_SON, ID_DAD, ID_MONITOR, ID_SHADES, MINI_SS, FULL_SS, ID_FOREMAN, ID_RADMAN, ID_GALLAGER_BEL, ID_BURKE, ID_BODY, ID_HOLO, ID_TREVOR, ID_ANCHOR, ID_WRECK_GUARD, ID_SKORL_GUARD, // BASE LEVEL ID_SC30_HENRI, ID_SC31_GUARD, ID_SC32_VINCENT, ID_SC32_GARDENER, ID_SC32_BUZZER, ID_SC36_BABS, ID_SC36_BARMAN, ID_SC36_COLSTON, ID_SC36_GALLAGHER, ID_SC36_JUKEBOX, ID_DANIELLE, ID_SC42_JUDGE, ID_SC42_CLERK, ID_SC42_PROSECUTION, ID_SC42_JOBSWORTH, // UNDERWORLD ID_MEDI, ID_WITNESS, ID_GALLAGHER, ID_KEN, ID_SC76_ANDROID_2, ID_SC76_ANDROID_3, ID_SC81_FATHER, ID_SC82_JOBSWORTH, // LINC WORLD ID_HOLOGRAM_B, 12289 }; void Logic::talk() { // first count through the frames // just frames - nothing tweeky // the speech finishes when the timer runs out & // not when the animation finishes // this routine is very task specific // TODO: Check for mouse clicking // Are we allowed to click if (_skyMouse->wasClicked()) for (int i = 0; i < ARRAYSIZE(clickTable); i++) if (clickTable[i] == (uint16)_scriptVariables[CUR_ID]) { if ((SkyEngine::_systemVars.systemFlags & SF_ALLOW_SPEECH) && (!_skySound->speechFinished())) _skySound->stopSpeech(); if ((SkyEngine::_systemVars.systemFlags & SF_ALLOW_TEXT) && (_compact->spTextId > 0) && (_compact->spTextId < 0xFFFF)) { _skyCompact->fetchCpt(_compact->spTextId)->status = 0; } if (_skyCompact->getGrafixPtr(_compact)) { _compact->frame = _compact->getToFlag; // set character to stand _compact->grafixProgId = 0; } _compact->logic = L_SCRIPT; logicScript(); return; } // If speech is allowed then check for it to finish before finishing animations if ((_compact->spTextId == 0xFFFF) && // is this a voc file? (_skySound->speechFinished())) { // finished? _compact->logic = L_SCRIPT; // restart character control if (_skyCompact->getGrafixPtr(_compact)) { _compact->frame = _compact->getToFlag; // set character to stand _compact->grafixProgId = 0; } logicScript(); return; } uint16 *graphixProg = _skyCompact->getGrafixPtr(_compact); if (graphixProg) { if ((*graphixProg) && ((_compact->spTime != 3) || (!_skySound->speechFinished()))) { // we will force the animation to finish 3 game cycles // before the speech actually finishes - because it looks good. _compact->frame = *(graphixProg + 2) + _compact->offset; graphixProg += 3; _compact->grafixProgPos += 3; } else { // we ran out of frames or finished speech, let actor stand still. _compact->frame = _compact->getToFlag; _compact->grafixProgId = 0; } } if (_skySound->speechFinished()) _compact->spTime--; if (_compact->spTime == 0) { // ok, speech has finished if (_compact->spTextId) { Compact *cpt = _skyCompact->fetchCpt(_compact->spTextId); // get text id to kill cpt->status = 0; // kill the text } _compact->logic = L_SCRIPT; logicScript(); } } void Logic::listen() { /// Stay in this mode until id in getToFlag leaves L_TALK mode Compact *cpt = _skyCompact->fetchCpt(_compact->flag); if (cpt->logic == L_TALK) return; _compact->logic = L_SCRIPT; logicScript(); } void Logic::stopped() { /// waiting for another mega to move or give-up trying /// /// this mode will always be set up from a special script /// that will be one level higher than the script we /// would wish to restart from Compact *cpt = _skyCompact->fetchCpt(_compact->waitingFor); if (cpt) if (!cpt->mood && collide(cpt)) return; // we are free, continue processing the script // restart script one level below *SkyCompact::getSub(_compact, _compact->mode - 2) = 0; _compact->waitingFor = 0xffff; _compact->logic = L_SCRIPT; logicScript(); } void Logic::choose() { // Remain in this mode until player selects some text if (!_scriptVariables[THE_CHOSEN_ONE]) return; fnNoHuman(0, 0, 0); // kill mouse again SkyEngine::_systemVars.systemFlags &= ~SF_CHOOSING; // restore save/restore _compact->logic = L_SCRIPT; // and continue script logicScript(); } void Logic::frames() { if (!_compact->sync) simpleAnim(); else { _compact->downFlag = 0; // return 'ok' to script _compact->logic = L_SCRIPT; logicScript(); } } void Logic::pause() { if (--_compact->flag) return; _compact->logic = L_SCRIPT; logicScript(); return; } void Logic::waitSync() { /// checks c_sync, when its non 0 /// the id is put back into script mode // use this instead of loops in the script if (!_compact->sync) return; _compact->logic = L_SCRIPT; logicScript(); } void Logic::simpleAnim() { /// follow an animation sequence module whilst ignoring the coordinate data uint16 *grafixProg = _skyCompact->getGrafixPtr(_compact); // *grafix_prog: command while (*grafixProg) { _compact->grafixProgPos += 3; if (*grafixProg != SEND_SYNC) { grafixProg++; grafixProg++; // skip coordinates // *grafix_prog: frame if (*grafixProg >= 64) _compact->frame = *grafixProg; else _compact->frame = *grafixProg + _compact->offset; return; } grafixProg++; // *grafix_prog: id to sync Compact *compact2 = _skyCompact->fetchCpt(*grafixProg); grafixProg++; // *grafix_prog: sync compact2->sync = *grafixProg; grafixProg++; } _compact->downFlag = 0; // return 'ok' to script _compact->logic = L_SCRIPT; logicScript(); } bool Logic::collide(Compact *cpt) { MegaSet *m1 = SkyCompact::getMegaSet(_compact); MegaSet *m2 = SkyCompact::getMegaSet(cpt); // target's base coordinates uint16 x = cpt->xcood & 0xfff8; uint16 y = cpt->ycood & 0xfff8; // The collision is direction dependent switch (_compact->dir) { case 0: // looking up x -= m1->colOffset; // compensate for inner x offsets x += m2->colOffset; if ((x + m2->colWidth) < _compact->xcood) // their rightmost return false; x -= m1->colWidth; // our left, their right if (x >= _compact->xcood) return false; y += 8; // bring them down a line if (y == _compact->ycood) return true; y += 8; // bring them down a line if (y == _compact->ycood) return true; return false; case 1: // looking down x -= m1->colOffset; // compensate for inner x offsets x += m2->colOffset; if ((x + m2->colWidth) < _compact->xcood) // their rightmoast return false; x -= m1->colWidth; // our left, their right if (x >= _compact->xcood) return false; y -= 8; // bring them up a line if (y == _compact->ycood) return true; y -= 8; // bring them up a line if (y == _compact->ycood) return true; return false; case 2: // looking left if (y != _compact->ycood) return false; x += m2->lastChr; if (x == _compact->xcood) return true; x -= 8; // out another one if (x == _compact->xcood) return true; return false; case 3: // looking right case 4: // talking (not sure if this makes sense...) if (y != _compact->ycood) return false; x -= m1->lastChr; // last block if (x == _compact->xcood) return true; x -= 8; // out another block if (x != _compact->xcood) return false; return true; default: error("Unknown Direction: %d", _compact->dir); } } void Logic::runGetOff() { uint32 getOff = _scriptVariables[GET_OFF]; _scriptVariables[GET_OFF] = 0; if (getOff) script((uint16)(getOff & 0xffff), (uint16)(getOff >> 16)); } void Logic::stopAndWait() { _compact->mode += 4; uint16 *scriptNo = SkyCompact::getSub(_compact, _compact->mode); uint16 *offset = SkyCompact::getSub(_compact, _compact->mode + 2); *scriptNo = _compact->stopScript; *offset = 0; _compact->logic = L_SCRIPT; logicScript(); } void Logic::checkModuleLoaded(uint16 moduleNo) { if (!_moduleList[moduleNo]) _moduleList[moduleNo] = (uint16 *)_skyDisk->loadFile((uint16)moduleNo + F_MODULE_0); } void Logic::push(uint32 a) { if (_stackPtr > ARRAYSIZE(_stack) - 2) error("Stack overflow"); _stack[_stackPtr++] = a; } uint32 Logic::pop() { if (_stackPtr < 1 || _stackPtr > ARRAYSIZE(_stack) - 1) error("No items on Stack to pop"); return _stack[--_stackPtr]; } const McodeTable Logic::_mcodeTable[] = { &Logic::fnCacheChip, &Logic::fnCacheFast, &Logic::fnDrawScreen, &Logic::fnAr, &Logic::fnArAnimate, &Logic::fnIdle, &Logic::fnInteract, &Logic::fnStartSub, &Logic::fnTheyStartSub, &Logic::fnAssignBase, &Logic::fnDiskMouse, &Logic::fnNormalMouse, &Logic::fnBlankMouse, &Logic::fnCrossMouse, &Logic::fnCursorRight, &Logic::fnCursorLeft, &Logic::fnCursorDown, &Logic::fnOpenHand, &Logic::fnCloseHand, &Logic::fnGetTo, &Logic::fnSetToStand, &Logic::fnTurnTo, &Logic::fnArrived, &Logic::fnLeaving, &Logic::fnSetAlternate, &Logic::fnAltSetAlternate, &Logic::fnKillId, &Logic::fnNoHuman, &Logic::fnAddHuman, &Logic::fnAddButtons, &Logic::fnNoButtons, &Logic::fnSetStop, &Logic::fnClearStop, &Logic::fnPointerText, &Logic::fnQuit, &Logic::fnSpeakMe, &Logic::fnSpeakMeDir, &Logic::fnSpeakWait, &Logic::fnSpeakWaitDir, &Logic::fnChooser, &Logic::fnHighlight, &Logic::fnTextKill, &Logic::fnStopMode, &Logic::fnWeWait, &Logic::fnSendSync, &Logic::fnSendFastSync, &Logic::fnSendRequest, &Logic::fnClearRequest, &Logic::fnCheckRequest, &Logic::fnStartMenu, &Logic::fnUnhighlight, &Logic::fnFaceId, &Logic::fnForeground, &Logic::fnBackground, &Logic::fnNewBackground, &Logic::fnSort, &Logic::fnNoSpriteEngine, &Logic::fnNoSpritesA6, &Logic::fnResetId, &Logic::fnToggleGrid, &Logic::fnPause, &Logic::fnRunAnimMod, &Logic::fnSimpleMod, &Logic::fnRunFrames, &Logic::fnAwaitSync, &Logic::fnIncMegaSet, &Logic::fnDecMegaSet, &Logic::fnSetMegaSet, &Logic::fnMoveItems, &Logic::fnNewList, &Logic::fnAskThis, &Logic::fnRandom, &Logic::fnPersonHere, &Logic::fnToggleMouse, &Logic::fnMouseOn, &Logic::fnMouseOff, &Logic::fnFetchX, &Logic::fnFetchY, &Logic::fnTestList, &Logic::fnFetchPlace, &Logic::fnCustomJoey, &Logic::fnSetPalette, &Logic::fnTextModule, &Logic::fnChangeName, &Logic::fnMiniLoad, &Logic::fnFlushBuffers, &Logic::fnFlushChip, &Logic::fnSaveCoods, &Logic::fnPlotGrid, &Logic::fnRemoveGrid, &Logic::fnEyeball, &Logic::fnCursorUp, &Logic::fnLeaveSection, &Logic::fnEnterSection, &Logic::fnRestoreGame, &Logic::fnRestartGame, &Logic::fnNewSwingSeq, &Logic::fnWaitSwingEnd, &Logic::fnSkipIntroCode, &Logic::fnBlankScreen, &Logic::fnPrintCredit, &Logic::fnLookAt, &Logic::fnLincTextModule, &Logic::fnTextKill2, &Logic::fnSetFont, &Logic::fnStartFx, &Logic::fnStopFx, &Logic::fnStartMusic, &Logic::fnStopMusic, &Logic::fnFadeDown, &Logic::fnFadeUp, &Logic::fnQuitToDos, &Logic::fnPauseFx, &Logic::fnUnPauseFx, &Logic::fnPrintf }; static const uint32 forwardList1b[] = { JOBS_SPEECH, JOBS_S4, JOBS_ALARMED, JOEY_RECYCLE, SHOUT_SSS, JOEY_MISSION, TRANS_MISSION, SLOT_MISSION, CORNER_MISSION, JOEY_LOGIC, GORDON_SPEECH, JOEY_BUTTON_MISSION, LOB_DAD_SPEECH, LOB_SON_SPEECH, GUARD_SPEECH, MANTRACH_SPEECH, WRECK_SPEECH, ANITA_SPEECH, LAMB_FACTORY, FORE_SPEECH, JOEY_42_MISS, JOEY_JUNCTION_MISS, WELDER_MISSION, JOEY_WELD_MISSION, RADMAN_SPEECH, LINK_7_29, LINK_29_7, LAMB_TO_3, LAMB_TO_2, BURKE_SPEECH, BURKE_1, BURKE_2, DR_BURKE_1, JASON_SPEECH, JOEY_BELLEVUE, ANCHOR_SPEECH, ANCHOR_MISSION, JOEY_PC_MISSION, HOOK_MISSION, TREVOR_SPEECH, JOEY_FACTORY, HELGA_SPEECH, JOEY_HELGA_MISSION, GALL_BELLEVUE, GLASS_MISSION, LAMB_FACT_RETURN, LAMB_LEAVE_GARDEN, LAMB_START_29, LAMB_BELLEVUE, CABLE_MISSION, FOSTER_TOUR, LAMB_TOUR, FOREMAN_LOGIC, LAMB_LEAVE_FACTORY, LAMB_BELL_LOGIC, LAMB_FACT_2, START90, 0, 0, LINK_28_31, LINK_31_28, EXIT_LINC, DEATH_SCRIPT }; static uint32 forwardList1b288[] = { JOBS_SPEECH, JOBS_S4, JOBS_ALARMED, JOEY_RECYCLE, SHOUT_SSS, JOEY_MISSION, TRANS_MISSION, SLOT_MISSION, CORNER_MISSION, JOEY_LOGIC, GORDON_SPEECH, JOEY_BUTTON_MISSION, LOB_DAD_SPEECH, LOB_SON_SPEECH, GUARD_SPEECH, 0x68, WRECK_SPEECH, ANITA_SPEECH, LAMB_FACTORY, FORE_SPEECH, JOEY_42_MISS, JOEY_JUNCTION_MISS, WELDER_MISSION, JOEY_WELD_MISSION, RADMAN_SPEECH, LINK_7_29, LINK_29_7, LAMB_TO_3, LAMB_TO_2, 0x3147, 0x3100, 0x3101, 0x3102, 0x3148, 0x3149, 0x314A, 0x30C5, 0x30C6, 0x30CB, 0x314B, JOEY_FACTORY, 0x314C, 0x30E2, 0x314D, 0x310C, LAMB_FACT_RETURN, 0x3139, 0x313A, 0x004F, CABLE_MISSION, FOSTER_TOUR, LAMB_TOUR, FOREMAN_LOGIC, LAMB_LEAVE_FACTORY, 0x3138, LAMB_FACT_2, 0x004D, 0, 0, LINK_28_31, LINK_31_28, 0x004E, DEATH_SCRIPT }; static const uint32 forwardList2b[] = { STD_ON, STD_EXIT_LEFT_ON, STD_EXIT_RIGHT_ON, ADVISOR_188, SHOUT_ACTION, MEGA_CLICK, MEGA_ACTION }; static const uint32 forwardList3b[] = { DANI_SPEECH, DANIELLE_GO_HOME, SPUNKY_GO_HOME, HENRI_SPEECH, BUZZER_SPEECH, FOSTER_VISIT_DANI, DANIELLE_LOGIC, JUKEBOX_SPEECH, VINCENT_SPEECH, EDDIE_SPEECH, BLUNT_SPEECH, DANI_ANSWER_PHONE, SPUNKY_SEE_VIDEO, SPUNKY_BARK_AT_FOSTER, SPUNKY_SMELLS_FOOD, BARRY_SPEECH, COLSTON_SPEECH, GALL_SPEECH, BABS_SPEECH, CHUTNEY_SPEECH, FOSTER_ENTER_COURT }; static const uint32 forwardList4b[] = { WALTER_SPEECH, JOEY_MEDIC, JOEY_MED_LOGIC, JOEY_MED_MISSION72, KEN_LOGIC, KEN_SPEECH, KEN_MISSION_HAND, SC70_IRIS_OPENED, SC70_IRIS_CLOSED, FOSTER_ENTER_BOARDROOM, BORED_ROOM, FOSTER_ENTER_NEW_BOARDROOM, HOBS_END, SC82_JOBS_SSS }; static const uint32 forwardList5b[] = { SET_UP_INFO_WINDOW, SLAB_ON, UP_MOUSE, DOWN_MOUSE, LEFT_MOUSE, RIGHT_MOUSE, DISCONNECT_FOSTER }; void Logic::fnExec(uint16 num, uint32 a, uint32 b, uint32 c) { (this->*_mcodeTable[num])(a, b, c); } void Logic::initScriptVariables() { for (int i = 0; i < ARRAYSIZE(_scriptVariables); i++) _scriptVariables[i] = 0; _scriptVariables[LOGIC_LIST_NO] = 141; _scriptVariables[LAMB_GREET] = 62; _scriptVariables[JOEY_SECTION] = 1; _scriptVariables[LAMB_SECTION] = 2; _scriptVariables[S15_FLOOR] = 8371; _scriptVariables[GUARDIAN_THERE] = 1; _scriptVariables[DOOR_67_68_FLAG] = 1; _scriptVariables[SC70_IRIS_FLAG] = 3; _scriptVariables[DOOR_73_75_FLAG] = 1; _scriptVariables[SC76_CABINET1_FLAG] = 1; _scriptVariables[SC76_CABINET2_FLAG] = 1; _scriptVariables[SC76_CABINET3_FLAG] = 1; _scriptVariables[DOOR_77_78_FLAG] = 1; _scriptVariables[SC80_EXIT_FLAG] = 1; _scriptVariables[SC31_LIFT_FLAG] = 1; _scriptVariables[SC32_LIFT_FLAG] = 1; _scriptVariables[SC33_SHED_DOOR_FLAG] = 1; _scriptVariables[BAND_PLAYING] = 1; _scriptVariables[COLSTON_AT_TABLE] = 1; _scriptVariables[SC36_NEXT_DEALER] = 16731; _scriptVariables[SC36_DOOR_FLAG] = 1; _scriptVariables[SC37_DOOR_FLAG] = 2; _scriptVariables[SC40_LOCKER_1_FLAG] = 1; _scriptVariables[SC40_LOCKER_2_FLAG] = 1; _scriptVariables[SC40_LOCKER_3_FLAG] = 1; _scriptVariables[SC40_LOCKER_4_FLAG] = 1; _scriptVariables[SC40_LOCKER_5_FLAG] = 1; if (SkyEngine::_systemVars.gameVersion == 288) memcpy(_scriptVariables + 352, forwardList1b288, sizeof(forwardList1b288)); else memcpy(_scriptVariables + 352, forwardList1b, sizeof(forwardList1b)); memcpy(_scriptVariables + 656, forwardList2b, sizeof(forwardList2b)); memcpy(_scriptVariables + 721, forwardList3b, sizeof(forwardList3b)); memcpy(_scriptVariables + 663, forwardList4b, sizeof(forwardList4b)); memcpy(_scriptVariables + 505, forwardList5b, sizeof(forwardList5b)); } uint16 Logic::mouseScript(uint32 scrNum, Compact *scriptComp) { Compact *tmpComp = _compact; _compact = scriptComp; uint16 retVal = script((uint16)(scrNum & 0xFFFF), (uint16)(scrNum >> 16)); _compact = tmpComp; return retVal; } /** * This is the actual script engine. It interprets script \a scriptNo starting at \a offset * * @param scriptNo The script to interpret. * @arg Bits 0-11 - Script number * @arg Bits 12-15 - Module number * @param offset At which offset to start interpreting the script. * * @return 0 if script finished. Else offset where to continue. */ uint16 Logic::script(uint16 scriptNo, uint16 offset) { script: /// process a script /// low level interface to interpreter uint16 moduleNo = (uint16)((scriptNo & 0xff00) >> 12); debug(3, "Doing Script %x", (offset << 16) | scriptNo); uint16 *scriptData = _moduleList[moduleNo]; // get module address if (!scriptData) { // The module has not been loaded scriptData = (uint16 *)_skyDisk->loadFile(moduleNo + F_MODULE_0); _moduleList[moduleNo] = scriptData; // module has been loaded } uint16 *moduleStart = scriptData; // Check whether we have an offset or what if (offset) scriptData = moduleStart + offset; else scriptData += READ_LE_UINT16(scriptData + (scriptNo & 0x0fff)); uint32 a = 0, b = 0, c = 0; uint16 command, s; for (;;) { command = READ_LE_UINT16(scriptData++); // get a command Debug::script(command, scriptData); switch (command) { case 0: // push_variable push( _scriptVariables[READ_LE_UINT16(scriptData++) / 4] ); break; case 1: // less_than a = pop(); b = pop(); if (a > b) push(1); else push(0); break; case 2: // push_number push(READ_LE_UINT16(scriptData++)); break; case 3: // not_equal a = pop(); b = pop(); if (a != b) push(1); else push(0); break; case 4: // if_and a = pop(); b = pop(); if (a && b) push(1); else push(0); break; case 5: // skip_zero s = READ_LE_UINT16(scriptData++); a = pop(); if (!a) scriptData += s / 2; break; case 6: // pop_var b = _scriptVariables[READ_LE_UINT16(scriptData++) / 4] = pop(); break; case 7: // minus a = pop(); b = pop(); push(b-a); break; case 8: // plus a = pop(); b = pop(); push(b+a); break; case 9: // skip_always s = READ_LE_UINT16(scriptData++); scriptData += s / 2; break; case 10: // if_or a = pop(); b = pop(); if (a || b) push(1); else push(0); break; case 11: // call_mcode { a = READ_LE_UINT16(scriptData++); assert(a <= 3); // No, I did not forget the "break"s switch (a) { case 3: c = pop(); case 2: b = pop(); case 1: a = pop(); } uint16 mcode = READ_LE_UINT16(scriptData++)/4; // get mcode number Debug::mcode(mcode, a, b, c); Compact *saveCpt = _compact; bool ret = (this->*_mcodeTable[mcode]) (a, b, c); _compact = saveCpt; if (!ret) return (scriptData - moduleStart); } break; case 12: // more_than a = pop(); b = pop(); if (a < b) push(1); else push(0); break; case 14: // switch c = s = READ_LE_UINT16(scriptData++); // get number of cases a = pop(); // and value to switch on do { if (a == READ_LE_UINT16(scriptData)) { scriptData += READ_LE_UINT16(scriptData + 1) / 2; scriptData++; break; } scriptData += 2; } while (--s); if (s == 0) scriptData += READ_LE_UINT16(scriptData)/2; // use the default break; case 15: // push_offset push( *(uint16 *)_skyCompact->getCompactElem(_compact, READ_LE_UINT16(scriptData++)) ); break; case 16: // pop_offset // pop a value into a compact *(uint16 *)_skyCompact->getCompactElem(_compact, READ_LE_UINT16(scriptData++)) = (uint16)pop(); break; case 17: // is_equal a = pop(); b = pop(); if (a == b) push(1); else push(0); break; case 18: { // skip_nz int16 t = READ_LE_UINT16(scriptData++); a = pop(); if (a) scriptData += t / 2; break; } case 13: case 19: // script_exit return 0; case 20: // restart_script offset = 0; goto script; default: error("Unknown script command: %d", command); } } } bool Logic::fnCacheChip(uint32 a, uint32 b, uint32 c) { _skySound->fnStopFx(); _skyDisk->fnCacheChip((uint16*)_skyCompact->fetchCpt((uint16)a)); return true; } bool Logic::fnCacheFast(uint32 a, uint32 b, uint32 c) { _skyDisk->fnCacheFast((uint16*)_skyCompact->fetchCpt((uint16)a)); return true; } bool Logic::fnDrawScreen(uint32 a, uint32 b, uint32 c) { debug(5, "Call: fnDrawScreen(%X, %X)",a,b); SkyEngine::_systemVars.currentPalette = a; _skyScreen->fnDrawScreen(a, b); if (Logic::_scriptVariables[SCREEN] == 32) { /* workaround for script bug #786482 Under certain circumstances, which never got completely cleared, the gardener can get stuck in an animation, waiting for a sync signal from foster. This is most probably caused by foster leaving the screen before sending the sync. To work around that, we simply send a sync to the gardener every time we enter the screen. If he isn't stuck (and thus not waiting for sync) it will be ignored anyways */ debug(1, "sending gardener sync"); fnSendSync(ID_SC32_GARDENER, 1, 0); } return true; } bool Logic::fnAr(uint32 x, uint32 y, uint32 c) { _compact->downFlag = 1; // assume failure in-case logic is interupted by speech (esp Joey) _compact->arTargetX = (uint16)x; _compact->arTargetY = (uint16)y; _compact->logic = L_AR; // Set to AR mode _compact->xcood &= 0xfff8; _compact->ycood &= 0xfff8; return false; // drop out of script } bool Logic::fnArAnimate(uint32 a, uint32 b, uint32 c) { _compact->mood = 0; // high level 'not stood still' _compact->logic = L_AR_ANIM; return false; // drop out of script } bool Logic::fnIdle(uint32 a, uint32 b, uint32 c) { // set the player idling _compact->logic = 0; return true; } bool Logic::fnInteract(uint32 targetId, uint32 b, uint32 c) { _compact->mode += 4; // next level up _compact->logic = L_SCRIPT; Compact *cpt = _skyCompact->fetchCpt(targetId); *SkyCompact::getSub(_compact, _compact->mode) = cpt->actionScript; *SkyCompact::getSub(_compact, _compact->mode + 2) = 0; return false; } bool Logic::fnStartSub(uint32 scr, uint32 b, uint32 c) { _compact->mode += 4; *SkyCompact::getSub(_compact, _compact->mode) = (uint16)(scr & 0xffff); *SkyCompact::getSub(_compact, _compact->mode + 2) = (uint16)(scr >> 16); return false; } bool Logic::fnTheyStartSub(uint32 mega, uint32 scr, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(mega); cpt->mode += 4; *SkyCompact::getSub(cpt, cpt->mode) = (uint16)(scr & 0xffff); *SkyCompact::getSub(cpt, cpt->mode + 2) = (uint16)(scr >> 16); return true; } bool Logic::fnAssignBase(uint32 id, uint32 scr, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(id); cpt->mode = C_BASE_MODE; cpt->logic = L_SCRIPT; cpt->baseSub = (uint16)(scr & 0xffff); cpt->baseSub_off = (uint16)(scr >> 16); return true; } bool Logic::fnDiskMouse(uint32 a, uint32 b, uint32 c) { _skyMouse->spriteMouse(MOUSE_DISK, 11, 11); return true; } bool Logic::fnNormalMouse(uint32 a, uint32 b, uint32 c) { _skyMouse->spriteMouse(MOUSE_NORMAL, 0, 0); return true; } bool Logic::fnBlankMouse(uint32 a, uint32 b, uint32 c) { _skyMouse->spriteMouse(MOUSE_BLANK, 0, 0); return true; } bool Logic::fnCrossMouse(uint32 a, uint32 b, uint32 c) { if (_scriptVariables[OBJECT_HELD]) _skyMouse->fnOpenCloseHand(false); else _skyMouse->spriteMouse(MOUSE_CROSS, 4, 4); return true; } bool Logic::fnCursorRight(uint32 a, uint32 b, uint32 c) { _skyMouse->spriteMouse(MOUSE_RIGHT, 9, 4); return true; } bool Logic::fnCursorLeft(uint32 a, uint32 b, uint32 c) { _skyMouse->spriteMouse(MOUSE_LEFT, 0, 5); return true; } bool Logic::fnCursorDown(uint32 a, uint32 b, uint32 c) { _skyMouse->spriteMouse(MOUSE_DOWN, 9, 4); return true; } bool Logic::fnCursorUp(uint32 a, uint32 b, uint32 c) { _skyMouse->spriteMouse(MOUSE_UP, 9, 4); return true; } bool Logic::fnOpenHand(uint32 a, uint32 b, uint32 c) { _skyMouse->fnOpenCloseHand(true); return true; } bool Logic::fnCloseHand(uint32 a, uint32 b, uint32 c) { _skyMouse->fnOpenCloseHand(false); return true; } bool Logic::fnGetTo(uint32 targetPlaceId, uint32 mode, uint32 c) { _compact->upFlag = (uint16)mode; // save mode for action script _compact->mode += 4; // next level up Compact *cpt = _skyCompact->fetchCpt(_compact->place); if (!cpt) { warning("can't find _compact's getToTable. Place compact is NULL"); return false; } uint16 *getToTable = (uint16*)_skyCompact->fetchCpt(cpt->getToTableId); if (!getToTable) { warning("Place compact's getToTable is NULL!"); return false; } while (*getToTable != targetPlaceId) getToTable += 2; // get new script *SkyCompact::getSub(_compact, _compact->mode) = *(getToTable + 1); *SkyCompact::getSub(_compact, _compact->mode + 2) = 0; return false; // drop out of script } bool Logic::fnSetToStand(uint32 a, uint32 b, uint32 c) { _compact->mood = 1; // high level stood still _compact->grafixProgId = *(uint16*)_skyCompact->getCompactElem(_compact, C_STAND_UP + _compact->megaSet + _compact->dir * 4); _compact->grafixProgPos = 0; uint16 *standList = _skyCompact->getGrafixPtr(_compact); _compact->offset = *standList; // get frames offset _compact->logic = L_SIMPLE_MOD; _compact->grafixProgPos++; simpleAnim(); return false; // drop out of script } bool Logic::fnTurnTo(uint32 dir, uint32 b, uint32 c) { /// turn compact to direction dir uint16 curDir = _compact->dir; // get current direction _compact->dir = (uint16)(dir & 0xffff); // set new direction uint16 *tt = _skyCompact->getTurnTable(_compact, curDir); if (!tt[dir]) return true; // keep going _compact->turnProgId = tt[dir]; // put turn program in _compact->turnProgPos = 0; _compact->logic = L_TURNING; turn(); return false; // drop out of script } bool Logic::fnArrived(uint32 scriptVar, uint32 b, uint32 c) { _compact->leaving = (uint16)(scriptVar & 0xffff); _scriptVariables[scriptVar/4]++; return true; } bool Logic::fnLeaving(uint32 a, uint32 b, uint32 c) { _compact->atWatch = 0; if (_compact->leaving) { _scriptVariables[_compact->leaving/4]--; _compact->leaving = 0; // I shall do this only once } return true; // keep going } bool Logic::fnSetAlternate(uint32 scr, uint32 b, uint32 c) { _compact->alt = (uint16)(scr & 0xffff); _compact->logic = L_ALT; return false; } bool Logic::fnAltSetAlternate(uint32 target, uint32 scr, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(target); cpt->alt = (uint16)(scr & 0xffff); cpt->logic = L_ALT; return false; } bool Logic::fnKillId(uint32 id, uint32 b, uint32 c) { if (id) { Compact *cpt = _skyCompact->fetchCpt(id); if (cpt->status & (1 << 7)) _skyGrid->removeObjectFromWalk(cpt); cpt->status = 0; } return true; } bool Logic::fnNoHuman(uint32 a, uint32 b, uint32 c) { if (!_scriptVariables[MOUSE_STOP]) { _scriptVariables[MOUSE_STATUS] &= 1; runGetOff(); fnBlankMouse(0, 0, 0); } return true; } bool Logic::fnAddHuman(uint32 a, uint32 b, uint32 c) { return _skyMouse->fnAddHuman(); } bool Logic::fnAddButtons(uint32 a, uint32 b, uint32 c) { _scriptVariables[MOUSE_STATUS] |= 4; return true; } bool Logic::fnNoButtons(uint32 a, uint32 b, uint32 c) { //remove the mouse buttons _scriptVariables[MOUSE_STATUS] &= 0xFFFFFFFB; return true; } bool Logic::fnSetStop(uint32 a, uint32 b, uint32 c) { _scriptVariables[MOUSE_STOP] |= 1; return true; } bool Logic::fnClearStop(uint32 a, uint32 b, uint32 c) { _scriptVariables[MOUSE_STOP] = 0; return true; } bool Logic::fnPointerText(uint32 a, uint32 b, uint32 c) { _skyText->fnPointerText(a, _skyMouse->giveMouseX(), _skyMouse->giveMouseY()); return true; } bool Logic::fnQuit(uint32 a, uint32 b, uint32 c) { return false; } bool Logic::fnSpeakMe(uint32 targetId, uint32 mesgNum, uint32 animNum) { stdSpeak(_skyCompact->fetchCpt(targetId), mesgNum, animNum, 0); return false; //drop out of script } bool Logic::fnSpeakMeDir(uint32 targetId, uint32 mesgNum, uint32 animNum) { //must be player so don't cause script to drop out //this function sets the directional option whereby //the anim chosen is linked to c_dir animNum += _compact->dir << 1; //2 sizes (large and small) return fnSpeakMe(targetId, mesgNum, animNum); } bool Logic::fnSpeakWait(uint32 id, uint32 message, uint32 animation) { // non player mega char speaks // player will wait for it to finish before continuing script processing _compact->flag = (uint16)(id & 0xffff); _compact->logic = L_LISTEN; return fnSpeakMe(id, message, animation); } bool Logic::fnSpeakWaitDir(uint32 a, uint32 b, uint32 c) { /* non player mega chr$ speaks S2(20Jan93tw) the player will wait for it to finish before continuing script processing this function sets the directional option whereby the anim chosen is linked to c_dir - _compact is player a is ID to speak (not us) b is text message number c is base of mini table within anim_talk_table */ #ifdef __DC__ __builtin_alloca(4); // Works around a gcc bug (wrong-code/11736) #endif _compact->flag = (uint16)a; _compact->logic = L_LISTEN; Compact *speaker = _skyCompact->fetchCpt(a); if (c) { c += speaker->dir << 1; stdSpeak(speaker, b, c, speaker->dir << 1); } else stdSpeak(speaker, b, c, 0); return false; } bool Logic::fnChooser(uint32 a, uint32 b, uint32 c) { // setup the text questions to be clicked on // read from TEXT1 until 0 SkyEngine::_systemVars.systemFlags |= SF_CHOOSING; // can't save/restore while choosing _scriptVariables[THE_CHOSEN_ONE] = 0; // clear result uint32 *p = _scriptVariables + TEXT1; uint16 ycood = TOP_LEFT_Y; // rolling coordinate while (*p) { uint32 textNum = *p++; struct lowTextManager_t lowText = _skyText->lowTextManager(textNum, GAME_SCREEN_WIDTH, 0, 241, 0); uint8 *data = lowText.textData; // stipple the text uint32 size = ((dataFileHeader *)data)->s_height * ((dataFileHeader *)data)->s_width; uint32 index = 0; uint32 width = ((dataFileHeader *)data)->s_width; data += sizeof(dataFileHeader); while (index < size) { if (index % width <= 1) index ^= 1; //index++; if (!data[index]) data[index] = 1; index += 2; } Compact *textCompact = _skyCompact->fetchCpt(lowText.compactNum); textCompact->getToFlag = (uint16)textNum; textCompact->downFlag = (uint16)*p++; // get animation number textCompact->status |= ST_MOUSE; // mouse detects textCompact->xcood = TOP_LEFT_X; // set coordinates textCompact->ycood = ycood; ycood += 12; } if (p == _scriptVariables + TEXT1) return true; _compact->logic = L_CHOOSE; // player frozen until choice made fnAddHuman(0, 0, 0); // bring back mouse return false; } bool Logic::fnHighlight(uint32 itemNo, uint32 pen, uint32 c) { pen -= 11; pen ^= 1; pen += 241; Compact *textCompact = _skyCompact->fetchCpt(itemNo); uint8 *sprData = (uint8 *)SkyEngine::fetchItem(textCompact->flag); _skyText->changeTextSpriteColour(sprData, (uint8)pen); return true; } bool Logic::fnTextKill(uint32 a, uint32 b, uint32 c) { /// Kill of text items that are mouse detectable uint32 id = FIRST_TEXT_COMPACT; for (int i = 10; i > 0; i--) { Compact *cpt = _skyCompact->fetchCpt(id); if (cpt->status & (1 << 4)) cpt->status = 0; id++; } return true; } bool Logic::fnStopMode(uint32 a, uint32 b, uint32 c) { _compact->logic = L_STOPPED; return false; } bool Logic::fnWeWait(uint32 id, uint32 b, uint32 c) { /// We have hit another mega /// we are going to wait for it to move _compact->waitingFor = (uint16) id; stopAndWait(); return true; // not sure about this } bool Logic::fnSendSync(uint32 mega, uint32 sync, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(mega); cpt->sync = (uint16)(sync & 0xffff); return false; } bool Logic::fnSendFastSync(uint32 mega, uint32 sync, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(mega); cpt->sync = (uint16)(sync & 0xffff); return true; } bool Logic::fnSendRequest(uint32 target, uint32 scr, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(target); cpt->request = (uint16)(scr & 0xffff); return false; } bool Logic::fnClearRequest(uint32 target, uint32 b, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(target); cpt->request = 0; return true; } bool Logic::fnCheckRequest(uint32 a, uint32 b, uint32 c) { /// check for interaction request if (!_compact->request) return true; _compact->mode = C_ACTION_MODE; // into action mode _compact->actionSub = _compact->request; _compact->actionSub_off = 0; _compact->request = 0; // trash request return false; // drop from script } bool Logic::fnStartMenu(uint32 firstObject, uint32 b, uint32 c) { /// initialise the top menu bar // firstObject is o0 for game menu, k0 for linc uint i; firstObject /= 4; // (1) FIRST, SET UP THE 2 ARROWS SO THEY APPEAR ON SCREEN Compact *cpt = _skyCompact->fetchCpt(47); cpt->status = ST_MOUSE + ST_FOREGROUND + ST_LOGIC + ST_RECREATE; cpt->screen = (uint16)(_scriptVariables[SCREEN] & 0xffff); cpt = _skyCompact->fetchCpt(48); cpt->status = ST_MOUSE + ST_FOREGROUND + ST_LOGIC + ST_RECREATE; cpt->screen = (uint16)(_scriptVariables[SCREEN] & 0xffff); // (2) COPY OBJECTS FROM NON-ZERO INVENTORY VARIABLES INTO OBJECT DISPLAY LIST (& COUNT THEM) // sort the objects and pad with blanks uint32 menuLength = 0; for (i = firstObject; i < firstObject + ARRAYSIZE(_objectList); i++) { if ( _scriptVariables[i] ) _objectList[menuLength++] = _scriptVariables[i]; } _scriptVariables[MENU_LENGTH] = menuLength; // (3) OK, NOW TOP UP THE LIST WITH THE REQUIRED NO. OF BLANK OBJECTS (for min display length 11) uint32 blankID = 51; for (i = menuLength; i < 11; i++) _objectList[i] = blankID++; // (4) KILL ID's OF ALL 20 OBJECTS SO UNWANTED ICONS (SCROLLED OFF) DON'T REMAIN ON SCREEN // (There should be a better way of doing this - only kill id of 12th item when menu has scrolled right) for (i = 0; i < ARRAYSIZE(_objectList); i++) { if (_objectList[i]) (_skyCompact->fetchCpt(_objectList[i]))->status = ST_LOGIC; else break; } // (5) NOW FIND OUT WHICH OBJECT TO START THE DISPLAY FROM (depending on scroll offset) if (menuLength < 11) // check we can scroll _scriptVariables[SCROLL_OFFSET] = 0; else if (menuLength < _scriptVariables[SCROLL_OFFSET] + 11) _scriptVariables[SCROLL_OFFSET] = menuLength - 11; // (6) AND FINALLY, INITIALISE THE 11 OBJECTS SO THEY APPEAR ON SCREEEN uint16 rollingX = TOP_LEFT_X + 28; for (i = 0; i < 11; i++) { cpt = _skyCompact->fetchCpt( _objectList[_scriptVariables[SCROLL_OFFSET] + i]); cpt->status = ST_MOUSE + ST_FOREGROUND + ST_LOGIC + ST_RECREATE; cpt->screen = (uint16)(_scriptVariables[SCREEN] & 0xffff); cpt->xcood = rollingX; rollingX += 24; if (_scriptVariables[MENU] == 2) cpt->ycood = 136; else cpt->ycood = 112; } return true; } bool Logic::fnUnhighlight(uint32 item, uint32 b, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(item); cpt->frame--; cpt->getToFlag = 0; return true; } bool Logic::fnFaceId(uint32 otherId, uint32 b, uint32 c) { /// return the direction to turn to face another id /// pass back result in c_just_flag Compact *cpt = _skyCompact->fetchCpt(otherId); int16 x = _compact->xcood - cpt->xcood; if (x < 0) { // we're to the left x = -x; _compact->getToFlag = 3; } else { // it's to the left _compact->getToFlag = 2; } // now check y // we must find the true bottom of the sprite // it is not enough to use y coord because changing // sprite offsets can ruin the formula - instead we // will use the bottom of the mouse collision area int16 y = _compact->ycood - (cpt->ycood + cpt->mouseRelY + cpt->mouseSizeY); if (y < 0) { // it's below y = -y; if (y >= x) _compact->getToFlag = 1; } else { // it's above if (y >= x) _compact->getToFlag = 0; } return true; } bool Logic::fnForeground(uint32 sprite, uint32 b, uint32 c) { /// Make sprite a foreground sprite Compact *cpt = _skyCompact->fetchCpt(sprite); cpt->status &= 0xfff8; cpt->status |= ST_FOREGROUND; return true; } bool Logic::fnBackground(uint32 a, uint32 b, uint32 c) { /// Make us a background sprite _compact->status &= 0xfff8; _compact->status |= ST_BACKGROUND; return true; } bool Logic::fnNewBackground(uint32 sprite, uint32 b, uint32 c) { /// Make sprite a background sprite Compact *cpt = _skyCompact->fetchCpt(sprite); cpt->status &= 0xfff8; cpt->status |= ST_BACKGROUND; return true; } bool Logic::fnSort(uint32 mega, uint32 b, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(mega); cpt->status &= 0xfff8; cpt->status |= ST_SORT; return true; } bool Logic::fnNoSpriteEngine(uint32 a, uint32 b, uint32 c) { /// stop the compact printing /// remove foreground, background & sort _compact->status &= 0xfff8; return true; } bool Logic::fnNoSpritesA6(uint32 us, uint32 b, uint32 c) { /// stop the compact printing /// remove foreground, background & sort Compact *cpt = _skyCompact->fetchCpt(us); cpt->status &= 0xfff8; return true; } bool Logic::fnResetId(uint32 id, uint32 resetBlock, uint32 c) { /// used when a mega is to be restarted /// eg - when a smaller mega turn to larger /// - a mega changes rooms... Compact *cpt = _skyCompact->fetchCpt(id); uint16 *rst = (uint16 *)_skyCompact->fetchCpt(resetBlock); if (!cpt) { warning("fnResetId(): Compact %d (id) == NULL",id); return true; } if (!rst) { warning("fnResetId(): Compact %d (resetBlock) == NULL",resetBlock); return true; } uint16 off; while ((off = *rst++) != 0xffff) *(uint16 *)_skyCompact->getCompactElem(cpt, off) = *rst++; return true; } bool Logic::fnToggleGrid(uint32 a, uint32 b, uint32 c) { /// Toggle a mega's grid plotting _compact->status ^= ST_GRID_PLOT; return true; } bool Logic::fnPause(uint32 cycles, uint32 b, uint32 c) { /// Set mega to L_PAUSE _compact->flag = (uint16)(cycles & 0xffff); _compact->logic = L_PAUSE; return false; // drop out of script } bool Logic::fnRunAnimMod(uint32 animNo, uint32 b, uint32 c) { _compact->grafixProgId = animNo; _compact->grafixProgPos = 0; _compact->offset = *_skyCompact->getGrafixPtr(_compact); _compact->grafixProgPos++; _compact->logic = L_MOD_ANIMATE; anim(); return false; // drop from script } bool Logic::fnSimpleMod(uint32 animSeqNo, uint32 b, uint32 c) { _compact->grafixProgId = animSeqNo; _compact->grafixProgPos = 0; _compact->logic = L_SIMPLE_MOD; _compact->offset = *_skyCompact->getGrafixPtr(_compact); _compact->grafixProgPos++; simpleAnim(); return false; } bool Logic::fnRunFrames(uint32 sequenceNo, uint32 b, uint32 c) { _compact->grafixProgId = sequenceNo; _compact->grafixProgPos = 0; _compact->logic = L_FRAMES; _compact->offset = *_skyCompact->getGrafixPtr(_compact); _compact->grafixProgPos++; simpleAnim(); return false; } bool Logic::fnAwaitSync(uint32 a, uint32 b, uint32 c) { if (_compact->sync) return true; _compact->logic = L_WAIT_SYNC; return false; } bool Logic::fnIncMegaSet(uint32 a, uint32 b, uint32 c) { _compact->megaSet += NEXT_MEGA_SET; return true; } bool Logic::fnDecMegaSet(uint32 a, uint32 b, uint32 c) { _compact->megaSet -= NEXT_MEGA_SET; return true; } bool Logic::fnSetMegaSet(uint32 mega, uint32 setNo, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(mega); cpt->megaSet = (uint16) (setNo * NEXT_MEGA_SET); return true; } bool Logic::fnMoveItems(uint32 listNo, uint32 screenNo, uint32 c) { // Move a list of id's to another screen uint16 *p = (uint16*)_skyCompact->fetchCpt(CPT_MOVE_LIST); p = (uint16*)_skyCompact->fetchCpt(p[listNo]); for (int i = 0; i < 2; i++) { if (!*p) return true; Compact *cpt = _skyCompact->fetchCpt(*p++); cpt->screen = (uint16)(screenNo & 0xffff); } return true; } bool Logic::fnNewList(uint32 a, uint32 b, uint32 c) { /// Reset the chooser list for (int i = 0; i < 16; i++) _scriptVariables[TEXT1 + i] = 0; return true; } bool Logic::fnAskThis(uint32 textNo, uint32 animNo, uint32 c) { // find first free position uint32 *p = _scriptVariables + TEXT1; while (*p) p += 2; *p++ = textNo; *p = animNo; return true; } bool Logic::fnRandom(uint32 a, uint32 b, uint32 c) { _scriptVariables[RND] = _rnd.getRandomNumber(65536) & a; return true; } bool Logic::fnPersonHere(uint32 id, uint32 room, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(id); _scriptVariables[RESULT] = cpt->screen == room ? 1 : 0; return true; } bool Logic::fnToggleMouse(uint32 a, uint32 b, uint32 c) { _skyCompact->fetchCpt(a)->status ^= ST_MOUSE; return true; } bool Logic::fnMouseOn(uint32 a, uint32 b, uint32 c) { //switch on the mouse highlight Compact *cpt = _skyCompact->fetchCpt(a); cpt->status |= ST_MOUSE; return true; } bool Logic::fnMouseOff(uint32 a, uint32 b, uint32 c) { //switch off the mouse highlight Compact *cpt = _skyCompact->fetchCpt(a); cpt->status &= ~ST_MOUSE; return true; } bool Logic::fnFetchX(uint32 id, uint32 b, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(id); _scriptVariables[RESULT] = cpt->xcood; return true; } bool Logic::fnFetchY(uint32 id, uint32 b, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(id); _scriptVariables[RESULT] = cpt->ycood; return true; } bool Logic::fnTestList(uint32 id, uint32 x, uint32 y) { _scriptVariables[RESULT] = 0; // assume fail uint16 *list = (uint16 *)_skyCompact->fetchCpt(id); while (*list) { if ((x >= list[0]) && (x < list[1]) && (y >= list[2]) && (y < list[3])) _scriptVariables[RESULT] = list[4]; list += 5; } return true; } bool Logic::fnFetchPlace(uint32 id, uint32 b, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(id); _scriptVariables[RESULT] = cpt->place; return true; } bool Logic::fnCustomJoey(uint32 id, uint32 b, uint32 c) { /// return id's x & y coordinate & c_mood (i.e. stood still yes/no) /// used by Joey-Logic - done in code like this because scripts can't /// get access to another megas compact as easily Compact *cpt = _skyCompact->fetchCpt(id); _scriptVariables[PLAYER_X] = cpt->xcood; _scriptVariables[PLAYER_Y] = cpt->ycood; _scriptVariables[PLAYER_MOOD] = cpt->mood; _scriptVariables[PLAYER_SCREEN] = cpt->screen; return true; } bool Logic::fnSetPalette(uint32 a, uint32 b, uint32 c) { _skyScreen->setPaletteEndian((uint8 *)_skyCompact->fetchCpt(a)); SkyEngine::_systemVars.currentPalette = a; return true; } bool Logic::fnTextModule(uint32 a, uint32 b, uint32 c) { _skyText->fnTextModule(a, b); return true; } bool Logic::fnChangeName(uint32 id, uint32 textNo, uint32 c) { Compact *cpt = _skyCompact->fetchCpt(id); cpt->cursorText = (uint16) textNo; return true; } bool Logic::fnMiniLoad(uint32 a, uint32 b, uint32 c) { _skyDisk->fnMiniLoad((uint16)a); return true; } bool Logic::fnFlushBuffers(uint32 a, uint32 b, uint32 c) { _skyDisk->fnFlushBuffers(); return true; } bool Logic::fnFlushChip(uint32 a, uint32 b, uint32 c) { // this should be done automatically return true; } bool Logic::fnSaveCoods(uint32 a, uint32 b, uint32 c) { _skyMouse->fnSaveCoods(); return true; } bool Logic::fnPlotGrid(uint32 x, uint32 y, uint32 width) { _skyGrid->plotGrid(x, y, width, _compact); return true; } bool Logic::fnRemoveGrid(uint32 x, uint32 y, uint32 width) { _skyGrid->removeGrid(x, y, width, _compact); return true; } bool Logic::fnEyeball(uint32 id, uint32 b, uint32 c) { // set 'result' to frame no. pointing to foster, according to table used // eg. FN_eyeball (id_eye_90_table); uint16 *eyeTable = (uint16 *)_skyCompact->fetchCpt(id); Compact *cpt = _skyCompact->fetchCpt(ID_BLUE_FOSTER); uint32 x = cpt->xcood; // 168 < x < 416 x -= 168; x >>= 3; uint32 y = cpt->ycood; // 256 < y < 296 y -= 256; y <<= 2; _scriptVariables[RESULT] = eyeTable[x + y] + S91; return true; } bool Logic::fnLeaveSection(uint32 sectionNo, uint32 b, uint32 c) { if (SkyEngine::isDemo()) _skyControl->showGameQuitMsg(); if (sectionNo == 5) //linc section - has different mouse icons _skyMouse->replaceMouseCursors(60301); return true; } bool Logic::fnEnterSection(uint32 sectionNo, uint32 b, uint32 c) { if (SkyEngine::isDemo() && (sectionNo > 2)) _skyControl->showGameQuitMsg(); _scriptVariables[CUR_SECTION] = sectionNo; SkyEngine::_systemVars.currentMusic = 0; if (sectionNo == 5) //linc section - has different mouse icons _skyMouse->replaceMouseCursors(60302); if ((sectionNo != _currentSection) || (SkyEngine::_systemVars.systemFlags & SF_GAME_RESTORED)) { _currentSection = sectionNo; sectionNo++; _skyMusic->loadSection((byte)sectionNo); _skySound->loadSection((byte)sectionNo); _skyGrid->loadGrids(); SkyEngine::_systemVars.systemFlags &= ~SF_GAME_RESTORED; } return true; } bool Logic::fnRestoreGame(uint32 a, uint32 b, uint32 c) { _skyControl->doLoadSavePanel(); return false; } bool Logic::fnRestartGame(uint32 a, uint32 b, uint32 c) { _skyControl->restartGame(); return false; } bool Logic::fnNewSwingSeq(uint32 a, uint32 b, uint32 c) { // only certain files work on pc. (huh?! something we should take care of?) if ((a == 85) || (a == 106) || (a == 75) || (a == 15)) { _skyScreen->startSequenceItem((uint16)a); } else { debug(1,"Logic::fnNewSwingSeq: ignored seq %d",a); } return true; } bool Logic::fnWaitSwingEnd(uint32 a, uint32 b, uint32 c) { _skyScreen->waitForSequence(); return true; } bool Logic::fnSkipIntroCode(uint32 a, uint32 b, uint32 c) { SkyEngine::_systemVars.pastIntro = true; return true; } bool Logic::fnBlankScreen(uint32 a, uint32 b, uint32 c) { _skyScreen->clearScreen(); return true; } bool Logic::fnPrintCredit(uint32 a, uint32 b, uint32 c) { lowTextManager_t creditText = _skyText->lowTextManager(a, 240, 0, 248, true); Compact *credCompact = _skyCompact->fetchCpt(creditText.compactNum); credCompact->xcood = 168; if ((a == 558) && (c == 215)) credCompact->ycood = 211; else credCompact->ycood = (uint16)c; _scriptVariables[RESULT] = creditText.compactNum; return true; } bool Logic::fnLookAt(uint32 a, uint32 b, uint32 c) { struct lowTextManager_t textInfo = _skyText->lowTextManager(a, 240, 0, 248, true); Compact *textCpt = _skyCompact->fetchCpt(textInfo.compactNum); textCpt->xcood = 168; textCpt->ycood = (uint16)c; _skyScreen->recreate(); _skyScreen->spriteEngine(); _skyScreen->flip(); fnNoHuman(0, 0, 0); _skyMouse->lockMouse(); _skyMouse->waitMouseNotPressed(); _skyMouse->unlockMouse(); fnAddHuman(0, 0, 0); textCpt->status = 0; return true; } bool Logic::fnLincTextModule(uint32 textPos, uint32 textNo, uint32 buttonAction) { uint16 cnt; if (buttonAction & 0x8000) for (cnt = LINC_DIGIT_0; cnt <= LINC_DIGIT_9; cnt++) _scriptVariables[cnt] = 0; buttonAction &= 0x7FFF; if (buttonAction < 10) _scriptVariables[LINC_DIGIT_0 + buttonAction] = textNo; lowTextManager_t text = _skyText->lowTextManager(textNo, 220, 0, 215, false); Compact *textCpt = _skyCompact->fetchCpt(text.compactNum); if (textPos < 20) { // line number (for text) textCpt->xcood = 152; textCpt->ycood = (uint16)textPos * 13 + 170; } else if (textPos > 20) { // x coordinate (for numbers) textCpt->xcood = (uint16)textPos; textCpt->ycood = 214; } else warning("::fnLincTextModule: textPos == 20"); textCpt->getToFlag = (uint16)textNo; return true; } bool Logic::fnTextKill2(uint32 a, uint32 b, uint32 c) { /// Kill all text items uint32 id = FIRST_TEXT_COMPACT; for (int i = 10; i > 0; i--) { Compact *cpt = _skyCompact->fetchCpt(id); cpt->status = 0; id++; } return true; } bool Logic::fnSetFont(uint32 font, uint32 b, uint32 c) { _skyText->fnSetFont(font); return true; } bool Logic::fnStartFx(uint32 sound, uint32 b, uint32 c) { _skySound->fnStartFx(sound, (uint8)(b & 1)); return true; } bool Logic::fnStopFx(uint32 a, uint32 b, uint32 c) { _skySound->fnStopFx(); return true; } bool Logic::fnStartMusic(uint32 a, uint32 b, uint32 c) { if (!(SkyEngine::_systemVars.systemFlags & SF_MUS_OFF)) _skyMusic->startMusic((uint16)a); SkyEngine::_systemVars.currentMusic = (uint16)a; return true; } bool Logic::fnStopMusic(uint32 a, uint32 b, uint32 c) { _skyMusic->startMusic(0); SkyEngine::_systemVars.currentMusic = 0; return true; } bool Logic::fnFadeDown(uint32 a, uint32 b, uint32 c) { _skyScreen->fnFadeDown(a); return true; } bool Logic::fnFadeUp(uint32 a, uint32 b, uint32 c) { SkyEngine::_systemVars.currentPalette = a; _skyScreen->fnFadeUp(a,b); return true; } bool Logic::fnQuitToDos(uint32 a, uint32 b, uint32 c) { _skyControl->showGameQuitMsg(); // calls _system->quit() return true; } bool Logic::fnPauseFx(uint32 a, uint32 b, uint32 c) { _skySound->fnPauseFx(); return true; } bool Logic::fnUnPauseFx(uint32 a, uint32 b, uint32 c) { _skySound->fnUnPauseFx(); return true; } bool Logic::fnPrintf(uint32 a, uint32 b, uint32 c) { printf("fnPrintf: %d\n", a); return true; } void Logic::stdSpeak(Compact *target, uint32 textNum, uint32 animNum, uint32 base) { animNum += target->megaSet / NEXT_MEGA_SET; animNum &= 0xFF; uint16 *talkTable = (uint16*)_skyCompact->fetchCpt(CPT_TALK_TABLE_LIST); target->grafixProgId = talkTable[animNum]; target->grafixProgPos = 0; uint16 *animPtr = _skyCompact->getGrafixPtr(target); if (animPtr) { target->offset = *animPtr++; target->getToFlag = *animPtr++; target->grafixProgPos += 2; } else { target->grafixProgId = 0; } bool speechUsed = false; // startSpeech returns false if no speech file exists for that text if (SkyEngine::isCDVersion()) speechUsed = _skySound->startSpeech((uint16)textNum); // if sky is configured to speech-only return now - except if we're running another // language than english if (speechUsed && (!(SkyEngine::_systemVars.systemFlags & SF_ALLOW_TEXT))) { target->spTime = 10; target->logic = L_TALK; return; } //now form the text sprite struct lowTextManager_t textInfo; textInfo = _skyText->lowTextManager(textNum, FIXED_TEXT_WIDTH, 0, (uint8)target->spColour, true); Compact *textCompact = _skyCompact->fetchCpt(textInfo.compactNum); target->spTextId = textInfo.compactNum; //So we know what text to kill byte *textGfx = textInfo.textData; //create the x coordinate for the speech text //we need the talkers sprite information textCompact->screen = target->screen; //put our screen in if (_scriptVariables[SCREEN] == target->screen) { // Only use coordinates if we are on the current screen //talking on-screen byte *targetGfx = (byte *)SkyEngine::fetchItem(target->frame >> 6); uint16 xPos = target->xcood + ((struct dataFileHeader *)targetGfx)->s_offset_x; uint16 width = (((struct dataFileHeader *)targetGfx)->s_width >> 1); xPos += width - (FIXED_TEXT_WIDTH / 2); //middle of talker if (xPos < TOP_LEFT_X) xPos = TOP_LEFT_X; width = xPos + FIXED_TEXT_WIDTH; if ((TOP_LEFT_X + FULL_SCREEN_WIDTH) <= width) { xPos = TOP_LEFT_X + FULL_SCREEN_WIDTH; xPos -= FIXED_TEXT_WIDTH; } textCompact->xcood = xPos; uint16 yPos = target->ycood + ((struct dataFileHeader *)targetGfx)->s_offset_y - 6 - ((struct dataFileHeader *)textGfx)->s_height; if (yPos < TOP_LEFT_Y) yPos = TOP_LEFT_Y; textCompact->ycood = yPos; } else { //talking off-screen target->spTextId = 0; //don't kill any text 'cos none was made textCompact->status = 0; //don't display text } // In CD version, we're doing the timing by checking when the VOC has stopped playing. // Setting spTime to 10 thus means that we're doing a pause of 10 gamecycles between // each sentence. if (speechUsed) target->spTime = 10; else target->spTime = (uint16)_skyText->_dtLetters + 5; target->logic = L_TALK; } } // End of namespace Sky