scummvm/engines/saga/sthread.cpp

748 lines
20 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
// Scripting module thread management component
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/actor.h"
#include "saga/console.h"
#include "saga/interface.h"
#include "saga/script.h"
#include "saga/stream.h"
#include "saga/scene.h"
#include "saga/sagaresnames.h"
namespace Saga {
ScriptThread *Script::createThread(uint16 scriptModuleNumber, uint16 scriptEntryPointNumber) {
ScriptThread *newThread;
loadModule(scriptModuleNumber);
if (_modules[scriptModuleNumber].entryPointsCount <= scriptEntryPointNumber) {
error("Script::createThread wrong scriptEntryPointNumber");
}
newThread = _threadList.pushFront().operator->();
newThread->_flags = kTFlagNone;
newThread->_stackSize = DEFAULT_THREAD_STACK_SIZE;
newThread->_stackBuf = (uint16 *)malloc(newThread->_stackSize * sizeof(*newThread->_stackBuf));
newThread->_stackTopIndex = newThread->_stackSize - 2;
newThread->_instructionOffset = _modules[scriptModuleNumber].entryPoints[scriptEntryPointNumber].offset;
newThread->_commonBase = _commonBuffer;
newThread->_staticBase = _commonBuffer + _modules[scriptModuleNumber].staticOffset;
newThread->_moduleBase = _modules[scriptModuleNumber].moduleBase;
newThread->_moduleBaseSize = _modules[scriptModuleNumber].moduleBaseSize;
newThread->_strings = &_modules[scriptModuleNumber].strings;
if (_vm->getGameType() == GType_IHNM)
newThread->_voiceLUT = &_globalVoiceLUT;
else
newThread->_voiceLUT = &_modules[scriptModuleNumber].voiceLUT;
return newThread;
}
void Script::wakeUpActorThread(int waitType, void *threadObj) {
ScriptThread *thread;
ScriptThreadList::iterator threadIterator;
for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
thread = threadIterator.operator->();
if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType) && (thread->_threadObj == threadObj)) {
thread->_flags &= ~kTFlagWaiting;
}
}
}
void Script::wakeUpThreads(int waitType) {
ScriptThread *thread;
ScriptThreadList::iterator threadIterator;
for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
thread = threadIterator.operator->();
if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType)) {
thread->_flags &= ~kTFlagWaiting;
}
}
}
void Script::wakeUpThreadsDelayed(int waitType, int sleepTime) {
ScriptThread *thread;
ScriptThreadList::iterator threadIterator;
for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
thread = threadIterator.operator->();
if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType)) {
thread->_waitType = kWaitTypeDelay;
thread->_sleepTime = sleepTime;
}
}
}
void Script::executeThreads(uint msec) {
ScriptThread *thread;
ScriptThreadList::iterator threadIterator;
if (_vm->_interface->_statusTextInput) {
return;
}
threadIterator = _threadList.begin();
while (threadIterator != _threadList.end()) {
thread = threadIterator.operator->();
if (thread->_flags & (kTFlagFinished | kTFlagAborted)) {
if (thread->_flags & kTFlagFinished)
setPointerVerb();
if (_vm->getGameType() == GType_IHNM) {
thread->_flags &= ~kTFlagFinished;
thread->_flags |= kTFlagAborted;
++threadIterator;
} else {
threadIterator = _threadList.erase(threadIterator);
}
continue;
}
if (thread->_flags & kTFlagWaiting) {
switch (thread->_waitType) {
case kWaitTypeDelay:
if (thread->_sleepTime < msec) {
thread->_sleepTime = 0;
} else {
thread->_sleepTime -= msec;
}
if (thread->_sleepTime == 0)
thread->_flags &= ~kTFlagWaiting;
break;
case kWaitTypeWalk:
{
ActorData *actor;
actor = (ActorData *)thread->_threadObj;
if (actor->_currentAction == kActionWait) {
thread->_flags &= ~kTFlagWaiting;
}
}
break;
case kWaitTypeWaitFrames: // IHNM
if (thread->_frameWait < _vm->_frameCount)
thread->_flags &= ~kTFlagWaiting;
break;
}
}
if (!(thread->_flags & kTFlagWaiting)) {
if (runThread(thread, STHREAD_TIMESLICE)) {
break;
}
}
++threadIterator;
}
}
void Script::abortAllThreads(void) {
ScriptThread *thread;
ScriptThreadList::iterator threadIterator;
threadIterator = _threadList.begin();
while (threadIterator != _threadList.end()) {
thread = threadIterator.operator->();
thread->_flags |= kTFlagAborted;
++threadIterator;
}
executeThreads(0);
}
void Script::completeThread(void) {
int limit = (_vm->getGameType() == GType_IHNM) ? 100 : 40;
for (int i = 0; i < limit && !_threadList.empty(); i++)
executeThreads(0);
}
bool Script::runThread(ScriptThread *thread, uint instructionLimit) {
const char*operandName;
uint instructionCount;
uint16 savedInstructionOffset;
byte *addr;
byte mode;
uint16 jmpOffset1;
int16 iparam1;
int16 iparam2;
int16 iparam3;
bool disContinue;
byte argumentsCount;
uint16 functionNumber;
uint16 checkStackTopIndex;
ScriptFunctionType scriptFunction;
int operandChar;
int i;
MemoryReadStream scriptS(thread->_moduleBase, thread->_moduleBaseSize);
scriptS.seek(thread->_instructionOffset);
for (instructionCount = 0; instructionCount < instructionLimit; instructionCount++) {
if (thread->_flags & (kTFlagAsleep))
break;
savedInstructionOffset = thread->_instructionOffset;
operandChar = scriptS.readByte();
#define CASEOP(opName) case opName: \
if (operandChar == opName) { \
operandName = #opName; \
debug(2, operandName); \
_vm->_console->DebugPrintf("%s\n", operandName); \
}
debug(8, "Executing thread offset: %u (%x) stack: %d", thread->_instructionOffset, operandChar, thread->pushedSize());
operandName="";
switch (operandChar) {
CASEOP(opNextBlock)
// Some sort of "jump to the start of the next memory
// page" instruction, I think.
thread->_instructionOffset = (((thread->_instructionOffset) >> 10) + 1) << 10;
break;
// STACK INSTRUCTIONS
CASEOP(opDup)
thread->push(thread->stackTop());
break;
CASEOP(opDrop)
thread->pop();
break;
CASEOP(opZero)
thread->push(0);
break;
CASEOP(opOne)
thread->push(1);
break;
CASEOP(opConstint)
CASEOP(opStrlit)
iparam1 = scriptS.readSint16LE();
thread->push(iparam1);
debug(8, "0x%X", iparam1);
break;
// DATA INSTRUCTIONS
CASEOP(opGetFlag)
addr = thread->baseAddress(scriptS.readByte());
iparam1 = scriptS.readSint16LE();
addr += (iparam1 >> 3);
iparam1 = (1 << (iparam1 & 7));
thread->push((*addr) & iparam1 ? 1 : 0);
break;
CASEOP(opGetInt)
mode = scriptS.readByte();
addr = thread->baseAddress(mode);
iparam1 = scriptS.readSint16LE();
addr += iparam1;
thread->push(readUint16(addr, mode));
debug(8, "0x%X", readUint16(addr, mode));
break;
CASEOP(opPutFlag)
addr = thread->baseAddress(scriptS.readByte());
iparam1 = scriptS.readSint16LE();
addr += (iparam1 >> 3);
iparam1 = (1 << (iparam1 & 7));
if (thread->stackTop()) {
*addr |= iparam1;
} else {
*addr &= ~iparam1;
}
break;
CASEOP(opPutInt)
mode = scriptS.readByte();
addr = thread->baseAddress(mode);
iparam1 = scriptS.readSint16LE();
addr += iparam1;
writeUint16(addr, thread->stackTop(), mode);
break;
CASEOP(opPutFlagV)
addr = thread->baseAddress(scriptS.readByte());
iparam1 = scriptS.readSint16LE();
addr += (iparam1 >> 3);
iparam1 = (1 << (iparam1 & 7));
if (thread->pop()) {
*addr |= iparam1;
} else {
*addr &= ~iparam1;
}
break;
CASEOP(opPutIntV)
mode = scriptS.readByte();
addr = thread->baseAddress(mode);
iparam1 = scriptS.readSint16LE();
addr += iparam1;
writeUint16(addr, thread->pop(), mode);
break;
// FUNCTION CALL INSTRUCTIONS
CASEOP(opCall)
argumentsCount = scriptS.readByte();
iparam1 = scriptS.readByte();
if (iparam1 != kAddressModule) {
error("Script::runThread iparam1 != kAddressModule");
}
addr = thread->baseAddress(iparam1);
iparam1 = scriptS.readSint16LE();
addr += iparam1;
thread->push(argumentsCount);
jmpOffset1 = scriptS.pos();
// NOTE: The original pushes the program
// counter as a pointer here. But I don't think
// we will have to do that.
thread->push(jmpOffset1);
// NOTE2: program counter is 32bit - so we should "emulate" it size - because kAddressStack relies on it
thread->push(0);
thread->_instructionOffset = iparam1;
break;
CASEOP(opCcall)
CASEOP(opCcallV)
argumentsCount = scriptS.readByte();
functionNumber = scriptS.readUint16LE();
if (functionNumber >= ((_vm->getGameType() == GType_IHNM) ?
IHNM_SCRIPT_FUNCTION_MAX : ITE_SCRIPT_FUNCTION_MAX)) {
error("Script::runThread() Invalid script function number (%d)", functionNumber);
}
debug(2, "Calling #%d %s argCount=%i", functionNumber, _scriptFunctionsList[functionNumber].scriptFunctionName, argumentsCount);
scriptFunction = _scriptFunctionsList[functionNumber].scriptFunction;
checkStackTopIndex = thread->_stackTopIndex + argumentsCount;
disContinue = false;
(this->*scriptFunction)(thread, argumentsCount, disContinue);
if (disContinue) {
return true;
}
if (scriptFunction == &Saga::Script::sfScriptGotoScene ||
scriptFunction == &Saga::Script::sfVsetTrack) {
return true; // cause abortAllThreads called and _this_ thread destroyed
}
thread->_stackTopIndex = checkStackTopIndex;
if (operandChar == opCcall) {// CALL function
thread->push(thread->_returnValue);
}
if (thread->_flags & kTFlagAsleep)
instructionCount = instructionLimit; // break out of loop!
break;
CASEOP(opEnter)
thread->push(thread->_frameIndex);
thread->_frameIndex = thread->_stackTopIndex;
thread->_stackTopIndex -= (scriptS.readSint16LE() / 2);
break;
CASEOP(opReturn)
thread->_returnValue = thread->pop();
CASEOP(opReturnV)
thread->_stackTopIndex = thread->_frameIndex;
thread->_frameIndex = thread->pop();
if (thread->pushedSize() == 0) {
thread->_flags |= kTFlagFinished;
return true;
} else {
thread->pop(); //cause it 0
thread->_instructionOffset = thread->pop();
// Pop all the call parameters off the stack
iparam1 = thread->pop();
while (iparam1--) {
thread->pop();
}
if (operandChar == opReturn) {
thread->push(thread->_returnValue);
}
}
break;
// BRANCH INSTRUCTIONS
CASEOP(opJmp)
jmpOffset1 = scriptS.readUint16LE();
thread->_instructionOffset = jmpOffset1;
break;
CASEOP(opJmpTrueV)
jmpOffset1 = scriptS.readUint16LE();
if (thread->pop()) {
thread->_instructionOffset = jmpOffset1;
}
break;
CASEOP(opJmpFalseV)
jmpOffset1 = scriptS.readUint16LE();
if (!thread->pop()) {
thread->_instructionOffset = jmpOffset1;
}
break;
CASEOP(opJmpTrue)
jmpOffset1 = scriptS.readUint16LE();
if (thread->stackTop()) {
thread->_instructionOffset = jmpOffset1;
}
break;
CASEOP(opJmpFalse)
jmpOffset1 = scriptS.readUint16LE();
if (!thread->stackTop()) {
thread->_instructionOffset = jmpOffset1;
}
break;
CASEOP(opJmpSwitch)
iparam1 = scriptS.readSint16LE();
iparam2 = thread->pop();
while (iparam1--) {
iparam3 = scriptS.readUint16LE();
thread->_instructionOffset = scriptS.readUint16LE();
if (iparam3 == iparam2) {
break;
}
}
if (iparam1 < 0) {
thread->_instructionOffset = scriptS.readUint16LE();
}
break;
CASEOP(opJmpRandom)
// Supposedly the number of possible branches.
// The original interpreter ignores it.
scriptS.readUint16LE();
iparam1 = scriptS.readSint16LE();
iparam1 = _vm->_rnd.getRandomNumber(iparam1 - 1);
while (1) {
iparam2 = scriptS.readSint16LE();
thread->_instructionOffset = scriptS.readUint16LE();
iparam1 -= iparam2;
if (iparam1 < 0) {
break;
}
}
break;
// UNARY INSTRUCTIONS
CASEOP(opNegate)
thread->push(-thread->pop());
break;
CASEOP(opNot)
thread->push(!thread->pop());
break;
CASEOP(opCompl)
thread->push(~thread->pop());
break;
CASEOP(opIncV)
mode = scriptS.readByte();
addr = thread->baseAddress(mode);
iparam1 = scriptS.readSint16LE();
addr += iparam1;
iparam1 = readUint16(addr, mode);
writeUint16(addr, iparam1 + 1, mode);
break;
CASEOP(opDecV)
mode = scriptS.readByte();
addr = thread->baseAddress(mode);
iparam1 = scriptS.readSint16LE();
addr += iparam1;
iparam1 = readUint16(addr, mode);
writeUint16(addr, iparam1 - 1, mode);
break;
CASEOP(opPostInc)
mode = scriptS.readByte();
addr = thread->baseAddress(mode);
iparam1 = scriptS.readSint16LE();
addr += iparam1;
iparam1 = readUint16(addr, mode);
thread->push(iparam1);
writeUint16(addr, iparam1 + 1, mode);
break;
CASEOP(opPostDec)
mode = scriptS.readByte();
addr = thread->baseAddress(mode);
iparam1 = scriptS.readSint16LE();
addr += iparam1;
iparam1 = readUint16(addr, mode);
thread->push(iparam1);
writeUint16(addr, iparam1 - 1, mode);
break;
// ARITHMETIC INSTRUCTIONS
CASEOP(opAdd)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 += iparam2;
thread->push(iparam1);
break;
CASEOP(opSub)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 -= iparam2;
thread->push(iparam1);
break;
CASEOP(opMul)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 *= iparam2;
thread->push(iparam1);
break;
CASEOP(opDiv)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 /= iparam2;
thread->push(iparam1);
break;
CASEOP(opMod)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 %= iparam2;
thread->push(iparam1);
break;
// COMPARISION INSTRUCTIONS
CASEOP(opEq)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push((iparam1 == iparam2) ? 1 : 0);
debug(8, "0x%X 0x%X", iparam1, iparam2);
break;
CASEOP(opNe)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push((iparam1 != iparam2) ? 1 : 0);
break;
CASEOP(opGt)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push((iparam1 > iparam2) ? 1 : 0);
break;
CASEOP(opLt)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push((iparam1 < iparam2) ? 1 : 0);
break;
CASEOP(opGe)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push((iparam1 >= iparam2) ? 1 : 0);
break;
CASEOP(opLe)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push((iparam1 <= iparam2) ? 1 : 0);
break;
// SHIFT INSTRUCTIONS
CASEOP(opRsh)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 >>= iparam2;
thread->push(iparam1);
break;
CASEOP(opLsh)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 <<= iparam2;
thread->push(iparam1);
break;
// BITWISE INSTRUCTIONS
CASEOP(opAnd)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 &= iparam2;
thread->push(iparam1);
break;
CASEOP(opOr)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 |= iparam2;
thread->push(iparam1);
break;
CASEOP(opXor)
iparam2 = thread->pop();
iparam1 = thread->pop();
iparam1 ^= iparam2;
thread->push(iparam1);
break;
// LOGICAL INSTRUCTIONS
CASEOP(opLAnd)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push((iparam1 && iparam2) ? 1 : 0);
break;
CASEOP(opLOr)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push((iparam1 || iparam2) ? 1 : 0);
break;
CASEOP(opLXor)
iparam2 = thread->pop();
iparam1 = thread->pop();
thread->push(((iparam1 && !iparam2) || (!iparam1 && iparam2)) ? 1 : 0);
break;
// GAME INSTRUCTIONS
CASEOP(opSpeak) {
int stringsCount;
uint16 actorId;
uint16 speechFlags;
int sampleResourceId = -1;
int16 first;
const char *strings[ACTOR_SPEECH_STRING_MAX];
if (_vm->_actor->isSpeaking()) {
thread->wait(kWaitTypeSpeech);
return false;
}
stringsCount = scriptS.readByte();
actorId = scriptS.readUint16LE();
speechFlags = scriptS.readByte();
scriptS.readUint16LE(); // x,y skip
if (stringsCount == 0)
error("opSpeak stringsCount == 0");
if (stringsCount > ACTOR_SPEECH_STRING_MAX)
error("opSpeak stringsCount=0x%X exceed ACTOR_SPEECH_STRING_MAX", stringsCount);
iparam1 = first = thread->stackTop();
for (i = 0; i < stringsCount; i++) {
iparam1 = thread->pop();
strings[i] = thread->_strings->getString(iparam1);
}
// now data contains last string index
if (_vm->getGameId() == GID_ITE_DISK_G) { // special ITE dos
if ((_vm->_scene->currentSceneNumber() == ITE_DEFAULT_SCENE) &&
(iparam1 >= 288) && (iparam1 <= (RID_SCENE1_VOICE_138 - RID_SCENE1_VOICE_009 + 288))) {
sampleResourceId = RID_SCENE1_VOICE_009 + iparam1 - 288;
}
} else {
if (thread->_voiceLUT->voicesCount > first) {
sampleResourceId = thread->_voiceLUT->voices[first];
}
}
if (sampleResourceId < 0 || sampleResourceId > 4000)
sampleResourceId = -1;
if (_vm->getGameType() == GType_ITE && !sampleResourceId)
sampleResourceId = -1;
_vm->_actor->actorSpeech(actorId, strings, stringsCount, sampleResourceId, speechFlags);
if (!(speechFlags & kSpeakAsync)) {
thread->wait(kWaitTypeSpeech);
}
}
break;
CASEOP(opDialogBegin)
if (_conversingThread) {
thread->wait(kWaitTypeDialogBegin);
return false;
}
_conversingThread = thread;
_vm->_interface->converseClear();
break;
CASEOP(opDialogEnd)
if (thread == _conversingThread) {
_vm->_interface->activate();
_vm->_interface->setMode(kPanelConverse);
thread->wait(kWaitTypeDialogEnd);
return false;
}
break;
CASEOP(opReply) {
const char *str;
byte replyNum;
byte flags;
replyNum = scriptS.readByte();
flags = scriptS.readByte();
iparam1 = 0;
if (flags & kReplyOnce) {
iparam1 = scriptS.readSint16LE();
addr = thread->_staticBase + (iparam1 >> 3);
if (*addr & (1 << (iparam1 & 7))) {
break;
}
}
str = thread->_strings->getString(thread->pop());
if (_vm->_interface->converseAddText(str, replyNum, flags, iparam1))
warning("Error adding ConverseText (%s, %d, %d, %d)", str, replyNum, flags, iparam1);
}
break;
CASEOP(opAnimate)
scriptS.readUint16LE();
scriptS.readUint16LE();
jmpOffset1 = scriptS.readByte();
thread->_instructionOffset += jmpOffset1;
break;
default:
error("Script::runThread() Invalid opcode encountered 0x%X", operandChar);
}
if (thread->_flags & (kTFlagFinished | kTFlagAborted)) {
error("Wrong flags %d in thread", thread->_flags);
}
// Set instruction offset only if a previous instruction didn't branch
if (savedInstructionOffset == thread->_instructionOffset) {
thread->_instructionOffset = scriptS.pos();
} else {
if (thread->_instructionOffset >= scriptS.size()) {
error("Script::runThread() Out of range script execution");
}
scriptS.seek(thread->_instructionOffset);
}
}
return false;
}
} // End of namespace Saga