openmohaa/code/globalcpp/scriptvm.cpp
2016-03-27 11:49:47 +02:00

2314 lines
40 KiB
C++

/*
===========================================================================
Copyright (C) 2015 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code 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.
OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// scriptvm.cpp : Script virtual machine, interprets and execute scripts
#include "glb_local.h"
#include "scriptmaster.h"
#include "scriptvm.h"
#include "compiler.h"
#include "game.h"
#include "level.h"
#include "parm.h"
#include "world.h"
#ifdef CGAME_DLL
#define VM_Printf cgi.Printf
#define VM_DPrintf cgi.DPrintf
#elif defined GAME_DLL
#define VM_Printf gi.Printf
#define VM_DPrintf gi.DPrintf2
#else
#define VM_Printf printf
#define VM_DPrintf printf
#endif
//====================
// ScriptClass
//====================
MEM_BlockAlloc< ScriptClass, MEM_BLOCKSIZE > ScriptClass_allocator;
CLASS_DECLARATION( Listener, ScriptClass, NULL )
{
{ NULL, NULL }
};
/*
====================
new ScriptClass
====================
*/
void *ScriptClass::operator new( size_t size )
{
return ScriptClass_allocator.Alloc();
}
/*
====================
delete ptr
====================
*/
void ScriptClass::operator delete( void *ptr )
{
ScriptClass_allocator.Free( ptr );
}
/*
====================
ScriptClass
====================
*/
ScriptClass::ScriptClass( GameScript *gameScript, Listener *self )
{
m_Self = self;
m_Script = gameScript;
m_Threads = NULL;
}
/*
====================
ScriptClass
====================
*/
ScriptClass::ScriptClass()
{
m_Self = NULL;
m_Script = NULL;
m_Threads = NULL;
}
/*
====================
~ScriptClass
====================
*/
ScriptClass::~ScriptClass()
{
if( m_Script == NULL ) {
ScriptError( "Attempting to delete dead class." );
}
KillThreads();
if( !m_Script->m_Filename )
{
// This is a temporary gamescript
delete m_Script;
}
}
/*
====================
Archive
====================
*/
void ScriptClass::Archive( Archiver& arc )
{
}
/*
====================
ArchiveInternal
====================
*/
void ScriptClass::ArchiveInternal( Archiver& arc )
{
Listener::Archive( arc );
arc.ArchiveObjectPosition( this );
arc.ArchiveSafePointer( &m_Self );
GameScript::Archive( arc, m_Script );
}
/*
====================
ArchiveScript
====================
*/
void ScriptClass::ArchiveScript( Archiver& arc, ScriptClass **obj )
{
ScriptClass *scr;
ScriptVM *m_current;
ScriptThread *m_thread;
int num;
int i;
if( arc.Saving() )
{
scr = *obj;
scr->ArchiveInternal( arc );
num = 0;
for( m_current = scr->m_Threads; m_current != NULL; m_current = m_current->next )
num++;
arc.ArchiveInteger( &num );
for( m_current = scr->m_Threads; m_current != NULL; m_current = m_current->next )
m_current->m_Thread->ArchiveInternal( arc );
}
else
{
scr = new ScriptClass();
scr->ArchiveInternal( arc );
arc.ArchiveInteger( &num );
for( i = 0; i < num; i++ )
{
m_thread = new ScriptThread( scr, NULL );
m_thread->ArchiveInternal( arc );
}
*obj = scr;
}
}
/*
====================
ArchiveCodePos
====================
*/
void ScriptClass::ArchiveCodePos( Archiver& arc, unsigned char **codePos )
{
m_Script->ArchiveCodePos( arc, codePos );
}
/*
====================
CreateThreadInternal
====================
*/
ScriptThread *ScriptClass::CreateThreadInternal( const ScriptVariable& label )
{
GameScript *scr;
ScriptThread *thread = NULL;
if( label.GetType() == VARIABLE_STRING || label.GetType() == VARIABLE_CONSTSTRING )
{
ScriptClass *scriptClass = Director.CurrentScriptClass();
scr = scriptClass->GetScript();
if( label.GetType() == VARIABLE_CONSTSTRING )
thread = Director.CreateScriptThread( scr, m_Self, label.constStringValue() );
else
thread = Director.CreateScriptThread( scr, m_Self, label.stringValue() );
}
else if( label.GetType() == VARIABLE_CONSTARRAY && label.arraysize() > 1 )
{
ScriptVariable *script = label[ 1 ];
ScriptVariable *labelname = label[ 2 ];
if( script->GetType() == VARIABLE_CONSTSTRING )
scr = Director.GetGameScript( script->constStringValue() );
else
scr = Director.GetGameScript( script->stringValue() );
if( labelname->GetType() == VARIABLE_CONSTSTRING )
thread = Director.CreateScriptThread( scr, m_Self, labelname->constStringValue() );
else
thread = Director.CreateScriptThread( scr, m_Self, labelname->stringValue() );
}
else
{
ScriptError( "ScriptClass::CreateThreadInternal: bad argument format" );
}
return thread;
}
/*
====================
CreateScriptInternal
====================
*/
ScriptThread *ScriptClass::CreateScriptInternal( const ScriptVariable& label )
{
GameScript *scr;
ScriptThread *thread = NULL;
if( label.GetType() == VARIABLE_STRING || label.GetType() == VARIABLE_CONSTSTRING )
{
if( label.GetType() == VARIABLE_CONSTSTRING )
thread = Director.CreateScriptThread( Director.GetGameScript( label.stringValue() ), m_Self, "" );
else
thread = Director.CreateScriptThread( Director.GetGameScript( label.constStringValue() ), m_Self, "" );
}
else if( label.GetType() == VARIABLE_CONSTARRAY && label.arraysize() > 1 )
{
ScriptVariable *script = label[ 1 ];
ScriptVariable *labelname = label[ 2 ];
if( script->GetType() == VARIABLE_CONSTSTRING )
scr = Director.GetGameScript( script->constStringValue() );
else
scr = Director.GetGameScript( script->stringValue() );
if( labelname->GetType() == VARIABLE_CONSTSTRING )
thread = Director.CreateScriptThread( scr, m_Self, labelname->constStringValue() );
else
thread = Director.CreateScriptThread( scr, m_Self, labelname->stringValue() );
}
else
{
ScriptError( "ScriptClass::CreateScriptInternal: bad label type '%s'", label.GetTypeName() );
}
return thread;
}
/*
====================
AddThread
====================
*/
void ScriptClass::AddThread( ScriptVM *m_ScriptVM )
{
m_ScriptVM->next = m_Threads;
m_Threads = m_ScriptVM;
}
/*
====================
KillThreads
====================
*/
void ScriptClass::KillThreads()
{
if( !m_Threads ) {
return;
}
ScriptVM *m_current;
ScriptVM *m_next;
m_current = m_Threads;
do
{
m_current->m_ScriptClass = NULL;
m_next = m_current->next;
delete m_current->m_Thread;
} while( m_current = m_next );
m_Threads = NULL;
}
/*
====================
RemoveThread
====================
*/
void ScriptClass::RemoveThread( ScriptVM *m_ScriptVM )
{
if( m_Threads == m_ScriptVM )
{
m_Threads = m_ScriptVM->next;
if( m_ScriptVM->next == NULL ) {
delete this;
}
}
else
{
ScriptVM *m_current = m_Threads;
ScriptVM *i;
for( i = m_Threads->next; i != m_ScriptVM; i = i->next ) {
m_current = i;
}
m_current->next = i->next;
}
}
/*
====================
Filename
====================
*/
str ScriptClass::Filename()
{
return m_Script->Filename();
}
/*
====================
FindLabel
====================
*/
unsigned char *ScriptClass::FindLabel( str label )
{
return m_Script->m_State.FindLabel( label );
}
/*
====================
FindLabel
====================
*/
unsigned char *ScriptClass::FindLabel( const_str label )
{
return m_Script->m_State.FindLabel( label );
}
/*
====================
NearestLabel
====================
*/
const_str ScriptClass::NearestLabel( unsigned char *pos )
{
return m_Script->m_State.NearestLabel( pos );
}
/*
====================
GetCatchStateScript
====================
*/
StateScript *ScriptClass::GetCatchStateScript( unsigned char *in, unsigned char *&out )
{
return m_Script->GetCatchStateScript( in, out );
}
/*
====================
GetScript
====================
*/
GameScript *ScriptClass::GetScript()
{
return m_Script;
}
/*
====================
GetSelf
====================
*/
Listener *ScriptClass::GetSelf()
{
return static_cast< Listener * >( m_Self.Pointer() );
}
//====================
// ScriptVM
//====================
MEM_BlockAlloc< ScriptVM, char[ 256 ] > ScriptVM_allocator;
/*
====================
new ScriptVM
====================
*/
void *ScriptVM::operator new( size_t size )
{
return ScriptVM_allocator.Alloc();
}
/*
====================
delete ptr
====================
*/
void ScriptVM::operator delete( void *ptr )
{
ScriptVM_allocator.Free( ptr );
}
/*
====================
ScriptVM
====================
*/
ScriptVM::ScriptVM( ScriptClass *scriptClass, unsigned char *pCodePos, ScriptThread *thread )
{
next = NULL;
m_Thread = thread;
m_ScriptClass = scriptClass;
m_Stack = NULL;
m_PrevCodePos = NULL;
m_CodePos = pCodePos;
state = STATE_RUNNING;
m_ThreadState = THREAD_RUNNING;
m_pOldData = NULL;
m_OldDataSize = 0;
m_bMarkStack = false;
m_StackPos = NULL;
m_bAllowContextSwitch = true;
localStackSize = m_ScriptClass->GetScript()->GetRequiredStackSize();
if( localStackSize <= 0 ) {
localStackSize = 1;
}
localStack = new ScriptVariable[ localStackSize ];
pTop = localStack;
m_ScriptClass->AddThread( this );
}
/*
====================
~ScriptVM
====================
*/
ScriptVM::~ScriptVM()
{
fastEvent.data = m_pOldData;
fastEvent.dataSize = m_OldDataSize;
// clean-up the call stack
while( callStack.NumObjects() )
{
LeaveFunction();
}
delete[] localStack;
}
/*
====================
Archive
====================
*/
void ScriptVM::Archive( Archiver& arc )
{
int stack = 0;
if( arc.Saving() )
{
if( m_Stack )
stack = m_Stack->m_Count;
arc.ArchiveInteger( &stack );
}
else
{
arc.ArchiveInteger( &stack );
if( stack )
{
m_Stack = new ScriptStack;
m_Stack->m_Array = new ScriptVariable[ stack ];
m_Stack->m_Count = stack;
}
}
for( int i = 1; i <= stack; i++ )
{
m_Stack->m_Array[ i ].ArchiveInternal( arc );
}
m_ReturnValue.ArchiveInternal( arc );
m_ScriptClass->ArchiveCodePos( arc, &m_PrevCodePos );
m_ScriptClass->ArchiveCodePos( arc, &m_CodePos );
arc.ArchiveByte( &state );
arc.ArchiveByte( &m_ThreadState );
}
/*
====================
error
Triggers an error
====================
*/
void ScriptVM::error( const char *format, ... )
{
char buffer[ 4000 ];
va_list va;
va_start( va, format );
vsprintf( buffer, format, va );
va_end( va );
glbs.Printf( "----------------------------------------------------------\n%s\n", buffer );
m_ReturnValue.setStringValue( "$.INTERRUPTED" );
}
/*
====================
executeCommand
====================
*/
void ScriptVM::executeCommand( Listener *listener, int iParamCount, int eventnum, bool bReturn )
{
Event ev;
ScriptVariable *var;
ev = Event( eventnum );
if( bReturn )
{
var = pTop;
}
else
{
var = pTop + 1;
}
ev.dataSize = iParamCount;
ev.data = new ScriptVariable[ ev.dataSize ];
ev.fromScript = true;
for( int i = 0; i < iParamCount; i++ ) {
ev.data[ i ] = var[ i ];
}
listener->ProcessScriptEvent( ev );
if( ev.NumArgs() > iParamCount ) {
*pTop = ev.GetValue( ev.NumArgs() );
} else {
pTop->Clear();
}
}
/*
====================
executeGetter
====================
*/
bool ScriptVM::executeGetter( Listener *listener, str& name )
{
Event ev;
int eventnum = Event::FindGetterEventNum( name );
if( eventnum && listener->classinfo()->GetDef( eventnum ) )
{
ev = Event( eventnum );
ev.fromScript = true;
if( listener->ProcessScriptEvent( ev ) )
{
if( ev.NumArgs() > 0 ) {
*pTop = ev.GetValue( ev.NumArgs() );
} else {
pTop->Clear();
}
return true;
}
else
{
return false;
}
}
else
{
eventnum = Event::FindSetterEventNum( name );
assert( !eventnum || !listener->classinfo()->GetDef( eventnum ) );
if( eventnum && listener->classinfo()->GetDef( eventnum ) )
{
ScriptError( "Cannot get a write-only variable" );
}
}
return false;
}
/*
====================
executeSetter
====================
*/
bool ScriptVM::executeSetter( Listener *listener, str& name )
{
Event ev;
int eventnum = Event::FindSetterEventNum( name );
if( eventnum && listener->classinfo()->GetDef( eventnum ) )
{
ev = Event( eventnum );
ev.fromScript = true;
ev.AddValue( *pTop );
if( listener->ProcessScriptEvent( ev ) )
{
return true;
}
else
{
return false;
}
}
else
{
eventnum = Event::FindGetterEventNum( name );
if( eventnum && listener->classinfo()->GetDef( eventnum ) )
{
ScriptError( "Cannot set a read-only variable" );
}
}
return false;
}
/*
====================
jump
====================
*/
void ScriptVM::jump( int offset )
{
m_CodePos += offset;
}
/*
====================
jumpBool
====================
*/
void ScriptVM::jumpBool( int offset, bool booleanValue )
{
if( booleanValue )
{
jump( offset );
}
else
{
m_CodePos += sizeof( unsigned int );
}
}
/*
====================
loadTop
====================
*/
void ScriptVM::loadTop( Listener *listener, bool noTop )
{
int index;
index = *reinterpret_cast< int * >( m_CodePos );
m_CodePos += sizeof( unsigned int );
if( index != -1 )
{
str& variable = Director.GetString( index );
if( !executeSetter( listener, variable ) )
{
listener->Vars()->SetVariable( variable, *pTop );
}
}
if( !noTop ) {
pTop--;
}
}
/*
====================
storeTop
====================
*/
void ScriptVM::storeTop( Listener *listener, bool noTop )
{
str variable;
int index;
index = *reinterpret_cast< int * >( m_CodePos );
m_CodePos += sizeof( unsigned int );
if( index != -1 )
{
variable = Director.GetString( index );
}
if( !noTop ) {
pTop++;
}
if( index != -1 && !executeGetter( listener, variable ) )
{
*pTop = *listener->Vars()->GetOrCreateVariable( index );
}
}
/*
====================
ProgBuffer
Returns the current program buffer
====================
*/
unsigned char *ScriptVM::ProgBuffer( void )
{
return m_CodePos;
}
/*
====================
EnterFunction
Sets a new instruction pointer
====================
*/
void ScriptVM::EnterFunction( Event *ev )
{
ScriptCallStack *stack;
str label = ev->GetString( 1 );
SetFastData( ev->data + 1, ev->dataSize - 1 );
unsigned char *codePos = m_ScriptClass->FindLabel( label );
if( !codePos )
{
ScriptError( "ScriptVM::EnterFunction: label '%s' does not exist in '%s'.", label.c_str(), Filename().c_str() );
}
stack = new ScriptCallStack;
stack->codePos = m_CodePos;
stack->pTop = pTop;
stack->returnValue = m_ReturnValue;
stack->localStack = localStack;
stack->m_Self = m_ScriptClass->GetSelf();
callStack.AddObject( stack );
m_CodePos = codePos;
localStack = new ScriptVariable[ localStackSize ];
pTop = localStack;
m_ReturnValue.Clear();
}
/*
====================
LeaveFunction
Returns to the previous function
====================
*/
void ScriptVM::LeaveFunction()
{
int num = callStack.NumObjects();
if( num )
{
ScriptCallStack *stack = callStack.ObjectAt( num );
pTop = stack->pTop;
*pTop = m_ReturnValue;
m_CodePos = stack->codePos;
m_ReturnValue = stack->returnValue;
m_ScriptClass->m_Self = stack->m_Self;
delete[] localStack;
localStack = stack->localStack;
delete stack;
callStack.RemoveObjectAt( num );
}
else
{
delete m_Thread;
}
}
/*
====================
End
End with a return value
====================
*/
void ScriptVM::End( const ScriptVariable& returnValue )
{
m_ReturnValue.setPointer( returnValue );
LeaveFunction();
}
/*
====================
End
====================
*/
void ScriptVM::End()
{
m_ReturnValue.ClearPointer();
LeaveFunction();
}
/*
====================
Execute
Executes a program
====================
*/
void ScriptVM::Execute( ScriptVariable *data, int dataSize, str label )
{
unsigned char *opcode;
bool doneProcessing = false;
bool deleteThread = false;
bool eventCalled = false;
ScriptVariable *a;
ScriptVariable *b;
ScriptVariable *c;
int index, iParamCount;
Listener *listener;
Event ev;
Event *ev2;
ScriptVariable *var = NULL;
static str str_null = "";
str& value = str_null;
ConSimple *targetList;
if( label != "" )
{
// Throw if label is not found
if( !( m_CodePos = m_ScriptClass->FindLabel( label ) ) )
{
ScriptError( "ScriptVM::Execute: label '%s' does not exist in '%s'.", label.c_str(), Filename().c_str() );
}
}
if( Director.stackCount >= MAX_STACK_DEPTH )
{
state = STATE_EXECUTION;
ScriptException::next_abort = -1;
ScriptException exc( "stack overflow" );
throw exc;
}
Director.stackCount++;
if( dataSize )
{
SetFastData( data, dataSize );
}
state = STATE_RUNNING;
Director.cmdTime = glbs.Milliseconds();
Director.cmdCount = 0;
while( !doneProcessing && state == STATE_RUNNING )
{
m_PrevCodePos = m_CodePos;
Director.cmdCount++;
try
{
if( !Director.m_bAllowContextSwitch )
{
if( Director.cmdCount > 9999 && glbs.Milliseconds() - Director.cmdTime > Director.maxTime )
{
if( level.m_LoopProtection )
{
Director.cmdTime = glbs.Milliseconds();
deleteThread = true;
state = STATE_EXECUTION;
if( level.m_LoopDrop ) {
ScriptException::next_abort = -1;
}
ScriptError( "Command overflow. Possible infinite loop in thread.\n" );
}
VM_DPrintf( "Update of script position - This is not an error.\n" );
VM_DPrintf( "=================================================\n" );
m_ScriptClass->GetScript()->PrintSourcePos( opcode, true );
VM_DPrintf( "=================================================\n" );
Director.cmdCount = 0;
}
}
else
{
if( glbs.Milliseconds() - Director.cmdTime > Director.maxTime )
{
// Request a context switch
RequestContextSwitch();
}
}
if( !m_bMarkStack )
{
assert( pTop >= localStack && pTop < localStack + localStackSize );
if( pTop < localStack )
{
deleteThread = true;
state = STATE_EXECUTION;
error( "VM stack error. Negative stack value %d.\n", pTop - localStack );
break;
}
else if( pTop >= localStack + localStackSize )
{
deleteThread = true;
state = STATE_EXECUTION;
error( "VM stack error. Exceeded the maximum stack size %d.\n", localStackSize );
break;
}
}
index = 0;
eventCalled = false;
opcode = m_CodePos++;
switch( *opcode )
{
case OP_BIN_BITWISE_AND:
a = pTop--;
b = pTop;
*b &= *a;
break;
case OP_BIN_BITWISE_OR:
a = pTop--;
b = pTop;
*b |= *a;
break;
case OP_BIN_BITWISE_EXCL_OR:
a = pTop--;
b = pTop;
*b ^= *a;
break;
case OP_BIN_EQUALITY:
a = pTop--;
b = pTop;
b->setIntValue( *b == *a );
break;
case OP_BIN_INEQUALITY:
a = pTop--;
b = pTop;
b->setIntValue( *b != *a );
break;
case OP_BIN_GREATER_THAN:
a = pTop--;
b = pTop;
b->greaterthan( *a );
break;
case OP_BIN_GREATER_THAN_OR_EQUAL:
a = pTop--;
b = pTop;
b->greaterthanorequal( *a );
break;
case OP_BIN_LESS_THAN:
a = pTop--;
b = pTop;
b->lessthan( *a );
break;
case OP_BIN_LESS_THAN_OR_EQUAL:
a = pTop--;
b = pTop;
b->lessthanorequal( *a );
break;
case OP_BIN_PLUS:
a = pTop--;
b = pTop;
*b += *a;
break;
case OP_BIN_MINUS:
a = pTop--;
b = pTop;
*b -= *a;
break;
case OP_BIN_MULTIPLY:
a = pTop--;
b = pTop;
*b *= *a;
break;
case OP_BIN_DIVIDE:
a = pTop--;
b = pTop;
*b /= *a;
break;
case OP_BIN_PERCENTAGE:
a = pTop--;
b = pTop;
*b %= *a;
break;
case OP_BIN_SHIFT_LEFT:
a = pTop--;
b = pTop;
*b <<= *a;
break;
case OP_BIN_SHIFT_RIGHT:
a = pTop--;
b = pTop;
*b >>= *a;
break;
case OP_BOOL_JUMP_FALSE4:
jumpBool( *reinterpret_cast< unsigned int * >( m_CodePos ) + sizeof( unsigned int ), !pTop->m_data.intValue );
pTop--;
break;
case OP_BOOL_JUMP_TRUE4:
jumpBool( *reinterpret_cast< unsigned int * >( m_CodePos ) + sizeof( unsigned int ), pTop->m_data.intValue ? true : false );
pTop--;
break;
case OP_VAR_JUMP_FALSE4:
jumpBool( *reinterpret_cast< unsigned int * >( m_CodePos ) + sizeof( unsigned int ), !pTop->booleanValue() );
pTop--;
break;
case OP_VAR_JUMP_TRUE4:
jumpBool( *reinterpret_cast< unsigned int * >( m_CodePos ) + sizeof( unsigned int ), pTop->booleanValue() );
pTop--;
break;
case OP_BOOL_LOGICAL_AND:
if( pTop->m_data.intValue )
{
pTop--;
m_CodePos += sizeof( unsigned int );
}
else
{
m_CodePos += *reinterpret_cast< unsigned int * >( m_CodePos ) + sizeof( unsigned int );
}
break;
case OP_BOOL_LOGICAL_OR:
if( !pTop->m_data.intValue )
{
pTop--;
m_CodePos += sizeof( unsigned int );
}
else
{
m_CodePos += *reinterpret_cast< unsigned int * >( m_CodePos ) + sizeof( unsigned int );
}
break;
case OP_VAR_LOGICAL_AND:
if( pTop->booleanValue() )
{
pTop--;
m_CodePos += sizeof( unsigned int );
}
else
{
pTop->SetFalse();
m_CodePos += *reinterpret_cast< unsigned int * >( m_CodePos ) + sizeof( unsigned int );
}
break;
case OP_VAR_LOGICAL_OR:
if( !pTop->booleanValue() )
{
pTop--;
m_CodePos += sizeof( unsigned int );
}
else
{
pTop->SetTrue();
m_CodePos += *reinterpret_cast< unsigned int * >( m_CodePos ) + sizeof( unsigned int );
}
break;
case OP_BOOL_STORE_FALSE:
pTop++;
pTop->SetFalse();
break;
case OP_BOOL_STORE_TRUE:
pTop++;
pTop->SetTrue();
break;
case OP_BOOL_UN_NOT:
pTop->m_data.intValue = ( pTop->m_data.intValue == 0 );
break;
case OP_CALC_VECTOR:
c = pTop--;
b = pTop--;
a = pTop;
pTop->setVectorValue( Vector( a->floatValue(), b->floatValue(), c->floatValue() ) );
break;
case OP_EXEC_CMD0:
iParamCount = 0;
goto __execCmd;
case OP_EXEC_CMD1:
iParamCount = 1;
goto __execCmd;
case OP_EXEC_CMD2:
iParamCount = 2;
goto __execCmd;
case OP_EXEC_CMD3:
iParamCount = 3;
goto __execCmd;
case OP_EXEC_CMD4:
iParamCount = 4;
goto __execCmd;
case OP_EXEC_CMD5:
iParamCount = 5;
goto __execCmd;
case OP_EXEC_CMD_COUNT1:
iParamCount = *m_CodePos++;
__execCmd:
index = *reinterpret_cast< unsigned int * >( m_CodePos );
m_CodePos += sizeof( unsigned int );
pTop -= iParamCount;
try
{
executeCommand( m_Thread, iParamCount, index );
}
catch( ScriptException& exc )
{
throw exc;
}
break;
case OP_EXEC_CMD_METHOD0:
iParamCount = 0;
goto __execCmdMethod;
case OP_EXEC_CMD_METHOD1:
iParamCount = 1;
goto __execCmdMethod;
case OP_EXEC_CMD_METHOD2:
iParamCount = 2;
goto __execCmdMethod;
case OP_EXEC_CMD_METHOD3:
iParamCount = 3;
goto __execCmdMethod;
case OP_EXEC_CMD_METHOD4:
iParamCount = 4;
goto __execCmdMethod;
case OP_EXEC_CMD_METHOD5:
iParamCount = 5;
goto __execCmdMethod;
__execCmdMethod:
m_CodePos--;
goto __execCmdMethodInternal;
case OP_EXEC_CMD_METHOD_COUNT1:
iParamCount = *m_CodePos;
__execCmdMethodInternal:
a = pTop--;
try
{
index = *reinterpret_cast< unsigned int * >( m_CodePos + sizeof( byte ) );
pTop -= iParamCount;
if( a->arraysize() < 0 )
{
ScriptError( "command '%s' applied to NIL", Event::GetEventName( index ).c_str() );
}
ScriptVariable array = *a;
Listener *listener;
array.CastConstArrayValue();
for( int i = array.arraysize(); i > 0; i-- )
{
if( !( listener = array[ i ]->listenerValue() ) )
{
ScriptError( "command '%s' applied to NULL listener", Event::GetEventName( index ).c_str() );
}
executeCommand( listener, iParamCount, index );
}
}
catch( ScriptException& exc )
{
m_CodePos += sizeof( byte ) + sizeof( unsigned int );
throw exc;
}
m_CodePos += sizeof( byte ) + sizeof( unsigned int );
break;
case OP_EXEC_METHOD0:
iParamCount = 0;
goto __execMethod;
case OP_EXEC_METHOD1:
iParamCount = 1;
goto __execMethod;
case OP_EXEC_METHOD2:
iParamCount = 2;
goto __execMethod;
case OP_EXEC_METHOD3:
iParamCount = 3;
goto __execMethod;
case OP_EXEC_METHOD4:
iParamCount = 4;
goto __execMethod;
case OP_EXEC_METHOD5:
iParamCount = 5;
__execMethod:
m_CodePos--;
goto __execMethodInternal;
case OP_EXEC_METHOD_COUNT1:
iParamCount = *m_CodePos;
__execMethodInternal:
a = pTop--;
try
{
index = *reinterpret_cast< unsigned int * >( m_CodePos + sizeof( byte ) );
pTop -= iParamCount;
pTop++; // push the return value
Listener *listener = a->listenerValue();
if( !listener )
{
ScriptError( "command '%s' applied to NULL listener", Event::GetEventName( index ).c_str() );
}
executeCommand( listener, iParamCount, index, true );
}
catch( ScriptException& exc )
{
m_CodePos += sizeof( byte ) + sizeof( unsigned int );
throw exc;
}
m_CodePos += sizeof( byte ) + sizeof( unsigned int );
break;
case OP_FUNC:
ev.Clear();
if( !*m_CodePos++ )
{
str& label = Director.GetString( *reinterpret_cast< unsigned int * >( m_CodePos ) );
m_CodePos += sizeof( unsigned int );
try
{
listener = pTop->listenerValue();
if( !listener )
{
ScriptError( "function '%s' applied to NULL listener", label.c_str() );
}
}
catch( ScriptException& exc )
{
pTop -= *m_CodePos++;
throw exc;
}
pTop--;
ev.AddString( label );
int params = *m_CodePos++;
var = pTop;
pTop -= params;
for( int i = 0; i < params; var++, i++ ) {
ev.AddValue( *var );
}
pTop++;
EnterFunction( &ev );
m_ScriptClass->m_Self = listener;
}
else
{
str filename, label;
filename = Director.GetString( *reinterpret_cast< unsigned int * >( m_CodePos ) );
m_CodePos += sizeof( unsigned int );
label = Director.GetString( *reinterpret_cast< unsigned int * >( m_CodePos ) );
m_CodePos += sizeof( unsigned int );
try
{
listener = pTop->listenerValue();
if( !listener )
{
ScriptError( "function '%s' in '%s' applied to NULL listener", label.c_str(), filename.c_str() );
}
}
catch( ScriptException& exc )
{
pTop -= *m_CodePos++;
throw exc;
}
pTop--;
ScriptVariable constarray;
ScriptVariable *pVar = new ScriptVariable[ 2 ];
pVar[ 0 ].setStringValue( filename );
pVar[ 1 ].setStringValue( label );
constarray.setConstArrayValue( pVar, 2 );
delete[] pVar;
ev2 = new Event( EV_Listener_WaitCreateReturnThread );
ev2->AddValue( constarray );
int params = *m_CodePos++;
var = pTop;
pTop -= params;
for( int i = 0; i < params; var++, i++ ) {
ev2->AddValue( *var );
}
pTop++;
*pTop = listener->ProcessEventReturn( ev2 );
}
break;
case OP_JUMP4:
m_CodePos += *reinterpret_cast< int * >( m_CodePos ) + sizeof( unsigned int );
break;
case OP_JUMP_BACK4:
m_CodePos -= *reinterpret_cast< int * >( m_CodePos );
break;
case OP_LOAD_ARRAY_VAR:
a = pTop--;
b = pTop--;
c = pTop--;
b->setArrayAt( *a, *c );
break;
case OP_LOAD_FIELD_VAR:
a = pTop--;
try
{
listener = a->listenerValue();
if( listener == NULL )
{
value = Director.GetString( *reinterpret_cast< int * >( m_CodePos ) );
ScriptError( "Field '%s' applied to NULL listener", value.c_str() );
}
else
{
eventCalled = true;
loadTop( listener );
}
}
catch( ScriptException& exc )
{
pTop--;
if( !eventCalled ) {
m_CodePos += sizeof( unsigned int );
}
throw exc;
}
break;
case OP_LOAD_CONST_ARRAY1:
index = *reinterpret_cast< short * >( m_CodePos );
m_CodePos += sizeof( short );
pTop -= index - 1;
pTop->setConstArrayValue( pTop, index );
break;
case OP_LOAD_GAME_VAR:
loadTop( &game );
break;
case OP_LOAD_GROUP_VAR:
loadTop( m_ScriptClass );
break;
case OP_LOAD_LEVEL_VAR:
loadTop( &level );
break;
case OP_LOAD_LOCAL_VAR:
loadTop( m_Thread );
break;
case OP_LOAD_OWNER_VAR:
if( !m_ScriptClass->m_Self )
{
pTop--;
m_CodePos += sizeof( unsigned int );
ScriptError( "self is NULL" );
}
if( !m_ScriptClass->m_Self->GetScriptOwner() )
{
pTop--;
m_CodePos += sizeof( unsigned int );
ScriptError( "self.owner is NULL" );
}
loadTop( m_ScriptClass->m_Self->GetScriptOwner() );
break;
case OP_LOAD_PARM_VAR:
loadTop( &parm );
break;
case OP_LOAD_SELF_VAR:
if( !m_ScriptClass->m_Self )
{
pTop--;
m_CodePos += sizeof( unsigned int );
ScriptError( "self is NULL" );
}
loadTop( m_ScriptClass->m_Self );
break;
case OP_LOAD_STORE_GAME_VAR:
loadTop( &game, true );
break;
case OP_LOAD_STORE_GROUP_VAR:
loadTop( m_ScriptClass, true );
break;
case OP_LOAD_STORE_LEVEL_VAR:
loadTop( &level, true );
break;
case OP_LOAD_STORE_LOCAL_VAR:
loadTop( m_Thread, true );
break;
case OP_LOAD_STORE_OWNER_VAR:
if( !m_ScriptClass->m_Self )
{
m_CodePos += sizeof( unsigned int );
ScriptError( "self is NULL" );
}
if( !m_ScriptClass->m_Self->GetScriptOwner() )
{
m_CodePos += sizeof( unsigned int );
ScriptError( "self.owner is NULL" );
}
loadTop( m_ScriptClass->m_Self->GetScriptOwner(), true );
break;
case OP_LOAD_STORE_PARM_VAR:
loadTop( &parm, true );
break;
case OP_LOAD_STORE_SELF_VAR:
if( !m_ScriptClass->m_Self )
{
ScriptError( "self is NULL" );
}
loadTop( m_ScriptClass->m_Self, true );
break;
case OP_MARK_STACK_POS:
m_StackPos = pTop;
m_bMarkStack = true;
break;
case OP_STORE_PARAM:
if( fastEvent.dataSize )
{
pTop = fastEvent.data++;
fastEvent.dataSize--;
}
else
{
pTop = m_StackPos + 1;
pTop->Clear();
}
break;
case OP_RESTORE_STACK_POS:
pTop = m_StackPos;
m_bMarkStack = false;
break;
case OP_STORE_ARRAY:
pTop--;
pTop->evalArrayAt( *( pTop + 1 ) );
break;
case OP_STORE_ARRAY_REF:
pTop--;
pTop->setArrayRefValue( *( pTop + 1 ) );
break;
case OP_STORE_FIELD_REF:
case OP_STORE_FIELD:
try
{
value = Director.GetString( *reinterpret_cast< int * >( m_CodePos ) );
listener = pTop->listenerValue();
if( listener == NULL )
{
ScriptError( "Field '%s' applied to NULL listener", value.c_str() );
}
else
{
eventCalled = true;
storeTop( listener, true );
}
if( *opcode == OP_STORE_FIELD_REF )
{
pTop->setRefValue( listener->vars->GetOrCreateVariable( value ) );
}
}
catch( ScriptException& exc )
{
if( *opcode == OP_STORE_FIELD_REF )
{
pTop->setRefValue( pTop );
}
if( !eventCalled ) {
m_CodePos += sizeof( unsigned int );
}
throw exc;
}
break;
case OP_STORE_FLOAT:
pTop++;
pTop->setFloatValue( *reinterpret_cast< float * >( m_CodePos ) );
m_CodePos += sizeof( float );
break;
case OP_STORE_INT0:
pTop++;
pTop->setIntValue( 0 );
break;
case OP_STORE_INT1:
pTop++;
pTop->setIntValue( *m_CodePos++ );
break;
case OP_STORE_INT2:
pTop++;
pTop->setIntValue( *reinterpret_cast< short * >( m_CodePos ) );
m_CodePos += sizeof( short );
break;
case OP_STORE_INT3:
pTop++;
pTop->setIntValue( *reinterpret_cast< short3 * >( m_CodePos ) );
m_CodePos += sizeof( short3 );
break;
case OP_STORE_INT4:
pTop++;
pTop->setIntValue( *reinterpret_cast< int * >( m_CodePos ) );
m_CodePos += sizeof( int );
break;
case OP_STORE_GAME_VAR:
storeTop( &game );
break;
case OP_STORE_GROUP_VAR:
storeTop( m_ScriptClass );
break;
case OP_STORE_LEVEL_VAR:
storeTop( &level );
break;
case OP_STORE_LOCAL_VAR:
storeTop( m_Thread );
break;
case OP_STORE_OWNER_VAR:
if( !m_ScriptClass->m_Self )
{
pTop++;
m_CodePos += sizeof( unsigned int );
ScriptError( "self is NULL" );
}
if( !m_ScriptClass->m_Self->GetScriptOwner() )
{
pTop++;
m_CodePos += sizeof( unsigned int );
ScriptError( "self.owner is NULL" );
}
storeTop( m_ScriptClass->m_Self->GetScriptOwner() );
break;
case OP_STORE_PARM_VAR:
storeTop( &parm );
break;
case OP_STORE_SELF_VAR:
if( !m_ScriptClass->m_Self )
{
pTop++;
m_CodePos += sizeof( unsigned int );
ScriptError( "self is NULL" );
}
storeTop( m_ScriptClass->m_Self );
break;
case OP_STORE_GAME:
pTop++;
pTop->setListenerValue( &game );
break;
case OP_STORE_GROUP:
pTop++;
pTop->setListenerValue( m_ScriptClass );
break;
case OP_STORE_LEVEL:
pTop++;
pTop->setListenerValue( &level );
break;
case OP_STORE_LOCAL:
pTop++;
pTop->setListenerValue( m_Thread );
break;
case OP_STORE_OWNER:
pTop++;
if( !m_ScriptClass->m_Self )
{
pTop++;
ScriptError( "self is NULL" );
}
pTop->setListenerValue( m_ScriptClass->m_Self->GetScriptOwner() );
break;
case OP_STORE_PARM:
pTop++;
pTop->setListenerValue( &parm );
break;
case OP_STORE_SELF:
pTop++;
pTop->setListenerValue( m_ScriptClass->m_Self );
break;
case OP_STORE_NIL:
pTop++;
pTop->Clear();
break;
case OP_STORE_NULL:
pTop++;
pTop->setListenerValue( NULL );
break;
case OP_STORE_STRING:
pTop++;
pTop->setConstStringValue( *reinterpret_cast< unsigned int * >( m_CodePos ) );
m_CodePos += sizeof( unsigned int );
break;
case OP_STORE_VECTOR:
pTop++;
pTop->setVectorValue( *reinterpret_cast< Vector * >( m_CodePos ) );
m_CodePos += sizeof( Vector );
break;
case OP_SWITCH:
if( !Switch( *reinterpret_cast< StateScript ** >( m_CodePos ), *pTop ) )
{
m_CodePos += sizeof( unsigned int );
}
pTop--;
break;
case OP_UN_CAST_BOOLEAN:
pTop->CastBoolean();
break;
case OP_UN_COMPLEMENT:
pTop->complement();
break;
case OP_UN_MINUS:
pTop->minus();
break;
case OP_UN_DEC:
( *pTop )--;
break;
case OP_UN_INC:
( *pTop )++;
break;
case OP_UN_SIZE:
pTop->setIntValue( pTop->size() );
break;
case OP_UN_TARGETNAME:
targetList = world->GetExistingTargetList( pTop->stringValue() );
if( !targetList )
{
value = pTop->stringValue();
pTop->setListenerValue( NULL );
if( *m_CodePos >= OP_BIN_EQUALITY && *m_CodePos <= OP_BIN_GREATER_THAN_OR_EQUAL || *m_CodePos >= OP_BOOL_UN_NOT && *m_CodePos <= OP_UN_CAST_BOOLEAN ) {
ScriptError( "Targetname '%s' does not exist.", value.c_str() );
}
break;
}
if( targetList->NumObjects() == 1 )
{
pTop->setListenerValue( targetList->ObjectAt( 1 ) );
}
else if( targetList->NumObjects() > 1 )
{
pTop->setContainerValue( ( Container< SafePtr< Listener > > * )targetList );
}
else
{
value = pTop->stringValue();
pTop->setListenerValue( NULL );
if( *m_CodePos >= OP_BIN_EQUALITY && *m_CodePos <= OP_BIN_GREATER_THAN_OR_EQUAL || *m_CodePos >= OP_BOOL_UN_NOT && *m_CodePos <= OP_UN_CAST_BOOLEAN ) {
ScriptError( "Targetname '%s' does not exist.", value.c_str() );
}
break;
}
break;
case OP_VAR_UN_NOT:
pTop->setIntValue( pTop->booleanValue() );
break;
case OP_DONE:
End();
break;
case OP_NOP:
break;
default:
if( *opcode < OP_MAX )
{
glbs.DPrintf( "unknown opcode %d ('%s')\n", *opcode, OpcodeName( *opcode ) );
}
else
{
glbs.DPrintf( "unknown opcode %d\n", *opcode );
}
break;
}
}
catch( ScriptException& exc )
{
HandleScriptException( exc );
}
}
Director.stackCount--;
if( deleteThread || state == STATE_WAITING )
{
delete m_Thread;
}
else if( state == STATE_SUSPENDED )
{
state = STATE_EXECUTION;
}
if( state == STATE_DESTROYED )
{
delete this;
}
}
/*
====================
HandleScriptException
====================
*/
void ScriptVM::HandleScriptException( ScriptException& exc )
{
if( m_ScriptClass )
{
m_ScriptClass->GetScript()->PrintSourcePos( m_PrevCodePos, true );
}
else
{
glbs.DPrintf( "unknown source pos" );
}
if( exc.bAbort )
{
ScriptException e( exc.string );
e.bAbort = exc.bAbort;
e.bIsForAnim = exc.bIsForAnim;
state = STATE_EXECUTION;
throw e;
}
glbs.Printf( "^~^~^ Script Error : %s\n\n", exc.string.c_str() );
}
/*
====================
SetFastData
Sets the starting virtual machine parameters
====================
*/
void ScriptVM::SetFastData( ScriptVariable *data, int dataSize )
{
if( fastEvent.data )
{
fastEvent.data = m_pOldData;
fastEvent.dataSize = m_OldDataSize;
fastEvent.Clear();
m_pOldData = NULL;
m_OldDataSize = 0;
}
if( dataSize )
{
fastEvent.data = new ScriptVariable[ dataSize ];
fastEvent.dataSize = dataSize;
for( int i = 0; i < dataSize; i++ )
{
fastEvent.data[ i ] = data[ i ];
}
m_pOldData = fastEvent.data;
m_OldDataSize = fastEvent.dataSize;
}
}
/*
====================
NotifyDelete
====================
*/
void ScriptVM::NotifyDelete( void )
{
switch( state )
{
case STATE_DESTROYED:
ScriptError( "Attempting to delete a dead thread." );
break;
case STATE_RUNNING:
case STATE_SUSPENDED:
case STATE_WAITING:
state = STATE_DESTROYED;
if( m_ScriptClass ) {
m_ScriptClass->RemoveThread( this );
}
break;
case STATE_EXECUTION:
state = STATE_DESTROYED;
if( m_ScriptClass ) {
m_ScriptClass->RemoveThread( this );
}
delete this;
break;
}
}
/*
====================
Resume
====================
*/
void ScriptVM::Resume( qboolean bForce )
{
if( state == STATE_SUSPENDED || ( bForce && state != STATE_DESTROYED ) ) {
state = STATE_RUNNING;
}
}
/*
====================
Suspend
====================
*/
void ScriptVM::Suspend()
{
if( state == STATE_DESTROYED ) {
ScriptError( "Cannot suspend a dead thread." );
} else if( !state ) {
state = STATE_SUSPENDED;
}
}
/*
====================
Switch
Switch statement
====================
*/
bool ScriptVM::Switch( StateScript *stateScript, ScriptVariable& var )
{
unsigned char *pos;
fastEvent.dataSize = 0;
pos = stateScript->FindLabel( var.stringValue() );
if( !pos )
{
pos = stateScript->FindLabel( "" );
if( !pos ) {
return false;
}
}
m_CodePos = pos;
return true;
}
/*
====================
Filename
====================
*/
str ScriptVM::Filename( void )
{
return m_ScriptClass->Filename();
}
/*
====================
Label
====================
*/
str ScriptVM::Label( void )
{
const_str label = m_ScriptClass->NearestLabel( m_CodePos );
if( !label )
{
return "";
}
return Director.GetString( label );
}
/*
====================
GetScriptClass
====================
*/
ScriptClass *ScriptVM::GetScriptClass( void )
{
return m_ScriptClass;
}
/*
====================
IsSuspended
====================
*/
bool ScriptVM::IsSuspended( void )
{
return state == STATE_SUSPENDED;
}
/*
====================
State
====================
*/
int ScriptVM::State( void )
{
return state;
}
/*
====================
ThreadState
====================
*/
int ScriptVM::ThreadState( void )
{
return m_ThreadState;
}
/*
====================
EventGoto
====================
*/
void ScriptVM::EventGoto( Event *ev )
{
str label = ev->GetString( 1 );
SetFastData( ev->data + 1, ev->dataSize - 1 );
unsigned char *codePos = m_ScriptClass->FindLabel( label );
if( !codePos )
{
ScriptError( "ScriptVM::EventGoto: label '%s' does not exist in '%s'.", label.c_str(), Filename().c_str() );
}
m_CodePos = codePos;
}
/*
====================
EventThrow
Called when throwing an exception
====================
*/
bool ScriptVM::EventThrow( Event *ev )
{
str label = ev->GetString( 1 );
SetFastData( ev->data, ev->dataSize );
fastEvent.eventnum = ev->eventnum;
while( 1 )
{
StateScript *stateScript = m_ScriptClass->GetCatchStateScript( m_PrevCodePos, m_PrevCodePos );
if( !stateScript ) {
break;
}
m_CodePos = stateScript->FindLabel( label );
if( m_CodePos )
{
fastEvent.data++;
fastEvent.dataSize--;
return true;
}
}
return false;
}
/*
====================
AllowContextSwitch
====================
*/
void ScriptVM::AllowContextSwitch( bool allow )
{
m_bAllowContextSwitch = allow;
}
/*
====================
RequestContextSwitch
Requests a context switch
====================
*/
void ScriptVM::RequestContextSwitch( void )
{
if( !m_bAllowContextSwitch || !Director.m_bAllowContextSwitch ) {
return;
}
//glbs.DPrintf( "Performing context switch\n" );
Director.AddContextSwitch( m_Thread );
m_ThreadState = THREAD_CONTEXT_SWITCH;
Suspend();
}