/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ // Scripting module private header #ifndef SAGA_SCRIPT_H #define SAGA_SCRIPT_H #include "common/endian.h" #include "saga/font.h" namespace Saga { #define COMMON_BUFFER_SIZE 1024 // Why 1024? #define SCRIPT_TBLENTRY_LEN 4 #define SCRIPT_MAX 5000 #define ITE_SCRIPT_FUNCTION_MAX 78 #define IHNM_SCRIPT_FUNCTION_MAX 105 enum AddressTypes { kAddressCommon = 0, // offset from global variables kAddressStatic = 1, // offset from global variables kAddressModule = 2, // offset from start of module kAddressStack = 3, // offset from stack kAddressThread = 4 // offset from thread structure /* kAddressId = 5, // offset from const id object kAddressIdIndirect = 6, // offset from stack id object kAddressIndex = 7 // index from id*/ }; enum VerbTypes { kVerbNone, kVerbWalkTo, kVerbGive, kVerbUse, kVerbEnter, kVerbLookAt, kVerbPickUp, kVerbOpen, kVerbClose, kVerbTalkTo, kVerbWalkOnly, kVerbLookOnly, kVerbOptions }; #define STHREAD_TIMESLICE 8 enum ThreadVarTypes { kThreadVarObject = 0, kThreadVarWithObject = 1, kThreadVarAction = 2, kThreadVarActor = 3, kThreadVarMax = kThreadVarActor + 1 }; enum ThreadFlags { kTFlagNone = 0, kTFlagWaiting = 1, // wait for even denoted in waitType kTFlagFinished = 2, kTFlagAborted = 4, kTFlagAsleep = kTFlagWaiting | kTFlagFinished | kTFlagAborted // Combination of all flags which can halt a thread }; enum ThreadWaitTypes { kWaitTypeNone = 0, // waiting for nothing kWaitTypeDelay = 1, // waiting for a timer kWaitTypeSpeech = 2, // waiting for speech to finish kWaitTypeDialogEnd = 3, // waiting for my dialog to finish kWaitTypeDialogBegin = 4, // waiting for other dialog to finish kWaitTypeWalk = 5, // waiting to finish walking kWaitTypeRequest = 6, // a request is up kWaitTypePause = 7, kWaitTypePlacard = 8, kWaitTypeStatusTextInput = 9, kWaitTypeWaitFrames = 10, // IHNM. waiting for a frame count kWaitTypeWakeUp = 11 // IHNM. wait until get waken up }; enum CycleFlags { kCyclePong = 1 << 0, kCycleOnce = 1 << 1, kCycleRandom = 1 << 2, kCycleReverse = 1 << 3 }; enum WalkFlags { kWalkBackPedal = 1 << 0, kWalkAsync = 1 << 1, kWalkUseAngle = 1 << 2, kWalkFace = 1 << 5 }; enum ReplyFlags { kReplyOnce = 1 << 0, kReplySummary = 1 << 1, kReplyCondition = 1 << 2 }; struct EntryPoint { uint16 nameOffset; uint16 offset; }; typedef Common::Array<uint16> VoiceLUT; struct ModuleData { bool loaded; // is it loaded or not? int scriptResourceId; int stringsResourceId; int voicesResourceId; ByteArray moduleBase; // all base module uint16 staticSize; // size of static data uint staticOffset; // offset of static data begining in _commonBuffer Common::Array<EntryPoint> entryPoints; StringsTable strings; VoiceLUT voiceLUT; void clear() { loaded = false; strings.clear(); voiceLUT.clear(); moduleBase.clear(); entryPoints.clear(); } ModuleData() : loaded(false), scriptResourceId(0), stringsResourceId(0), voicesResourceId(0), staticSize(0), staticOffset(0) { } }; class ScriptThread { public: Common::Array<int16> _stackBuf; uint16 _stackTopIndex; uint16 _frameIndex; uint16 _threadVars[kThreadVarMax]; byte *_moduleBase; // uint16 _moduleBaseSize; byte *_commonBase; // byte *_staticBase; // VoiceLUT *_voiceLUT; // StringsTable *_strings; // int _flags; // ThreadFlags int _waitType; // ThreadWaitTypes uint _sleepTime; void *_threadObj; // which object we're handling int16 _returnValue; uint16 _instructionOffset; // Instruction offset int32 _frameWait; enum { THREAD_STACK_SIZE = 256 }; public: byte *baseAddress(byte addrMode) { switch (addrMode) { case kAddressCommon: return _commonBase; case kAddressStatic: return _staticBase; case kAddressModule: return _moduleBase; case kAddressStack: return (byte *)&_stackBuf[_frameIndex]; case kAddressThread: return (byte *)_threadVars; default: return _commonBase; } } int16 stackTop() { return _stackBuf[_stackTopIndex]; } uint pushedSize() { return THREAD_STACK_SIZE - _stackTopIndex - 2; } void push(int16 value) { if (_stackTopIndex <= 0) { error("ScriptThread::push() stack overflow"); } _stackBuf[--_stackTopIndex] = value; } int16 pop() { if (_stackTopIndex >= THREAD_STACK_SIZE) { error("ScriptThread::pop() stack underflow"); } return _stackBuf[_stackTopIndex++]; } // wait stuff void wait(int waitType) { _waitType = waitType; _flags |= kTFlagWaiting; } void waitWalk(void *threadObj) { debug(3, "waitWalk()"); wait(kWaitTypeWalk); _threadObj = threadObj; } void waitDelay(int sleepTime) { debug(3, "waitDelay(%d)", sleepTime); wait(kWaitTypeDelay); _sleepTime = sleepTime; } void waitFrames(int frames) { debug(3, "waitFrames(%d)", frames); wait(kWaitTypeWaitFrames); _frameWait = frames; } ScriptThread() { memset(&_frameIndex, 0xFE, sizeof(_frameIndex)); memset(_threadVars, 0xFE, sizeof(_threadVars)); memset(&_waitType, 0xFE, sizeof(_waitType)); memset(&_sleepTime, 0xFE, sizeof(_sleepTime)); memset(&_threadObj, 0xFE, sizeof(_threadObj)); memset(&_returnValue, 0xFE, sizeof(_threadObj)); memset(&_frameWait, 0xFE, sizeof(_frameWait)); _flags = kTFlagNone; } }; typedef Common::List<ScriptThread> ScriptThreadList; #define SCRIPTOP_PARAMS ScriptThread *thread, Common::SeekableReadStream *scriptS, bool &stopParsing, bool &breakOut #define SCRIPTFUNC_PARAMS ScriptThread *thread, int nArgs, bool &disContinue #define OPCODE(x) {&Script::x, #x} class Script { public: StringsTable _mainStrings; Script(SagaEngine *vm); virtual ~Script(); void loadModule(uint scriptModuleNumber); void clearModules(); void doVerb(); void showVerb(int statusColor = -1); void setVerb(int verb); int getCurrentVerb() const { return _currentVerb; } void setPointerVerb(); void whichObject(const Point& mousePoint); void hitObject(bool leftButton); void playfieldClick(const Point& mousePoint, bool leftButton); void setLeftButtonVerb(int verb); int getLeftButtonVerb() const { return _leftButtonVerb; } void setRightButtonVerb(int verb); int getRightButtonVerb() const { return _rightButtonVerb; } void setNonPlayfieldVerb() { setRightButtonVerb(getVerbType(kVerbNone)); _pointerObject = ID_NOTHING; _currentObject[_firstObjectSet ? 1 : 0] = ID_NOTHING; } void setNoPendingVerb() { _pendingVerb = getVerbType(kVerbNone); _currentObject[0] = _currentObject[1] = ID_NOTHING; setPointerVerb(); } int getVerbType(VerbTypes verbType); TextListEntry *getPlacardTextEntry() { return _placardTextEntry; } bool isNonInteractiveDemo(); protected: // When reading or writing data to the common buffer, we have to use a // well-defined byte order since it's stored in savegames. Otherwise, // we use native byte ordering since that data may be accessed in other // ways than through these functions. // // The "module" area is a special case, which possibly never ever // happens. But if it does, we need well-defined byte ordering. uint16 readUint16(byte *addr, byte addrMode) { switch (addrMode) { case kAddressCommon: case kAddressStatic: case kAddressModule: return READ_LE_UINT16(addr); default: return READ_UINT16(addr); } } void writeUint16(byte *addr, uint16 value, byte addrMode) { switch (addrMode) { case kAddressCommon: case kAddressStatic: case kAddressModule: WRITE_LE_UINT16(addr, value); break; default: WRITE_UINT16(addr, value); break; } } SagaEngine *_vm; ResourceContext *_scriptContext; ResourceContext *_dataContext; uint16 _modulesLUTEntryLen; Common::Array<ModuleData> _modules; TextListEntry *_placardTextEntry; friend class SagaEngine; ByteArray _commonBuffer; uint _staticSize; ScriptThreadList _threadList; ScriptThread *_conversingThread; //verb bool _firstObjectSet; bool _secondObjectNeeded; uint16 _currentObject[2]; int16 _currentObjectFlags[2]; int _currentVerb; int _stickyVerb; int _leftButtonVerb; int _rightButtonVerb; int _ihnmDemoCurrentY; public: uint16 _pendingObject[2]; int _pendingVerb; uint16 _pointerObject; bool _skipSpeeches; bool _abortEnabled; VoiceLUT _globalVoiceLUT; public: ScriptThread &createThread(uint16 scriptModuleNumber, uint16 scriptEntryPointNumber); int executeThread(ScriptThread *thread, int entrypointNumber); void executeThreads(uint msec); void completeThread(); void abortAllThreads(); void wakeUpActorThread(int waitType, void *threadObj); void wakeUpThreads(int waitType); void wakeUpThreadsDelayed(int waitType, int sleepTime); void loadVoiceLUT(VoiceLUT &voiceLUT, const ByteArray &resourceData); protected: void loadModuleBase(ModuleData &module, const ByteArray &resourceData); // runThread returns true if we should break running of other threads bool runThread(ScriptThread &thread); void setThreadEntrypoint(ScriptThread *thread, int entrypointNumber); public: void finishDialog(int strID, int replyID, int flags, int bitOffset); protected: // Script opcodes ------------------------------------------------------------ typedef void (Script::*ScriptOpType)(SCRIPTOP_PARAMS); struct ScriptOpDescription { ScriptOpType scriptOp; const char *scriptOpName; }; const ScriptOpDescription *_scriptOpsList; void setupScriptOpcodeList(); void opDummy(SCRIPTOP_PARAMS) { warning("Dummy opcode called"); } void opNextBlock(SCRIPTOP_PARAMS) { thread->_instructionOffset = (((thread->_instructionOffset) >> 10) + 1) << 10; } void opDup(SCRIPTOP_PARAMS); void opDrop(SCRIPTOP_PARAMS); void opZero(SCRIPTOP_PARAMS); void opOne(SCRIPTOP_PARAMS); void opConstInt(SCRIPTOP_PARAMS); void opStrLit(SCRIPTOP_PARAMS); void opGetFlag(SCRIPTOP_PARAMS); void opGetByte(SCRIPTOP_PARAMS); // SAGA 2 void opGetInt(SCRIPTOP_PARAMS); void opPutFlag(SCRIPTOP_PARAMS); void opPutByte(SCRIPTOP_PARAMS); // SAGA 2 void opPutInt(SCRIPTOP_PARAMS); void opPutFlagV(SCRIPTOP_PARAMS); void opPutByteV(SCRIPTOP_PARAMS); void opPutIntV(SCRIPTOP_PARAMS); void opCall(SCRIPTOP_PARAMS); // SAGA 1 void opCallNear(SCRIPTOP_PARAMS); // SAGA 2 void opCallFar(SCRIPTOP_PARAMS); // SAGA 2 void opCcall(SCRIPTOP_PARAMS); void opCcallV(SCRIPTOP_PARAMS); void opCallMember(SCRIPTOP_PARAMS); // SAGA 2 void opCallMemberV(SCRIPTOP_PARAMS); // SAGA 2 void opEnter(SCRIPTOP_PARAMS); void opReturn(SCRIPTOP_PARAMS); void opReturnV(SCRIPTOP_PARAMS); void opJmp(SCRIPTOP_PARAMS); void opJmpTrueV(SCRIPTOP_PARAMS); void opJmpFalseV(SCRIPTOP_PARAMS); void opJmpTrue(SCRIPTOP_PARAMS); void opJmpFalse(SCRIPTOP_PARAMS); void opJmpSwitch(SCRIPTOP_PARAMS); void opJmpRandom(SCRIPTOP_PARAMS); void opNegate(SCRIPTOP_PARAMS); void opNot(SCRIPTOP_PARAMS); void opCompl(SCRIPTOP_PARAMS); void opIncV(SCRIPTOP_PARAMS); void opDecV(SCRIPTOP_PARAMS); void opPostInc(SCRIPTOP_PARAMS); void opPostDec(SCRIPTOP_PARAMS); void opAdd(SCRIPTOP_PARAMS); void opSub(SCRIPTOP_PARAMS); void opMul(SCRIPTOP_PARAMS); void opDiv(SCRIPTOP_PARAMS); void opMod(SCRIPTOP_PARAMS); void opEq(SCRIPTOP_PARAMS); void opNe(SCRIPTOP_PARAMS); void opGt(SCRIPTOP_PARAMS); void opLt(SCRIPTOP_PARAMS); void opGe(SCRIPTOP_PARAMS); void opLe(SCRIPTOP_PARAMS); void opRsh(SCRIPTOP_PARAMS); void opLsh(SCRIPTOP_PARAMS); void opAnd(SCRIPTOP_PARAMS); void opOr(SCRIPTOP_PARAMS); void opXor(SCRIPTOP_PARAMS); void opLAnd(SCRIPTOP_PARAMS); void opLOr(SCRIPTOP_PARAMS); void opLXor(SCRIPTOP_PARAMS); void opSpeak(SCRIPTOP_PARAMS); void opDialogBegin(SCRIPTOP_PARAMS); void opDialogEnd(SCRIPTOP_PARAMS); void opReply(SCRIPTOP_PARAMS); void opAnimate(SCRIPTOP_PARAMS); void opJmpSeedRandom(SCRIPTOP_PARAMS); // Script functions ---------------------------------------------------------- typedef void (Script::*ScriptFunctionType)(SCRIPTFUNC_PARAMS); struct ScriptFunctionDescription { ScriptFunctionType scriptFunction; const char *scriptFunctionName; }; const ScriptFunctionDescription *_scriptFunctionsList; void setupITEScriptFuncList(); void setupIHNMScriptFuncList(); void sfPutString(SCRIPTFUNC_PARAMS); void sfWait(SCRIPTFUNC_PARAMS); void sfTakeObject(SCRIPTFUNC_PARAMS); void sfIsCarried(SCRIPTFUNC_PARAMS); void sfStatusBar(SCRIPTFUNC_PARAMS); void sfMainMode(SCRIPTFUNC_PARAMS); void sfScriptWalkTo(SCRIPTFUNC_PARAMS); void sfScriptDoAction(SCRIPTFUNC_PARAMS); void sfSetActorFacing(SCRIPTFUNC_PARAMS); void sfStartBgdAnim(SCRIPTFUNC_PARAMS); void sfStopBgdAnim(SCRIPTFUNC_PARAMS); void sfLockUser(SCRIPTFUNC_PARAMS); void sfPreDialog(SCRIPTFUNC_PARAMS); void sfKillActorThreads(SCRIPTFUNC_PARAMS); void sfFaceTowards(SCRIPTFUNC_PARAMS); void sfSetFollower(SCRIPTFUNC_PARAMS); void sfScriptGotoScene(SCRIPTFUNC_PARAMS); void sfSetObjImage(SCRIPTFUNC_PARAMS); void sfSetObjName(SCRIPTFUNC_PARAMS); void sfGetObjImage(SCRIPTFUNC_PARAMS); void sfGetNumber(SCRIPTFUNC_PARAMS); void sfScriptOpenDoor(SCRIPTFUNC_PARAMS); void sfScriptCloseDoor(SCRIPTFUNC_PARAMS); void sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS); void sfCycleColors(SCRIPTFUNC_PARAMS); void sfDoCenterActor(SCRIPTFUNC_PARAMS); void sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS); void sfScriptWalkToAsync(SCRIPTFUNC_PARAMS); void sfEnableZone(SCRIPTFUNC_PARAMS); void sfSetActorState(SCRIPTFUNC_PARAMS); void sfScriptMoveTo(SCRIPTFUNC_PARAMS); void sfSceneEq(SCRIPTFUNC_PARAMS); void sfDropObject(SCRIPTFUNC_PARAMS); void sfFinishBgdAnim(SCRIPTFUNC_PARAMS); void sfSwapActors(SCRIPTFUNC_PARAMS); void sfSimulSpeech(SCRIPTFUNC_PARAMS); void sfScriptWalk(SCRIPTFUNC_PARAMS); void sfCycleFrames(SCRIPTFUNC_PARAMS); void sfSetFrame(SCRIPTFUNC_PARAMS); void sfSetPortrait(SCRIPTFUNC_PARAMS); void sfSetProtagPortrait(SCRIPTFUNC_PARAMS); void sfChainBgdAnim(SCRIPTFUNC_PARAMS); void sfScriptSpecialWalk(SCRIPTFUNC_PARAMS); void sfPlaceActor(SCRIPTFUNC_PARAMS); void sfCheckUserInterrupt(SCRIPTFUNC_PARAMS); void sfScriptWalkRelative(SCRIPTFUNC_PARAMS); void sfScriptMoveRelative(SCRIPTFUNC_PARAMS); void sfSimulSpeech2(SCRIPTFUNC_PARAMS); void sfPlacard(SCRIPTFUNC_PARAMS); void sfPlacardOff(SCRIPTFUNC_PARAMS); void sfSetProtagState(SCRIPTFUNC_PARAMS); void sfResumeBgdAnim(SCRIPTFUNC_PARAMS); void sfThrowActor(SCRIPTFUNC_PARAMS); void sfWaitWalk(SCRIPTFUNC_PARAMS); void sfScriptSceneID(SCRIPTFUNC_PARAMS); void sfChangeActorScene(SCRIPTFUNC_PARAMS); void sfScriptClimb(SCRIPTFUNC_PARAMS); void sfSetDoorState(SCRIPTFUNC_PARAMS); void sfSetActorZ(SCRIPTFUNC_PARAMS); void sfScriptText(SCRIPTFUNC_PARAMS); void sfGetActorX(SCRIPTFUNC_PARAMS); void sfGetActorY(SCRIPTFUNC_PARAMS); void sfEraseDelta(SCRIPTFUNC_PARAMS); void sfPlayMusic(SCRIPTFUNC_PARAMS); void sfPickClimbOutPos(SCRIPTFUNC_PARAMS); void sfTossRif(SCRIPTFUNC_PARAMS); void sfShowControls(SCRIPTFUNC_PARAMS); void sfShowMap(SCRIPTFUNC_PARAMS); void sfPuzzleWon(SCRIPTFUNC_PARAMS); void sfEnableEscape(SCRIPTFUNC_PARAMS); void sfPlaySound(SCRIPTFUNC_PARAMS); void sfPlayLoopedSound(SCRIPTFUNC_PARAMS); void sfGetDeltaFrame(SCRIPTFUNC_PARAMS); void sfShowProtect(SCRIPTFUNC_PARAMS); void sfProtectResult(SCRIPTFUNC_PARAMS); void sfRand(SCRIPTFUNC_PARAMS); void sfFadeMusic(SCRIPTFUNC_PARAMS); void sfScriptStartCutAway(SCRIPTFUNC_PARAMS); void sfReturnFromCutAway(SCRIPTFUNC_PARAMS); void sfEndCutAway(SCRIPTFUNC_PARAMS); void sfGetMouseClicks(SCRIPTFUNC_PARAMS); void sfResetMouseClicks(SCRIPTFUNC_PARAMS); void sfWaitFrames(SCRIPTFUNC_PARAMS); void sfScriptFade(SCRIPTFUNC_PARAMS); void sfPlayVoice(SCRIPTFUNC_PARAMS); void sfVstopFX(SCRIPTFUNC_PARAMS); void sfVstopLoopedFX(SCRIPTFUNC_PARAMS); void sfDemoIsInteractive(SCRIPTFUNC_PARAMS); void sfVsetTrack(SCRIPTFUNC_PARAMS); void sfDebugShowData(SCRIPTFUNC_PARAMS); void sfNull(SCRIPTFUNC_PARAMS); void sfWaitFramesEsc(SCRIPTFUNC_PARAMS); void sfPsychicProfile(SCRIPTFUNC_PARAMS); void sfPsychicProfileOff(SCRIPTFUNC_PARAMS); void sfSetSpeechBox(SCRIPTFUNC_PARAMS); void sfSetChapterPoints(SCRIPTFUNC_PARAMS); void sfSetPortraitBgColor(SCRIPTFUNC_PARAMS); void sfScriptStartVideo(SCRIPTFUNC_PARAMS); void sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS); void sfScriptEndVideo(SCRIPTFUNC_PARAMS); void sfShowIHNMDemoHelpBg(SCRIPTFUNC_PARAMS); void sfAddIHNMDemoHelpTextLine(SCRIPTFUNC_PARAMS); void sfShowIHNMDemoHelpPage(SCRIPTFUNC_PARAMS); void sfGetPoints(SCRIPTFUNC_PARAMS); void sfSetGlobalFlag(SCRIPTFUNC_PARAMS); void sfDemoSetInteractive(SCRIPTFUNC_PARAMS); void sfClearGlobalFlag(SCRIPTFUNC_PARAMS); void sfTestGlobalFlag(SCRIPTFUNC_PARAMS); void sfSetPoints(SCRIPTFUNC_PARAMS); void sfQueueMusic(SCRIPTFUNC_PARAMS); void sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS); void sfStub(const char *name, ScriptThread *thread, int nArgs); }; class SAGA1Script : public Script { public: SAGA1Script(SagaEngine *vm); ~SAGA1Script(); }; class SAGA2Script : public Script { public: SAGA2Script(SagaEngine *vm); ~SAGA2Script(); }; } // End of namespace Saga #endif