mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 21:57:57 +03:00

This fixes an issue when loading from a save. A variable referencing target list container would have random entities in it, which would cause errors and crashes when trying to do an action on the array of entities
2230 lines
55 KiB
C++
2230 lines
55 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2023 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 "../fgame/g_local.h"
|
|
#include "../fgame/scriptmaster.h"
|
|
#include "../fgame/scriptthread.h"
|
|
#include "scriptclass.h"
|
|
#include "scriptvm.h"
|
|
#include "scriptcompiler.h"
|
|
#include "scriptexception.h"
|
|
#include "../fgame/game.h"
|
|
#include "../fgame/level.h"
|
|
#include "../fgame/parm.h"
|
|
#include "../fgame/worldspawn.h"
|
|
|
|
#include <utility>
|
|
|
|
#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
|
|
|
|
static const ScriptVM *currentScriptFile;
|
|
static unsigned int currentScriptLine;
|
|
|
|
class ScriptCommandEvent : public Event
|
|
{
|
|
public:
|
|
ScriptCommandEvent(unsigned int eventNum);
|
|
ScriptCommandEvent(unsigned int eventNum, int numArgs);
|
|
};
|
|
|
|
ScriptCommandEvent::ScriptCommandEvent(unsigned int eventNum)
|
|
: Event(eventNum)
|
|
{
|
|
fromScript = true;
|
|
}
|
|
|
|
ScriptCommandEvent::ScriptCommandEvent(unsigned int eventNum, int numArgs)
|
|
: Event(eventNum, numArgs)
|
|
{
|
|
fromScript = true;
|
|
}
|
|
|
|
ScriptVMStack::ScriptVMStack()
|
|
: localStack(nullptr)
|
|
, stackBottom(nullptr)
|
|
, pTop(nullptr)
|
|
, m_bMarkStack(false)
|
|
{}
|
|
|
|
ScriptVMStack::ScriptVMStack(size_t stackSize)
|
|
: ScriptVMStack()
|
|
{
|
|
Allocate(stackSize);
|
|
}
|
|
|
|
ScriptVMStack::ScriptVMStack(ScriptVMStack&& other)
|
|
: localStack(other.localStack)
|
|
, stackBottom(other.stackBottom)
|
|
, pTop(other.pTop)
|
|
{
|
|
other.localStack = other.stackBottom = nullptr;
|
|
other.pTop = nullptr;
|
|
}
|
|
|
|
ScriptVMStack& ScriptVMStack::operator=(ScriptVMStack&& other)
|
|
{
|
|
localStack = other.localStack;
|
|
stackBottom = other.stackBottom;
|
|
pTop = other.pTop;
|
|
other.localStack = other.stackBottom = nullptr;
|
|
other.pTop = nullptr;
|
|
|
|
return *this;
|
|
}
|
|
|
|
ScriptVMStack::~ScriptVMStack()
|
|
{
|
|
Free();
|
|
}
|
|
|
|
void ScriptVMStack::Allocate(size_t stackSize)
|
|
{
|
|
if (localStack) {
|
|
// free the current local stack
|
|
Free();
|
|
}
|
|
|
|
if (!stackSize) {
|
|
stackSize = 1;
|
|
}
|
|
|
|
// allocate at once
|
|
uint8_t *data = (uint8_t *)gi.Malloc((sizeof(ScriptVariable) + sizeof(ScriptVariable *)) * stackSize);
|
|
localStack = new (data) ScriptVariable[stackSize];
|
|
data += sizeof(ScriptVariable) * stackSize;
|
|
|
|
pTop = localStack;
|
|
stackBottom = localStack + stackSize;
|
|
}
|
|
|
|
void ScriptVMStack::Free()
|
|
{
|
|
const size_t localStackSize = GetStackSize();
|
|
for (uintptr_t i = 0; i < localStackSize; ++i) {
|
|
localStack[i].~ScriptVariable();
|
|
}
|
|
|
|
uint8_t *const data = (uint8_t *)localStack;
|
|
if (data) {
|
|
gi.Free(data);
|
|
}
|
|
}
|
|
|
|
size_t ScriptVMStack::GetStackSize() const
|
|
{
|
|
return stackBottom - localStack;
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::SetTop(ScriptVariable& newTop)
|
|
{
|
|
return *(pTop = &newTop);
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::GetTop() const
|
|
{
|
|
assert(m_bMarkStack || pTop < stackBottom);
|
|
return *pTop;
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::GetTop(size_t offset) const
|
|
{
|
|
assert(m_bMarkStack || (pTop + offset) < stackBottom);
|
|
return *(pTop + offset);
|
|
}
|
|
|
|
ScriptVariable *ScriptVMStack::GetTopPtr() const
|
|
{
|
|
return pTop;
|
|
}
|
|
|
|
ScriptVariable *ScriptVMStack::GetTopPtr(size_t offset) const
|
|
{
|
|
return pTop + offset;
|
|
}
|
|
|
|
ScriptVariable *ScriptVMStack::GetTopArray(size_t offset) const
|
|
{
|
|
return pTop + offset;
|
|
}
|
|
|
|
uintptr_t ScriptVMStack::GetIndex() const
|
|
{
|
|
return pTop - localStack;
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::Pop()
|
|
{
|
|
return *(pTop--);
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::Pop(size_t offset)
|
|
{
|
|
ScriptVariable& old = *pTop;
|
|
pTop -= offset;
|
|
return old;
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::PopAndGet()
|
|
{
|
|
return *--pTop;
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::PopAndGet(size_t offset)
|
|
{
|
|
pTop -= offset;
|
|
return *pTop;
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::Push()
|
|
{
|
|
assert(!m_bMarkStack && (pTop + 1) < stackBottom);
|
|
return *(pTop++);
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::Push(size_t offset)
|
|
{
|
|
ScriptVariable& old = *pTop;
|
|
assert(!m_bMarkStack && (pTop + offset) < stackBottom);
|
|
pTop += offset;
|
|
return old;
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::PushAndGet()
|
|
{
|
|
assert(!m_bMarkStack && (pTop + 1) < stackBottom);
|
|
return *++pTop;
|
|
}
|
|
|
|
ScriptVariable& ScriptVMStack::PushAndGet(size_t offset)
|
|
{
|
|
assert(!m_bMarkStack && (pTop + offset) < stackBottom);
|
|
pTop += offset;
|
|
return *pTop;
|
|
}
|
|
|
|
void ScriptVMStack::Archive(Archiver& arc)
|
|
{
|
|
unsigned int size;
|
|
unsigned int offset;
|
|
unsigned int numVars;
|
|
unsigned int i;
|
|
|
|
if (arc.Saving()) {
|
|
size = stackBottom - localStack;
|
|
offset = pTop - localStack;
|
|
numVars = offset + 1;
|
|
|
|
arc.ArchiveUnsigned(&size);
|
|
arc.ArchiveUnsigned(&offset);
|
|
} else {
|
|
arc.ArchiveUnsigned(&size);
|
|
arc.ArchiveUnsigned(&offset);
|
|
numVars = offset + 1;
|
|
|
|
Allocate(size);
|
|
pTop = localStack + offset;
|
|
}
|
|
|
|
if (localStack) {
|
|
for (i = 0; i < numVars; i++) {
|
|
localStack[i].ArchiveInternal(arc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptVMStack::MoveTop(ScriptVariable&& other)
|
|
{
|
|
*pTop = std::move(other);
|
|
}
|
|
|
|
//====================
|
|
// ScriptVM
|
|
//====================
|
|
|
|
MEM_BlockAlloc<ScriptVM> 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()
|
|
{
|
|
m_Stack = NULL;
|
|
|
|
m_PrevCodePos = NULL;
|
|
m_pOldData = NULL;
|
|
m_OldDataSize = 0;
|
|
|
|
m_StackPos = NULL;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ScriptVM
|
|
====================
|
|
*/
|
|
ScriptVM::ScriptVM(ScriptClass *scriptClass, unsigned char *pCodePos, ScriptThread *thread)
|
|
: m_VMStack(scriptClass->GetScript()->GetRequiredStackSize())
|
|
{
|
|
next = NULL;
|
|
|
|
m_Thread = thread;
|
|
m_ScriptClass = scriptClass;
|
|
|
|
m_Stack = NULL;
|
|
|
|
m_PrevCodePos = NULL;
|
|
m_CodePos = pCodePos;
|
|
|
|
state = STATE_EXECUTION;
|
|
m_ThreadState = THREAD_RUNNING;
|
|
|
|
m_pOldData = NULL;
|
|
m_OldDataSize = 0;
|
|
|
|
m_StackPos = NULL;
|
|
|
|
m_ScriptClass->AddThread(this);
|
|
|
|
if (g_scripttrace->integer && CanScriptTracePrint()) {
|
|
gi.DPrintf2("+++THREAD: %p %p\n", this, m_ScriptClass);
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
~ScriptVM
|
|
====================
|
|
*/
|
|
ScriptVM::~ScriptVM()
|
|
{
|
|
fastEvent.data = m_pOldData;
|
|
fastEvent.dataSize = m_OldDataSize;
|
|
|
|
// clean-up the call stack
|
|
while (callStack.NumObjects()) {
|
|
LeaveFunction();
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
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;
|
|
} else {
|
|
m_Stack = NULL;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
m_VMStack.Archive(arc);
|
|
}
|
|
|
|
/*
|
|
====================
|
|
error
|
|
|
|
Triggers an error
|
|
====================
|
|
*/
|
|
void ScriptVM::error(const char *format, ...)
|
|
{
|
|
char buffer[4000];
|
|
va_list va;
|
|
|
|
va_start(va, format);
|
|
Q_vsnprintf(buffer, sizeof(buffer), format, va);
|
|
va_end(va);
|
|
|
|
gi.Printf("----------------------------------------------------------\n%s\n", buffer);
|
|
m_ReturnValue.setStringValue("$.INTERRUPTED");
|
|
}
|
|
|
|
void ScriptVM::jump(unsigned int offset)
|
|
{
|
|
m_CodePos += offset;
|
|
}
|
|
|
|
void ScriptVM::jumpBack(unsigned int offset)
|
|
{
|
|
m_CodePos -= offset;
|
|
}
|
|
|
|
void ScriptVM::jumpBool(unsigned int offset, bool booleanValue)
|
|
{
|
|
if (booleanValue) {
|
|
jump(offset);
|
|
}
|
|
}
|
|
|
|
bool ScriptVM::jumpVar(unsigned int offset, bool booleanValue)
|
|
{
|
|
if (booleanValue) {
|
|
jump(offset);
|
|
return true;
|
|
} else {
|
|
m_VMStack.Pop();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ScriptVM::doJumpIf(bool booleanValue)
|
|
{
|
|
const unsigned int offset = fetchOpcodeValue<unsigned int>();
|
|
jumpBool(offset, booleanValue);
|
|
}
|
|
|
|
bool ScriptVM::doJumpVarIf(bool booleanValue)
|
|
{
|
|
const unsigned int offset = fetchOpcodeValue<unsigned int>();
|
|
return jumpVar(offset, booleanValue);
|
|
}
|
|
|
|
void ScriptVM::loadTopInternal(Listener *listener)
|
|
{
|
|
const const_str variable = fetchOpcodeValue<op_name_t>();
|
|
|
|
if (!executeSetter(listener, variable)) {
|
|
// just set the variable
|
|
ScriptVariable& pTop = m_VMStack.GetTop();
|
|
listener->Vars()->SetVariable(variable, std::move(pTop));
|
|
}
|
|
}
|
|
|
|
ScriptVariable *ScriptVM::storeTopInternal(Listener *listener)
|
|
{
|
|
const const_str variable = fetchOpcodeValue<op_name_t>();
|
|
ScriptVariable *listenerVar;
|
|
|
|
if (!executeGetter(listener, variable)) {
|
|
ScriptVariable& pTop = m_VMStack.GetTop();
|
|
listenerVar = listener->Vars()->GetOrCreateVariable(variable);
|
|
|
|
pTop = *listenerVar;
|
|
} else {
|
|
listenerVar = nullptr;
|
|
}
|
|
|
|
return listenerVar;
|
|
}
|
|
|
|
void ScriptVM::loadStoreTop(Listener *listener)
|
|
{
|
|
const const_str variable = fetchOpcodeValue<op_name_t>();
|
|
|
|
if (!executeSetter(listener, variable)) {
|
|
// just set the variable
|
|
ScriptVariable& pTop = m_VMStack.GetTop();
|
|
listener->Vars()->SetVariable(variable, pTop);
|
|
}
|
|
}
|
|
|
|
void ScriptVM::skipField()
|
|
{
|
|
m_CodePos += sizeof(unsigned int);
|
|
}
|
|
|
|
template<>
|
|
void ScriptVM::loadTop<false>(Listener *listener)
|
|
{
|
|
loadTopInternal(listener);
|
|
m_VMStack.Pop();
|
|
}
|
|
|
|
template<>
|
|
void ScriptVM::loadTop<true>(Listener *listener)
|
|
{
|
|
loadTopInternal(listener);
|
|
}
|
|
|
|
template<>
|
|
ScriptVariable *ScriptVM::storeTop<false>(Listener *listener)
|
|
{
|
|
m_VMStack.Push();
|
|
|
|
return storeTopInternal(listener);
|
|
}
|
|
|
|
template<>
|
|
ScriptVariable *ScriptVM::storeTop<true>(Listener *listener)
|
|
{
|
|
return storeTopInternal(listener);
|
|
}
|
|
|
|
template<>
|
|
void ScriptVM::executeCommandInternal<false>(
|
|
Event& ev, Listener *listener, ScriptVariable *fromVar, op_parmNum_t iParamCount
|
|
)
|
|
{
|
|
transferVarsToEvent(ev, fromVar, iParamCount);
|
|
listener->ProcessScriptEvent(ev);
|
|
}
|
|
|
|
template<>
|
|
void ScriptVM::executeCommandInternal<true>(
|
|
Event& ev, Listener *listener, ScriptVariable *fromVar, op_parmNum_t iParamCount
|
|
)
|
|
{
|
|
transferVarsToEvent(ev, fromVar, iParamCount);
|
|
|
|
try {
|
|
listener->ProcessScriptEvent(ev);
|
|
} catch (...) {
|
|
m_VMStack.GetTop().Clear();
|
|
throw;
|
|
}
|
|
|
|
ScriptVariable& pTop = m_VMStack.GetTop();
|
|
pTop = std::move(ev.GetValue());
|
|
}
|
|
|
|
template<>
|
|
void ScriptVM::executeCommand<false, false>(Listener *listener, op_parmNum_t iParamCount, op_evName_t eventnum)
|
|
{
|
|
ScriptCommandEvent ev = iParamCount ? ScriptCommandEvent(eventnum, iParamCount) : ScriptCommandEvent(eventnum);
|
|
return executeCommandInternal<false>(ev, listener, m_VMStack.GetTopArray(1), iParamCount);
|
|
}
|
|
|
|
template<>
|
|
void ScriptVM::executeCommand<true, false>(Listener *listener, op_parmNum_t iParamCount, op_evName_t eventnum)
|
|
{
|
|
ScriptCommandEvent ev = iParamCount ? ScriptCommandEvent(eventnum, iParamCount) : ScriptCommandEvent(eventnum);
|
|
return executeCommandInternal<false>(ev, listener, m_VMStack.GetTopArray(1), iParamCount);
|
|
}
|
|
|
|
template<>
|
|
void ScriptVM::executeCommand<false, true>(Listener *listener, op_parmNum_t iParamCount, op_evName_t eventnum)
|
|
{
|
|
ScriptCommandEvent ev = iParamCount ? ScriptCommandEvent(eventnum, iParamCount) : ScriptCommandEvent(eventnum);
|
|
return executeCommandInternal<true>(ev, listener, m_VMStack.GetTopArray(), iParamCount);
|
|
}
|
|
|
|
template<>
|
|
void ScriptVM::executeCommand<true, true>(Listener *listener, op_parmNum_t iParamCount, op_evName_t eventnum)
|
|
{
|
|
ScriptCommandEvent ev = iParamCount ? ScriptCommandEvent(eventnum, iParamCount) : ScriptCommandEvent(eventnum);
|
|
return executeCommandInternal<true>(ev, listener, m_VMStack.GetTopArray(), iParamCount);
|
|
}
|
|
|
|
void ScriptVM::transferVarsToEvent(Event& ev, ScriptVariable *fromVar, op_parmNum_t count)
|
|
{
|
|
ev.CopyValues(fromVar, count);
|
|
}
|
|
|
|
bool ScriptVM::executeGetter(Listener *listener, op_evName_t eventName)
|
|
{
|
|
int eventNum = Event::FindGetterEventNum(eventName);
|
|
|
|
if (eventNum && listener->classinfo()->GetDef(eventNum)) {
|
|
ScriptCommandEvent ev(eventNum);
|
|
|
|
listener->ProcessScriptEvent(ev);
|
|
|
|
ScriptVariable& pTop = m_VMStack.GetTop();
|
|
pTop = std::move(ev.GetValue());
|
|
|
|
return true;
|
|
} else {
|
|
eventNum = Event::FindSetterEventNum(eventName);
|
|
assert(!eventNum || !listener->classinfo()->GetDef(eventNum));
|
|
if (eventNum && listener->classinfo()->GetDef(eventNum)) {
|
|
ScriptError("Cannot set a read-only variable");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScriptVM::executeSetter(Listener *listener, op_evName_t eventName)
|
|
{
|
|
int eventNum = Event::FindSetterEventNum(eventName);
|
|
|
|
if (eventNum && listener->classinfo()->GetDef(eventNum)) {
|
|
ScriptCommandEvent ev(eventNum, 1);
|
|
|
|
ScriptVariable& pTop = m_VMStack.GetTop();
|
|
ev.CopyValues(&pTop, 1);
|
|
|
|
listener->ProcessScriptEvent(ev);
|
|
|
|
return true;
|
|
} else {
|
|
eventNum = Event::FindSetterEventNum(eventName);
|
|
assert(!eventNum || !listener->classinfo()->GetDef(eventNum));
|
|
if (eventNum && listener->classinfo()->GetDef(eventNum)) {
|
|
ScriptError("Cannot get a write-only variable");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ScriptVM::execCmdCommon(op_parmNum_t param)
|
|
{
|
|
const op_ev_t eventNum = fetchOpcodeValue<op_ev_t>();
|
|
|
|
m_VMStack.Pop(param);
|
|
|
|
executeCommand(m_Thread, param, eventNum);
|
|
}
|
|
|
|
void ScriptVM::execCmdMethodCommon(op_parmNum_t param)
|
|
{
|
|
const ScriptVariable& a = m_VMStack.Pop();
|
|
const op_ev_t eventNum = fetchOpcodeValue<op_ev_t>();
|
|
|
|
m_VMStack.Pop(param);
|
|
|
|
const size_t arraysize = a.arraysize();
|
|
if (arraysize == (size_t)-1) {
|
|
throw ScriptException("command '%s' applied to NIL", Event::GetEventName(eventNum));
|
|
}
|
|
|
|
if (arraysize > 1) {
|
|
if (a.IsConstArray()) {
|
|
// copy the variable
|
|
// because if it's a targetlist, the container object can be modified
|
|
// while iterating
|
|
ScriptVariable array = a;
|
|
array.CastConstArrayValue();
|
|
|
|
for (uintptr_t i = arraysize; i > 0; i--) {
|
|
Listener *const listener = array.listenerAt(i);
|
|
|
|
// if the listener is NULL, don't throw an exception
|
|
// it would be unfair if the other listeners executed the command
|
|
if (listener) {
|
|
executeCommand<true>(listener, param, eventNum);
|
|
}
|
|
}
|
|
} else {
|
|
ScriptVariable array = a;
|
|
// must cast into a const array value
|
|
array.CastConstArrayValue();
|
|
|
|
for (uintptr_t i = array.arraysize(); i > 0; i--) {
|
|
Listener *const listener = array.listenerAt(i);
|
|
if (listener) {
|
|
executeCommand<true>(listener, param, eventNum);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// avoid useless allocations of const array
|
|
Listener *const listener = a.listenerValue();
|
|
if (!listener) {
|
|
throw ScriptException("command '%s' applied to NULL listener", Event::GetEventName(eventNum));
|
|
}
|
|
|
|
executeCommand<true>(listener, param, eventNum);
|
|
}
|
|
}
|
|
|
|
void ScriptVM::execMethodCommon(op_parmNum_t param)
|
|
{
|
|
const ScriptVariable& a = m_VMStack.Pop();
|
|
const op_ev_t eventNum = fetchOpcodeValue<op_ev_t>();
|
|
|
|
m_VMStack.Pop(param);
|
|
// push the return value
|
|
m_VMStack.Push();
|
|
|
|
Listener *const listener = a.listenerValue();
|
|
if (!listener) {
|
|
m_VMStack.GetTop().Clear();
|
|
throw ScriptException("command '%s' applied to NULL listener", Event::GetEventName(eventNum));
|
|
}
|
|
|
|
executeCommand<true, true>(listener, param, eventNum);
|
|
}
|
|
|
|
void ScriptVM::execFunction(ScriptMaster& Director)
|
|
{
|
|
if (!fetchOpcodeValue<bool>()) {
|
|
const op_name_t label = fetchOpcodeValue<op_name_t>();
|
|
const op_parmNum_t params = fetchOpcodeValue<op_parmNum_t>();
|
|
|
|
Listener *listener;
|
|
|
|
try {
|
|
listener = m_VMStack.GetTop().listenerValue();
|
|
|
|
if (!listener) {
|
|
const str& labelName = Director.GetString(label);
|
|
throw ScriptException("function '" + labelName + "' applied to NULL listener");
|
|
}
|
|
} catch (...) {
|
|
m_VMStack.Pop(params);
|
|
throw;
|
|
}
|
|
|
|
m_VMStack.Pop();
|
|
|
|
Container<ScriptVariable> data;
|
|
data.Resize(params + 1);
|
|
|
|
ScriptVariable *labelVar = &data.ObjectAt(data.AddObject(ScriptVariable()));
|
|
labelVar->setConstStringValue(label);
|
|
|
|
const ScriptVariable *var = &m_VMStack.Pop(params);
|
|
|
|
for (int i = 0; i < params; var++, i++) {
|
|
data.AddObject(*var);
|
|
}
|
|
|
|
m_VMStack.Push();
|
|
EnterFunction(std::move(data));
|
|
|
|
GetScriptClass()->m_Self = listener;
|
|
} else {
|
|
const op_name_t filename = fetchOpcodeValue<op_name_t>();
|
|
const op_name_t label = fetchOpcodeValue<op_name_t>();
|
|
const op_parmNum_t params = fetchOpcodeValue<op_parmNum_t>();
|
|
|
|
Listener *listener;
|
|
try {
|
|
listener = m_VMStack.GetTop().listenerValue();
|
|
|
|
if (!listener) {
|
|
const str& labelStr = Director.GetString(label);
|
|
const str& fileStr = Director.GetString(filename);
|
|
throw ScriptException("function '" + labelStr + "' in '" + fileStr + "' applied to NULL listener");
|
|
}
|
|
} catch (...) {
|
|
m_VMStack.Pop(params);
|
|
throw;
|
|
}
|
|
|
|
m_VMStack.Pop();
|
|
|
|
ScriptVariable constarray;
|
|
ScriptVariable *pVar = new ScriptVariable[2];
|
|
|
|
pVar[0].setConstStringValue(filename);
|
|
pVar[1].setConstStringValue(label);
|
|
|
|
constarray.setConstArrayValue(pVar, 2);
|
|
|
|
delete[] pVar;
|
|
|
|
Event ev(EV_Listener_WaitCreateReturnThread);
|
|
|
|
const ScriptVariable *var = &m_VMStack.Pop(params);
|
|
|
|
for (int i = 0; i < params; var++, i++) {
|
|
ev.AddValue(*var);
|
|
}
|
|
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop() = listener->ProcessEventReturn(&ev);
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ProgBuffer
|
|
|
|
Returns the current program buffer
|
|
====================
|
|
*/
|
|
unsigned char *ScriptVM::ProgBuffer(void)
|
|
{
|
|
return m_CodePos;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ScriptTrace1
|
|
====================
|
|
*/
|
|
void ScriptVM::ScriptTrace1() const
|
|
{
|
|
GameScript *scr;
|
|
str sourceLine;
|
|
int column;
|
|
int line;
|
|
|
|
scr = GetScript();
|
|
|
|
if (!scr->GetSourceAt(m_CodePos, &sourceLine, column, line)) {
|
|
return;
|
|
}
|
|
|
|
if (currentScriptFile == this && line == currentScriptLine) {
|
|
return;
|
|
}
|
|
|
|
currentScriptFile = this;
|
|
currentScriptLine = line;
|
|
|
|
gi.DPrintf2("%s (%s, %d, %p, %d)\n", sourceLine.c_str(), Filename().c_str(), line, this, m_VMStack.GetIndex());
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ScriptTrace2
|
|
====================
|
|
*/
|
|
void ScriptVM::ScriptTrace2() const
|
|
{
|
|
gi.DPrintf2("%s, %p, %d\n", OpcodeName(*m_CodePos), this, m_VMStack.GetIndex());
|
|
GetScript()->PrintSourcePos(m_CodePos, true);
|
|
}
|
|
|
|
/*
|
|
====================
|
|
GetSourcePos
|
|
====================
|
|
*/
|
|
const char *ScriptVM::GetSourcePos() const
|
|
{
|
|
static str debugLine;
|
|
str sourceLine;
|
|
int column;
|
|
int line;
|
|
|
|
debugLine =
|
|
str(OpcodeName(*m_CodePos)) + " @" + str(m_CodePos - GetScript()->m_ProgBuffer) + " in " + Filename() + "\n";
|
|
|
|
if (GetScript()->GetSourceAt(m_CodePos, &sourceLine, column, line)) {
|
|
debugLine += str(" (") + str(line) + str(")\n");
|
|
}
|
|
|
|
return debugLine.c_str();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
EnterFunction
|
|
|
|
Sets a new instruction pointer
|
|
====================
|
|
*/
|
|
void ScriptVM::EnterFunction(Container<ScriptVariable>&&)
|
|
{
|
|
#if 0
|
|
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 = &m_VMStack.GetTop();
|
|
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();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
====================
|
|
LeaveFunction
|
|
|
|
Returns to the previous function
|
|
====================
|
|
*/
|
|
void ScriptVM::LeaveFunction()
|
|
{
|
|
#if 0
|
|
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;
|
|
}
|
|
#else
|
|
delete m_Thread;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
====================
|
|
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;
|
|
|
|
ScriptVariable *a;
|
|
ScriptVariable *b;
|
|
ScriptVariable *c;
|
|
|
|
op_name_t fieldNameIndex;
|
|
|
|
Listener *listener;
|
|
|
|
Event ev;
|
|
ScriptVariable *var = NULL;
|
|
|
|
TargetList *targetList;
|
|
|
|
if (Director.stackCount >= MAX_STACK_DEPTH) {
|
|
state = STATE_EXECUTION;
|
|
|
|
ScriptException::next_abort = -1;
|
|
throw ScriptException("stack overflow");
|
|
}
|
|
|
|
if (label.length()) {
|
|
// Throw if label is not found
|
|
m_CodePos = m_ScriptClass->FindLabel(label);
|
|
if (!m_CodePos) {
|
|
ScriptError("ScriptVM::Execute: label '%s' does not exist in '%s'.", label.c_str(), Filename().c_str());
|
|
}
|
|
}
|
|
|
|
if (g_scripttrace->integer && CanScriptTracePrint()) {
|
|
gi.DPrintf2(
|
|
"+++FRAME: %i (%p) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n",
|
|
Director.stackCount,
|
|
this
|
|
);
|
|
}
|
|
|
|
Director.stackCount++;
|
|
|
|
if (dataSize) {
|
|
SetFastData(data, dataSize);
|
|
}
|
|
|
|
state = STATE_RUNNING;
|
|
|
|
while (state == STATE_RUNNING) {
|
|
if (g_scripttrace->integer && CanScriptTracePrint()) {
|
|
switch (g_scripttrace->integer) {
|
|
case 1:
|
|
case 3:
|
|
ScriptTrace1();
|
|
break;
|
|
case 2:
|
|
case 4:
|
|
ScriptTrace2();
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_PrevCodePos = m_CodePos;
|
|
|
|
try {
|
|
if (!m_VMStack.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;
|
|
}
|
|
*/
|
|
}
|
|
|
|
opcode = m_CodePos++;
|
|
switch (*opcode) {
|
|
case OP_BIN_BITWISE_AND:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b &= *a;
|
|
break;
|
|
|
|
case OP_BIN_BITWISE_OR:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b |= *a;
|
|
break;
|
|
|
|
case OP_BIN_BITWISE_EXCL_OR:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b ^= *a;
|
|
break;
|
|
|
|
case OP_BIN_EQUALITY:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
b->setIntValue(*b == *a);
|
|
break;
|
|
|
|
case OP_BIN_INEQUALITY:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
b->setIntValue(*b != *a);
|
|
break;
|
|
|
|
case OP_BIN_GREATER_THAN:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
b->greaterthan(*a);
|
|
break;
|
|
|
|
case OP_BIN_GREATER_THAN_OR_EQUAL:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
b->greaterthanorequal(*a);
|
|
break;
|
|
|
|
case OP_BIN_LESS_THAN:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
b->lessthan(*a);
|
|
break;
|
|
|
|
case OP_BIN_LESS_THAN_OR_EQUAL:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
b->lessthanorequal(*a);
|
|
break;
|
|
|
|
case OP_BIN_PLUS:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b += *a;
|
|
break;
|
|
|
|
case OP_BIN_MINUS:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b -= *a;
|
|
break;
|
|
|
|
case OP_BIN_MULTIPLY:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b *= *a;
|
|
break;
|
|
|
|
case OP_BIN_DIVIDE:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b /= *a;
|
|
break;
|
|
|
|
case OP_BIN_PERCENTAGE:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b %= *a;
|
|
break;
|
|
|
|
case OP_BIN_SHIFT_LEFT:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b <<= *a;
|
|
break;
|
|
|
|
case OP_BIN_SHIFT_RIGHT:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.GetTop();
|
|
|
|
*b >>= *a;
|
|
break;
|
|
|
|
case OP_BOOL_JUMP_FALSE4:
|
|
doJumpIf(!m_VMStack.Pop().m_data.intValue);
|
|
break;
|
|
|
|
case OP_BOOL_JUMP_TRUE4:
|
|
doJumpIf(m_VMStack.Pop().m_data.intValue);
|
|
break;
|
|
|
|
case OP_VAR_JUMP_FALSE4:
|
|
doJumpIf(!m_VMStack.Pop().booleanValue());
|
|
break;
|
|
|
|
case OP_VAR_JUMP_TRUE4:
|
|
doJumpIf(m_VMStack.Pop().booleanValue());
|
|
break;
|
|
|
|
case OP_BOOL_LOGICAL_AND:
|
|
doJumpVarIf(!m_VMStack.GetTop().m_data.intValue);
|
|
break;
|
|
|
|
case OP_BOOL_LOGICAL_OR:
|
|
doJumpVarIf(m_VMStack.GetTop().m_data.intValue);
|
|
break;
|
|
|
|
case OP_VAR_LOGICAL_AND:
|
|
if (!doJumpVarIf(m_VMStack.GetTop().booleanValue())) {
|
|
m_VMStack.GetTop().SetFalse();
|
|
}
|
|
break;
|
|
|
|
case OP_VAR_LOGICAL_OR:
|
|
if (!doJumpVarIf(!m_VMStack.GetTop().booleanValue())) {
|
|
m_VMStack.GetTop().SetTrue();
|
|
}
|
|
break;
|
|
|
|
case OP_BOOL_STORE_FALSE:
|
|
m_VMStack.PushAndGet().SetFalse();
|
|
break;
|
|
|
|
case OP_BOOL_STORE_TRUE:
|
|
m_VMStack.PushAndGet().SetTrue();
|
|
break;
|
|
|
|
case OP_BOOL_UN_NOT:
|
|
m_VMStack.GetTop().m_data.intValue = (m_VMStack.GetTop().m_data.intValue == 0);
|
|
break;
|
|
|
|
case OP_CALC_VECTOR:
|
|
c = &m_VMStack.Pop();
|
|
b = &m_VMStack.Pop();
|
|
a = &m_VMStack.GetTop();
|
|
|
|
m_VMStack.GetTop().setVectorValue(Vector(a->floatValue(), b->floatValue(), c->floatValue()));
|
|
break;
|
|
|
|
case OP_EXEC_CMD0:
|
|
{
|
|
execCmdCommon(0);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD1:
|
|
{
|
|
execCmdCommon(1);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD2:
|
|
{
|
|
execCmdCommon(2);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD3:
|
|
{
|
|
execCmdCommon(3);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD4:
|
|
{
|
|
execCmdCommon(4);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD5:
|
|
{
|
|
execCmdCommon(5);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD_COUNT1:
|
|
{
|
|
const op_parmNum_t numParms = fetchOpcodeValue<op_parmNum_t>();
|
|
execCmdCommon(numParms);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD_METHOD0:
|
|
{
|
|
execCmdMethodCommon(0);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD_METHOD1:
|
|
{
|
|
execCmdMethodCommon(1);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD_METHOD2:
|
|
{
|
|
execCmdMethodCommon(2);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD_METHOD3:
|
|
{
|
|
execCmdMethodCommon(3);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD_METHOD4:
|
|
{
|
|
execCmdMethodCommon(4);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD_METHOD5:
|
|
{
|
|
execCmdMethodCommon(5);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_CMD_METHOD_COUNT1:
|
|
{
|
|
const op_parmNum_t numParms = fetchOpcodeValue<op_parmNum_t>();
|
|
execCmdMethodCommon(numParms);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_METHOD0:
|
|
{
|
|
execMethodCommon(0);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_METHOD1:
|
|
{
|
|
execMethodCommon(1);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_METHOD2:
|
|
{
|
|
execMethodCommon(2);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_METHOD3:
|
|
{
|
|
execMethodCommon(3);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_METHOD4:
|
|
{
|
|
execMethodCommon(4);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_METHOD5:
|
|
{
|
|
execMethodCommon(5);
|
|
break;
|
|
}
|
|
|
|
case OP_EXEC_METHOD_COUNT1:
|
|
{
|
|
const op_parmNum_t numParms = fetchOpcodeValue<op_parmNum_t>();
|
|
execMethodCommon(numParms);
|
|
break;
|
|
}
|
|
|
|
case OP_FUNC:
|
|
{
|
|
execFunction(Director);
|
|
break;
|
|
}
|
|
|
|
case OP_JUMP4:
|
|
jump(fetchOpcodeValue<unsigned int>());
|
|
break;
|
|
|
|
case OP_JUMP_BACK4:
|
|
jumpBack(fetchActualOpcodeValue<unsigned int>());
|
|
break;
|
|
|
|
case OP_LOAD_ARRAY_VAR:
|
|
a = &m_VMStack.Pop();
|
|
b = &m_VMStack.Pop();
|
|
c = &m_VMStack.Pop();
|
|
|
|
b->setArrayAt(*a, *c);
|
|
break;
|
|
|
|
case OP_LOAD_FIELD_VAR:
|
|
a = &m_VMStack.Pop();
|
|
|
|
try {
|
|
try {
|
|
listener = a->listenerValue();
|
|
|
|
if (listener == NULL) {
|
|
fieldNameIndex = fetchActualOpcodeValue<op_name_t>();
|
|
ScriptError(
|
|
"Field '%s' applied to NULL listener", Director.GetString(fieldNameIndex).c_str()
|
|
);
|
|
}
|
|
} catch (...) {
|
|
skipField();
|
|
throw;
|
|
}
|
|
|
|
loadTop(listener);
|
|
} catch (...) {
|
|
m_VMStack.Pop();
|
|
throw;
|
|
}
|
|
|
|
break;
|
|
|
|
case OP_LOAD_CONST_ARRAY1:
|
|
{
|
|
op_arrayParmNum_t numParms = fetchOpcodeValue<op_arrayParmNum_t>();
|
|
|
|
ScriptVariable& pTop = m_VMStack.PopAndGet(numParms - 1);
|
|
pTop.setConstArrayValue(&pTop, numParms);
|
|
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) {
|
|
m_VMStack.Pop();
|
|
m_CodePos += sizeof(unsigned int);
|
|
ScriptError("self is NULL");
|
|
}
|
|
|
|
if (!m_ScriptClass->m_Self->GetScriptOwner()) {
|
|
m_VMStack.Pop();
|
|
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) {
|
|
m_VMStack.Pop();
|
|
m_CodePos += sizeof(unsigned int);
|
|
ScriptError("self is NULL");
|
|
}
|
|
|
|
loadTop(m_ScriptClass->m_Self);
|
|
break;
|
|
|
|
case OP_LOAD_STORE_GAME_VAR:
|
|
loadStoreTop(&game);
|
|
break;
|
|
|
|
case OP_LOAD_STORE_GROUP_VAR:
|
|
loadStoreTop(m_ScriptClass);
|
|
break;
|
|
|
|
case OP_LOAD_STORE_LEVEL_VAR:
|
|
loadStoreTop(&level);
|
|
break;
|
|
|
|
case OP_LOAD_STORE_LOCAL_VAR:
|
|
loadStoreTop(m_Thread);
|
|
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");
|
|
}
|
|
|
|
loadStoreTop(m_ScriptClass->m_Self->GetScriptOwner());
|
|
break;
|
|
|
|
case OP_LOAD_STORE_PARM_VAR:
|
|
loadStoreTop(&parm);
|
|
break;
|
|
|
|
case OP_LOAD_STORE_SELF_VAR:
|
|
if (!m_ScriptClass->m_Self) {
|
|
ScriptError("self is NULL");
|
|
}
|
|
|
|
loadStoreTop(m_ScriptClass->m_Self);
|
|
break;
|
|
|
|
case OP_MARK_STACK_POS:
|
|
m_StackPos = &m_VMStack.GetTop();
|
|
m_VMStack.m_bMarkStack = true;
|
|
break;
|
|
|
|
case OP_STORE_PARAM:
|
|
if (fastEvent.dataSize) {
|
|
m_VMStack.SetTop(*(fastEvent.data++));
|
|
fastEvent.dataSize--;
|
|
} else {
|
|
m_VMStack.SetTop(*(m_StackPos + 1));
|
|
m_VMStack.GetTop().Clear();
|
|
}
|
|
break;
|
|
|
|
case OP_RESTORE_STACK_POS:
|
|
m_VMStack.SetTop(*m_StackPos);
|
|
m_VMStack.m_bMarkStack = false;
|
|
break;
|
|
|
|
case OP_STORE_ARRAY:
|
|
m_VMStack.Pop();
|
|
m_VMStack.GetTop().evalArrayAt(*(m_VMStack.GetTopPtr() + 1));
|
|
break;
|
|
|
|
case OP_STORE_ARRAY_REF:
|
|
m_VMStack.Pop();
|
|
m_VMStack.GetTop().setArrayRefValue(*(m_VMStack.GetTopPtr() + 1));
|
|
break;
|
|
|
|
case OP_STORE_FIELD_REF:
|
|
try {
|
|
try {
|
|
listener = m_VMStack.GetTop().listenerValue();
|
|
|
|
if (listener == nullptr) {
|
|
fieldNameIndex = fetchActualOpcodeValue<op_name_t>();
|
|
ScriptError(
|
|
"Field '%s' applied to NULL listener", Director.GetString(fieldNameIndex).c_str()
|
|
);
|
|
}
|
|
} catch (...) {
|
|
skipField();
|
|
m_VMStack.GetTop().Clear();
|
|
throw;
|
|
}
|
|
|
|
ScriptVariable *const listenerVar = storeTop<true>(listener);
|
|
|
|
if (listenerVar) {
|
|
// having a listener variable means the variable was just created
|
|
m_VMStack.GetTop().setRefValue(listenerVar);
|
|
}
|
|
break;
|
|
} catch (...) {
|
|
ScriptVariable *const pTop = m_VMStack.GetTopPtr();
|
|
pTop->setRefValue(pTop);
|
|
throw;
|
|
}
|
|
|
|
case OP_STORE_FIELD:
|
|
try {
|
|
listener = m_VMStack.GetTop().listenerValue();
|
|
|
|
if (listener == nullptr) {
|
|
fieldNameIndex = fetchActualOpcodeValue<op_name_t>();
|
|
ScriptError("Field '%s' applied to NULL listener", Director.GetString(fieldNameIndex).c_str());
|
|
}
|
|
} catch (...) {
|
|
skipField();
|
|
m_VMStack.GetTop().Clear();
|
|
throw;
|
|
}
|
|
|
|
storeTop<true>(listener);
|
|
break;
|
|
|
|
case OP_STORE_FLOAT:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setFloatValue(fetchOpcodeValue<float>());
|
|
break;
|
|
|
|
case OP_STORE_INT0:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setIntValue(0);
|
|
break;
|
|
|
|
case OP_STORE_INT1:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setIntValue(fetchOpcodeValue<byte>());
|
|
break;
|
|
|
|
case OP_STORE_INT2:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setIntValue(fetchOpcodeValue<short>());
|
|
break;
|
|
|
|
case OP_STORE_INT3:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setIntValue(fetchOpcodeValue<short3>());
|
|
break;
|
|
|
|
case OP_STORE_INT4:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setIntValue(fetchOpcodeValue<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) {
|
|
m_VMStack.Push();
|
|
m_CodePos += sizeof(unsigned int);
|
|
ScriptError("self is NULL");
|
|
}
|
|
|
|
if (!m_ScriptClass->m_Self->GetScriptOwner()) {
|
|
m_VMStack.Push();
|
|
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) {
|
|
m_VMStack.Push();
|
|
m_CodePos += sizeof(unsigned int);
|
|
ScriptError("self is NULL");
|
|
}
|
|
|
|
storeTop(m_ScriptClass->m_Self);
|
|
break;
|
|
|
|
case OP_STORE_GAME:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setListenerValue(&game);
|
|
break;
|
|
|
|
case OP_STORE_GROUP:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setListenerValue(m_ScriptClass);
|
|
break;
|
|
|
|
case OP_STORE_LEVEL:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setListenerValue(&level);
|
|
break;
|
|
|
|
case OP_STORE_LOCAL:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setListenerValue(m_Thread);
|
|
break;
|
|
|
|
case OP_STORE_OWNER:
|
|
m_VMStack.Push();
|
|
|
|
if (!m_ScriptClass->m_Self) {
|
|
m_VMStack.Push();
|
|
ScriptError("self is NULL");
|
|
}
|
|
|
|
m_VMStack.GetTop().setListenerValue(m_ScriptClass->m_Self->GetScriptOwner());
|
|
break;
|
|
|
|
case OP_STORE_PARM:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setListenerValue(&parm);
|
|
break;
|
|
|
|
case OP_STORE_SELF:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setListenerValue(m_ScriptClass->m_Self);
|
|
break;
|
|
|
|
case OP_STORE_NIL:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().Clear();
|
|
break;
|
|
|
|
case OP_STORE_NULL:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setListenerValue(NULL);
|
|
break;
|
|
|
|
case OP_STORE_STRING:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setConstStringValue(fetchOpcodeValue<unsigned int>());
|
|
break;
|
|
|
|
case OP_STORE_VECTOR:
|
|
m_VMStack.Push();
|
|
m_VMStack.GetTop().setVectorValue(fetchOpcodeValue<Vector>());
|
|
break;
|
|
|
|
case OP_SWITCH:
|
|
if (!Switch(fetchActualOpcodeValue<StateScript *>(), m_VMStack.Pop())) {
|
|
m_CodePos += sizeof(StateScript *);
|
|
}
|
|
break;
|
|
|
|
case OP_UN_CAST_BOOLEAN:
|
|
m_VMStack.GetTop().CastBoolean();
|
|
break;
|
|
|
|
case OP_UN_COMPLEMENT:
|
|
m_VMStack.GetTop().complement();
|
|
break;
|
|
|
|
case OP_UN_MINUS:
|
|
m_VMStack.GetTop().minus();
|
|
break;
|
|
|
|
case OP_UN_DEC:
|
|
m_VMStack.GetTop()--;
|
|
break;
|
|
|
|
case OP_UN_INC:
|
|
m_VMStack.GetTop()++;
|
|
break;
|
|
|
|
case OP_UN_SIZE:
|
|
m_VMStack.GetTop().setIntValue((int)m_VMStack.GetTop().size());
|
|
break;
|
|
|
|
case OP_UN_TARGETNAME:
|
|
// retrieve the target name
|
|
targetList = world->GetExistingTargetList(m_VMStack.GetTop().stringValue());
|
|
|
|
if (!targetList || !targetList->list.NumObjects()) {
|
|
str targetname = m_VMStack.GetTop().stringValue();
|
|
// the target name was not found
|
|
m_VMStack.GetTop().setListenerValue(NULL);
|
|
|
|
if ((*m_PrevCodePos >= OP_BIN_EQUALITY && *m_PrevCodePos <= OP_BIN_GREATER_THAN_OR_EQUAL)
|
|
|| (*m_PrevCodePos >= OP_BOOL_UN_NOT && *m_PrevCodePos <= OP_UN_CAST_BOOLEAN)) {
|
|
ScriptError("Targetname '%s' does not exist.", targetname.c_str());
|
|
}
|
|
} else if (targetList->list.NumObjects() == 1) {
|
|
// single listener
|
|
m_VMStack.GetTop().setListenerValue(targetList->list.ObjectAt(1));
|
|
} else if (targetList->list.NumObjects() > 1) {
|
|
// multiple listeners
|
|
m_VMStack.GetTop().setContainerValue((Container<SafePtr<Listener>> *)&targetList->list);
|
|
}
|
|
break;
|
|
|
|
case OP_VAR_UN_NOT:
|
|
m_VMStack.GetTop().setIntValue(m_VMStack.GetTop().booleanValue());
|
|
break;
|
|
|
|
case OP_DONE:
|
|
End();
|
|
break;
|
|
|
|
case OP_NOP:
|
|
break;
|
|
|
|
default:
|
|
assert(!"Invalid opcode");
|
|
if (*opcode < OP_MAX) {
|
|
gi.DPrintf("unknown opcode %d ('%s')\n", *opcode, OpcodeName(*opcode));
|
|
} else {
|
|
gi.DPrintf("unknown opcode %d\n", *opcode);
|
|
}
|
|
break;
|
|
}
|
|
|
|
Director.cmdCount++;
|
|
|
|
if (Director.cmdCount >= 15000) {
|
|
if (!Director.cmdTime) {
|
|
Director.cmdTime = gi.Milliseconds();
|
|
Director.cmdCount = 0;
|
|
continue;
|
|
}
|
|
|
|
if (gi.Milliseconds() - Director.cmdTime < Director.maxTime) {
|
|
Director.cmdCount = 0;
|
|
continue;
|
|
}
|
|
|
|
// The maximum execution time was reached
|
|
if (level.m_LoopProtection) {
|
|
Director.cmdTime = gi.Milliseconds();
|
|
|
|
GetScript()->PrintSourcePos(m_CodePos, true);
|
|
gi.DPrintf2("\n");
|
|
|
|
state = STATE_EXECUTION;
|
|
|
|
if (level.m_LoopDrop) {
|
|
ScriptException::next_abort = -1;
|
|
}
|
|
|
|
ScriptError("Command overflow. Possible infinite loop in thread.\n");
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
} catch (ScriptException& exc) {
|
|
HandleScriptException(exc);
|
|
}
|
|
}
|
|
|
|
Director.stackCount--;
|
|
|
|
if (g_scripttrace->integer && CanScriptTracePrint()) {
|
|
gi.DPrintf2(
|
|
"---FRAME: %i (%p) -------------------------------------------------------------------\n",
|
|
Director.stackCount,
|
|
this
|
|
);
|
|
}
|
|
|
|
switch (state) {
|
|
case STATE_WAITING:
|
|
delete m_Thread;
|
|
delete this;
|
|
break;
|
|
case STATE_SUSPENDED:
|
|
state = STATE_EXECUTION;
|
|
break;
|
|
case STATE_DESTROYED:
|
|
delete this;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
HandleScriptException
|
|
====================
|
|
*/
|
|
void ScriptVM::HandleScriptException(ScriptException& exc)
|
|
{
|
|
if (m_ScriptClass) {
|
|
m_ScriptClass->GetScript()->PrintSourcePos(m_PrevCodePos, true);
|
|
} else {
|
|
gi.DPrintf("unknown source pos");
|
|
}
|
|
|
|
if (exc.bAbort) {
|
|
ScriptException e(exc.string);
|
|
|
|
e.bAbort = exc.bAbort;
|
|
e.bIsForAnim = exc.bIsForAnim;
|
|
|
|
state = STATE_EXECUTION;
|
|
throw e;
|
|
}
|
|
|
|
gi.DPrintf2("^~^~^ Script Error : %s\n\n", exc.string.c_str());
|
|
|
|
if (m_ScriptClass->GetScript()->ScriptCheck()) {
|
|
if (g_scriptcheck->integer != 2 || !exc.bIsForAnim) {
|
|
ScriptException e("Script check failed");
|
|
|
|
e.bAbort = exc.bAbort;
|
|
e.bIsForAnim = exc.bIsForAnim;
|
|
|
|
state = STATE_EXECUTION;
|
|
throw e;
|
|
}
|
|
}
|
|
Director.cmdCount += 100;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
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] = std::move(data[i]);
|
|
}
|
|
|
|
m_pOldData = fastEvent.data;
|
|
m_OldDataSize = fastEvent.dataSize;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
NotifyDelete
|
|
====================
|
|
*/
|
|
void ScriptVM::NotifyDelete(void)
|
|
{
|
|
if (g_scripttrace->integer && CanScriptTracePrint()) {
|
|
gi.DPrintf2("---THREAD: %p\n", this);
|
|
}
|
|
|
|
switch (state) {
|
|
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;
|
|
|
|
case STATE_DESTROYED:
|
|
ScriptError("Attempting to delete a dead thread.");
|
|
|
|
state = STATE_DESTROYED;
|
|
|
|
if (m_ScriptClass) {
|
|
m_ScriptClass->RemoveThread(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_RUNNING) {
|
|
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(STRING_DEFAULT);
|
|
|
|
if (!pos) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
m_CodePos = pos;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
Filename
|
|
====================
|
|
*/
|
|
str ScriptVM::Filename(void) const
|
|
{
|
|
return m_ScriptClass->Filename();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
Label
|
|
====================
|
|
*/
|
|
str ScriptVM::Label(void) const
|
|
{
|
|
const_str label = m_ScriptClass->NearestLabel(m_CodePos);
|
|
|
|
if (!label) {
|
|
return "";
|
|
}
|
|
|
|
return Director.GetString(label);
|
|
}
|
|
|
|
/*
|
|
====================
|
|
GetScriptClass
|
|
====================
|
|
*/
|
|
ScriptClass *ScriptVM::GetScriptClass(void) const
|
|
{
|
|
return m_ScriptClass;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
GetScript
|
|
====================
|
|
*/
|
|
GameScript *ScriptVM::GetScript() const
|
|
{
|
|
return m_ScriptClass->GetScript();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
IsSuspended
|
|
====================
|
|
*/
|
|
bool ScriptVM::IsSuspended(void)
|
|
{
|
|
return state == STATE_EXECUTION;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
State
|
|
====================
|
|
*/
|
|
int ScriptVM::State(void)
|
|
{
|
|
return state;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ThreadState
|
|
====================
|
|
*/
|
|
int ScriptVM::ThreadState(void)
|
|
{
|
|
return m_ThreadState;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
EventGoto
|
|
====================
|
|
*/
|
|
void ScriptVM::EventGoto(Event *ev)
|
|
{
|
|
unsigned char *codePos;
|
|
const ScriptVariable& value = ev->GetValue(1);
|
|
if (value.type == VARIABLE_CONSTSTRING) {
|
|
const_str label = value.constStringValue();
|
|
codePos = m_ScriptClass->FindLabel(label);
|
|
|
|
if (!codePos) {
|
|
ScriptError(
|
|
"ScriptVM::EventGoto: label '%s' does not exist in '%s'.",
|
|
value.stringValue().c_str(),
|
|
Filename().c_str()
|
|
);
|
|
}
|
|
|
|
} else {
|
|
str label = value.stringValue();
|
|
codePos = m_ScriptClass->FindLabel(label);
|
|
|
|
if (!codePos) {
|
|
ScriptError("ScriptVM::EventGoto: label '%s' does not exist in '%s'.", label.c_str(), Filename().c_str());
|
|
}
|
|
}
|
|
|
|
SetFastData(ev->data + 1, ev->dataSize - 1);
|
|
|
|
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;
|
|
}
|
|
|
|
bool ScriptVM::CanScriptTracePrint(void)
|
|
{
|
|
if (g_scripttrace->integer < 1 || g_scripttrace->integer > 4) {
|
|
return false;
|
|
}
|
|
if (g_scripttrace->integer <= 2) {
|
|
return true;
|
|
}
|
|
if (!m_ScriptClass) {
|
|
return false;
|
|
}
|
|
if (!*g_monitor->string || !m_ScriptClass->m_Self || !m_ScriptClass->m_Self->isInheritedBy(&SimpleEntity::ClassInfo)
|
|
|| ((SimpleEntity *)m_ScriptClass->m_Self.Pointer())->targetname != g_monitor->string) {
|
|
if (g_monitorNum->integer >= 0) {
|
|
if (m_ScriptClass->m_Self && m_ScriptClass->m_Self->isInheritedBy(&Entity::ClassInfo)
|
|
&& ((Entity *)m_ScriptClass->m_Self.Pointer())->entnum == g_monitorNum->integer) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ScriptVM::fetchOpcodeValue(void *outValue, size_t size)
|
|
{
|
|
Com_Memcpy(outValue, m_CodePos, size);
|
|
m_CodePos += size;
|
|
}
|
|
|
|
void ScriptVM::fetchActualOpcodeValue(void *outValue, size_t size)
|
|
{
|
|
Com_Memcpy(outValue, m_CodePos, size);
|
|
}
|