scummvm/saga/sthread.cpp

797 lines
21 KiB
C++
Raw Normal View History

/* ScummVM - Scumm Interpreter
* Copyright (C) 2004-2005 The ScummVM project
*
* The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
*
* 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$
*
*/
2004-05-01 16:15:55 +00:00
// Scripting module thread management component
2004-08-02 16:20:35 +00:00
#include "saga/saga.h"
2004-08-02 16:20:35 +00:00
#include "saga/gfx.h"
#include "saga/actor.h"
2004-08-10 18:31:33 +00:00
#include "saga/console.h"
#include "saga/interface.h"
#include "saga/script.h"
#include "saga/stream.h"
#include "saga/scene.h"
#include "saga/resnames.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 - 1; // or 2 - as in original
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;
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;
}
}
}
int Script::executeThreads(uint msec) {
ScriptThread *thread;
ScriptThreadList::iterator threadIterator;
if (!isInitialized()) {
return FAILURE;
}
threadIterator = _threadList.begin();
while (threadIterator != _threadList.end()) {
thread = threadIterator.operator->();
if (thread->_flags & (kTFlagFinished | kTFlagAborted)) {
if (thread->_flags & kTFlagFinished)
setPointerVerb();
threadIterator = _threadList.erase(threadIterator);
continue;
}
if (thread->_flags & kTFlagWaiting) {
if (thread->_waitType == kWaitTypeDelay) {
if (thread->_sleepTime < msec) {
thread->_sleepTime = 0;
} else {
thread->_sleepTime -= msec;
}
if (thread->_sleepTime == 0)
thread->_flags &= ~kTFlagWaiting;
} else {
if (thread->_waitType == kWaitTypeWalk) {
ActorData *actor;
actor = (ActorData *)thread->_threadObj;
if (actor->currentAction == kActionWait) {
thread->_flags &= ~kTFlagWaiting;
}
}
}
}
if (!(thread->_flags & kTFlagWaiting))
runThread(thread, STHREAD_TIMESLICE);
++threadIterator;
}
return SUCCESS;
}
void Script::completeThread(void) {
for (int i = 0; i < 40 && !_threadList.isEmpty() ; i++)
executeThreads(0);
}
int Script::SThreadDebugStep() {
if (_dbg_singlestep) {
_dbg_dostep = 1;
}
return SUCCESS;
}
void Script::runThread(ScriptThread *thread, uint instructionLimit) {
uint instructionCount;
uint16 savedInstructionOffset;
byte *addr;
uint16 param1;
uint16 param2;
int16 iparam1;
int16 iparam2;
long iresult;
byte argumentsCount;
uint16 functionNumber;
int scriptFunctionReturnValue;
ScriptFunctionType scriptFunction;
uint16 data;
uint16 scriptRetVal = 0;
int debug_print = 0;
int n_buf;
// int bitstate;
int operandChar;
int i;
int unhandled = 0;
2004-05-01 16:15:55 +00:00
// Handle debug single-stepping
if ((thread == _dbg_thread) && _dbg_singlestep) {
if (_dbg_dostep) {
debug_print = 1;
thread->_sleepTime = 0;
instructionLimit = 1;
_dbg_dostep = 0;
} else {
return;
}
}
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) { \
debug(8, #opName); \
_vm->_console->DebugPrintf(#opName); \
}
// debug print (opCode name etc) should be placed here
// SDebugPrintInstr(thread)
debug(8, "Executing thread offset: %lu (%x) stack: %d", thread->_instructionOffset, operandChar, thread->pushedSize());
switch (operandChar) {
CASEOP(opNextBlock)
// Some sort of "jump to the start of the next memory
// page" instruction, I think.
thread->_instructionOffset = 1024 * ((thread->_instructionOffset / 1024) + 1);
break;
2004-05-01 16:15:55 +00:00
// 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)
param1 = scriptS.readUint16LE();
thread->push(param1);
break;
2004-05-01 16:15:55 +00:00
// DATA INSTRUCTIONS
CASEOP(opGetFlag)
addr = thread->baseAddress(scriptS.readByte());
param1 = scriptS.readUint16LE();
addr += (param1 >> 3);
param1 = (1 << (param1 & 7));
thread->push((*addr) & param1 ? 1 : 0);
break;
CASEOP(opGetInt)
addr = thread->baseAddress(scriptS.readByte());
param1 = scriptS.readUint16LE();
addr += param1;
thread->push(*((uint16*)addr));
break;
CASEOP(opPutFlag)
addr = thread->baseAddress(scriptS.readByte());
param1 = scriptS.readUint16LE();
addr += (param1 >> 3);
param1 = (1 << (param1 & 7));
if (thread->stackTop()) {
*addr |= param1;
} else {
*addr &= ~param1;
}
break;
CASEOP(opPutInt)
addr = thread->baseAddress(scriptS.readByte());
param1 = scriptS.readUint16LE();
addr += param1;
*(uint16*)addr = thread->stackTop();
break;
CASEOP(opPutFlagV)
addr = thread->baseAddress(scriptS.readByte());
param1 = scriptS.readUint16LE();
addr += (param1 >> 3);
param1 = (1 << (param1 & 7));
if (thread->pop()) {
*addr |= param1;
} else {
*addr &= ~param1;
}
break;
CASEOP(opPutIntV)
addr = thread->baseAddress(scriptS.readByte());
param1 = scriptS.readUint16LE();
addr += param1;
*(uint16*)addr = thread->pop();
break;
2004-05-01 16:15:55 +00:00
// CONTROL INSTRUCTIONS
CASEOP(opCall)
argumentsCount = scriptS.readByte();
param1 = scriptS.readByte();
if (param1 != kAddressModule) {
error("Script::runThread param1 != kAddressModule");
}
addr = thread->baseAddress(param1);
param1 = scriptS.readUint16LE();
addr += param1;
thread->push(argumentsCount);
param2 = 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(param2);
thread->_instructionOffset = param1;
break;
CASEOP(opCcall)
CASEOP(opCcallV)
argumentsCount = scriptS.readByte();
functionNumber = scriptS.readUint16LE();
if (functionNumber >= SCRIPT_FUNCTION_MAX) {
error("Script::runThread() Invalid script function number");
}
debug(8, "Calling 0x%X %s", functionNumber, _scriptFunctionsList[functionNumber].scriptFunctionName);
scriptFunction = _scriptFunctionsList[functionNumber].scriptFunction;
scriptFunctionReturnValue = (this->*scriptFunction)(thread, argumentsCount);
if (scriptFunctionReturnValue != SUCCESS) {
_vm->_console->DebugPrintf(S_WARN_PREFIX "%X: Script function %d failed.\n", thread->_instructionOffset, scriptFunctionReturnValue);
}
if (scriptFunction == SF_gotoScene) { // SF_gotoScene
instructionCount = instructionLimit; // break the loop
break;
}
if (operandChar == opCcall) // CALL function
thread->push(thread->_returnValue);
if (thread->_flags & kTFlagAsleep)
instructionCount = instructionLimit; // break out of loop!
break;
case opEnter: // Enter a function
thread->push(thread->_frameIndex);
thread->_frameIndex = thread->_stackTopIndex;
thread->_stackTopIndex -= (scriptS.readUint16LE() / 2);
break;
case opReturn: // Return with value
scriptRetVal = thread->pop();
// Fall through
case opReturnV: // Return with void
thread->_stackTopIndex = thread->_frameIndex;
thread->_frameIndex = thread->pop();
if (thread->pushedSize() == 0) {
_vm->_console->DebugPrintf("Script execution complete.\n");
thread->_flags |= kTFlagFinished;
return;
} else {
thread->_instructionOffset = thread->pop();
/* int n_args = */ thread->pop();
if (operandChar == opReturn)
thread->push(scriptRetVal);
}
break;
2004-05-01 16:15:55 +00:00
// BRANCH INSTRUCTIONS
2004-05-01 16:15:55 +00:00
// (JMP): Unconditional jump
case 0x1D:
param1 = scriptS.readUint16LE();
thread->_instructionOffset = (unsigned long)param1;
break;
2004-05-01 16:15:55 +00:00
// (JNZP): Jump if nonzero + POP
case 0x1E:
param1 = scriptS.readUint16LE();
data = thread->pop();
if (data) {
thread->_instructionOffset = (unsigned long)param1;
}
break;
2004-05-01 16:15:55 +00:00
// (JZP): Jump if zero + POP
case 0x1F:
param1 = scriptS.readUint16LE();
data = thread->pop();
if (!data) {
thread->_instructionOffset = (unsigned long)param1;
}
break;
2004-05-01 16:15:55 +00:00
// (JNZ): Jump if nonzero
case 0x20:
param1 = scriptS.readUint16LE();
data = thread->stackTop();
if (data) {
thread->_instructionOffset = (unsigned long)param1;
}
break;
2004-05-01 16:15:55 +00:00
// (JZ): Jump if zero
case 0x21:
param1 = scriptS.readUint16LE();
data = thread->stackTop();
if (!data) {
thread->_instructionOffset = (unsigned long)param1;
}
break;
2004-05-01 16:15:55 +00:00
// (SWCH): Switch
case 0x22:
{
int n_switch;
uint16 switch_num;
unsigned int switch_jmp;
unsigned int default_jmp;
int case_found = 0;
data = thread->pop();
n_switch = scriptS.readUint16LE();
for (i = 0; i < n_switch; i++) {
switch_num = scriptS.readUint16LE();
switch_jmp = scriptS.readUint16LE();
2004-05-01 16:15:55 +00:00
// Found the specified case
if (data == switch_num) {
thread->_instructionOffset = switch_jmp;
case_found = 1;
break;
}
}
2004-05-01 16:15:55 +00:00
// Jump to default case
if (!case_found) {
default_jmp = scriptS.readUint16LE();
thread->_instructionOffset = default_jmp;
}
}
break;
2004-05-01 16:15:55 +00:00
// (RJMP): Random branch
case 0x24:
{
// Supposedly the number of possible branches.
// The original interpreter ignores it.
scriptS.readUint16LE();
uint16 probability = _vm->_rnd.getRandomNumber(scriptS.readUint16LE() - 1);
while (1) {
uint16 branch_probability = scriptS.readUint16LE();
uint16 offset = scriptS.readUint16LE();
if (branch_probability > probability) {
thread->_instructionOffset = offset;
break;
}
probability -= branch_probability;
}
}
break;
// UNARY INSTRUCTIONS
2004-05-01 16:15:55 +00:00
// (NEG) Negate stack by 2's complement
case 0x25:
data = thread->pop();
thread->push(-(int)data);
break;
2004-05-01 16:15:55 +00:00
// (TSTZ) Test for zero
case 0x26:
data = thread->pop();
thread->push(!data);
break;
2004-05-01 16:15:55 +00:00
// (NOT) Binary not
case 0x27:
data = thread->pop();
thread->push(~data);
break;
case 0x28: // inc_v increment, don't push
n_buf = scriptS.readByte();
param1 = scriptS.readUint16LE();
//getWord(n_buf, param1, &data);
//putWord(n_buf, param1, data + 1);
break;
case 0x29: // dec_v decrement, don't push
n_buf = scriptS.readByte();
param1 = scriptS.readUint16LE();
//getWord(n_buf, param1, &data);
//putWord(n_buf, param1, data - 1);
break;
case 0x2A: // postinc
n_buf = scriptS.readByte();
param1 = scriptS.readUint16LE();
//getWord(n_buf, param1, &data);
// thread->push(data);
//putWord(n_buf, param1, data + 1);
break;
case 0x2B: // postdec
n_buf = scriptS.readByte();
param1 = scriptS.readUint16LE();
//getWord(n_buf, param1, &data);
// thread->push(data);
//putWord(n_buf, param1, data - 1);
break;
2004-05-01 16:15:55 +00:00
// ARITHMETIC INSTRUCTIONS
2004-05-01 16:15:55 +00:00
// (ADD): Addition
case 0x2C:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
iresult = iparam1 + iparam2;
thread->push( iresult);
break;
2004-05-01 16:15:55 +00:00
// (SUB): Subtraction
case 0x2D:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
iresult = iparam1 - iparam2;
thread->push( iresult);
break;
2004-05-01 16:15:55 +00:00
// (MULT): Integer multiplication
case 0x2E:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
iresult = iparam1 * iparam2;
thread->push( iresult);
break;
// (DIV): Integer division
case 0x2F:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
iresult = iparam1 / iparam2;
thread->push( iresult);
break;
2004-05-01 16:15:55 +00:00
// (MOD) Modulus
case 0x30:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
iresult = iparam1 % iparam2;
thread->push( iresult);
break;
2004-05-01 16:15:55 +00:00
// (EQU) Test equality
case 0x33:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
data = (iparam1 == iparam2) ? 1 : 0;
thread->push(data);
break;
2004-05-01 16:15:55 +00:00
// (NEQU) Test inequality
case 0x34:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
data = (iparam1 != iparam2) ? 1 : 0;
thread->push(data);
break;
2004-05-01 16:15:55 +00:00
// (GRT) Test Greater-than
case 0x35:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
data = (iparam1 > iparam2) ? 1 : 0;
thread->push(data);
break;
2004-05-01 16:15:55 +00:00
// (LST) Test Less-than
case 0x36:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
data = (iparam1 < iparam2) ? 1 : 0;
thread->push(data);
break;
2004-05-01 16:15:55 +00:00
// (GRTE) Test Greater-than or Equal to
case 0x37:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
data = (iparam1 >= iparam2) ? 1 : 0;
thread->push(data);
break;
2004-05-01 16:15:55 +00:00
// (LSTE) Test Less-than or Equal to
case 0x38:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
iparam1 = (long)param1;
data = (iparam1 <= iparam2) ? 1 : 0;
thread->push(data);
break;
// BITWISE INSTRUCTIONS
2004-05-01 16:15:55 +00:00
// (SHR): Arithmetic binary shift right
case 0x3F:
param2 = thread->pop();
param1 = thread->pop();
iparam2 = (long)param2;
2004-05-01 16:15:55 +00:00
// Preserve most significant bit
data = (0x01 << ((sizeof(param1) * CHAR_BIT) - 1)) & param1;
for (i = 0; i < (int)iparam2; i++) {
param1 >>= 1;
param1 |= data;
}
thread->push(param1);
break;
2004-05-01 16:15:55 +00:00
// (SHL) Binary shift left
case 0x40:
param2 = thread->pop();
param1 = thread->pop();
param1 <<= param2;
thread->push(param1);
break;
2004-05-01 16:15:55 +00:00
// (AND) Binary AND
case 0x41:
param2 = thread->pop();
param1 = thread->pop();
param1 &= param2;
thread->push(param1);
break;
2004-05-01 16:15:55 +00:00
// (OR) Binary OR
case 0x42:
param2 = thread->pop();
param1 = thread->pop();
param1 |= param2;
thread->push(param1);
break;
2004-05-01 16:15:55 +00:00
// (XOR) Binary XOR
case 0x43:
param2 = thread->pop();
param1 = thread->pop();
param1 ^= param2;
thread->push(param1);
break;
2004-05-01 16:15:55 +00:00
// BOOLEAN LOGIC INSTRUCTIONS
2004-05-01 16:15:55 +00:00
// (LAND): Logical AND
case 0x44:
param2 = thread->pop();
param1 = thread->pop();
data = (param1 && param2) ? 1 : 0;
thread->push(data);
break;
2004-05-01 16:15:55 +00:00
// (LOR): Logical OR
case 0x45:
param2 = thread->pop();
param1 = thread->pop();
data = (param1 || param2) ? 1 : 0;
thread->push(data);
break;
2004-05-01 16:15:55 +00:00
// (LXOR): Logical XOR
case 0x46:
param2 = thread->pop();
param1 = thread->pop();
data = ((param1) ? !(param2) : !!(param2));
thread->push(data);
break;
2004-05-01 16:15:55 +00:00
// GAME INSTRUCTIONS
case opSpeak: { // (opSpeak): Play Character Speech
int stringsCount;
uint16 actorId;
int speechFlags;
int sampleResourceId = -1;
int first;
const char *strings[ACTOR_SPEECH_STRING_MAX];
if (_vm->_actor->isSpeaking()) {
thread->wait(kWaitTypeSpeech);
return;
}
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);
data = first = thread->stackTop();
for (i = 0; i < stringsCount; i++) {
data = thread->pop();
strings[i] = thread->_strings->getString(data);
}
// now data contains last string index
if (_vm->getGameId() == GID_ITE_DISK_G) { // special ITE dos
if ((_vm->_scene->currentSceneNumber() == ITE_DEFAULT_SCENE) && (data >= 288) && (data <= (RID_SCENE1_VOICE_138 - RID_SCENE1_VOICE_009 + 288))) {
sampleResourceId = RID_SCENE1_VOICE_009 + data - 288;
}
} else {
if (thread->_voiceLUT->voicesCount > first) {
sampleResourceId = thread->_voiceLUT->voices[first];
}
}
_vm->_actor->actorSpeech(actorId, strings, stringsCount, sampleResourceId, speechFlags);
if (!(speechFlags & kSpeakAsync)) {
thread->wait(kWaitTypeSpeech);
thread->_instructionOffset = scriptS.pos();
return;
}
}
break;
case opDialogBegin: // (DLGS): Initialize dialogue interface
if (_conversingThread) {
thread->wait(kWaitTypeDialogBegin);
return;
}
_conversingThread = thread;
_vm->_interface->converseClear();
break;
case opDialogEnd: // (DLGX): Run dialogue interface
if (thread == _conversingThread) {
_vm->_interface->activate();
_vm->_interface->setMode(kPanelConverse);
thread->wait(kWaitTypeDialogEnd);
return;
}
break;
case opReply: // (DLGO): Add a dialogue option to interface
{
uint16 n = 0;
const char *str;
int replyNum = scriptS.readByte();
int flags = scriptS.readByte();
if (flags & kReplyOnce) {
n = scriptS.readUint16LE();
// TODO:
}
str = thread->_strings->getString(thread->pop());
if (_vm->_interface->converseAddText(str, replyNum, flags, n))
warning("Error adding ConverseText (%s, %d, %d, %d)", str, replyNum, flags, n);
}
break;
case 0x57: // animate
scriptS.readUint16LE();
scriptS.readUint16LE();
iparam1 = (long)scriptS.readByte();
thread->_instructionOffset += iparam1;
break;
2004-05-01 16:15:55 +00:00
// End instruction list
default:
scriptError(thread, "Invalid opcode encountered");
return;
2004-05-01 16:15:55 +00:00
}
2004-05-01 16:15:55 +00:00
// 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()) {
scriptError(thread, "Out of range script execution");
return;
}
scriptS.seek(thread->_instructionOffset);
}
if (unhandled) { // TODO: remove it
scriptError(thread, "Unhandled opcode");
}
}
}
} // End of namespace Saga