/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. Copyright (C) 2012 Robert Beckebans This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition 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 3 of the License, or (at your option) any later version. Doom 3 BFG Edition 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 Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #pragma hdrstop #include "precompiled.h" #include "../Game_local.h" // simple types. function types are dynamically allocated idTypeDef type_void( ev_void, &def_void, "void", 0, NULL ); // RB: 64 bit fixes, changed all pointer types to intptr_t idTypeDef type_scriptevent( ev_scriptevent, &def_scriptevent, "scriptevent", sizeof( intptr_t ), NULL ); idTypeDef type_namespace( ev_namespace, &def_namespace, "namespace", sizeof( intptr_t ), NULL ); idTypeDef type_string( ev_string, &def_string, "string", MAX_STRING_LEN, NULL ); idTypeDef type_float( ev_float, &def_float, "float", sizeof( intptr_t ), NULL ); idTypeDef type_vector( ev_vector, &def_vector, "vector", E_EVENT_SIZEOF_VEC, NULL ); idTypeDef type_entity( ev_entity, &def_entity, "entity", sizeof( intptr_t ), NULL ); // stored as entity number pointer idTypeDef type_field( ev_field, &def_field, "field", sizeof( intptr_t ), NULL ); idTypeDef type_function( ev_function, &def_function, "function", sizeof( intptr_t ), &type_void ); idTypeDef type_virtualfunction( ev_virtualfunction, &def_virtualfunction, "virtual function", sizeof( intptr_t ), NULL ); idTypeDef type_pointer( ev_pointer, &def_pointer, "pointer", sizeof( intptr_t ), NULL ); idTypeDef type_object( ev_object, &def_object, "object", sizeof( intptr_t ), NULL ); // stored as entity number pointer idTypeDef type_jumpoffset( ev_jumpoffset, &def_jumpoffset, "", sizeof( intptr_t ), NULL ); // only used for jump opcodes idTypeDef type_argsize( ev_argsize, &def_argsize, "", sizeof( intptr_t ), NULL ); // only used for function call and thread opcodes idTypeDef type_boolean( ev_boolean, &def_boolean, "boolean", sizeof( intptr_t ), NULL ); // RB end idVarDef def_void( &type_void ); idVarDef def_scriptevent( &type_scriptevent ); idVarDef def_namespace( &type_namespace ); idVarDef def_string( &type_string ); idVarDef def_float( &type_float ); idVarDef def_vector( &type_vector ); idVarDef def_entity( &type_entity ); idVarDef def_field( &type_field ); idVarDef def_function( &type_function ); idVarDef def_virtualfunction( &type_virtualfunction ); idVarDef def_pointer( &type_pointer ); idVarDef def_object( &type_object ); idVarDef def_jumpoffset( &type_jumpoffset ); // only used for jump opcodes idVarDef def_argsize( &type_argsize ); idVarDef def_boolean( &type_boolean ); /*********************************************************************** function_t ***********************************************************************/ /* ================ function_t::function_t ================ */ function_t::function_t() { Clear(); } /* ================ function_t::Allocated ================ */ size_t function_t::Allocated() const { return name.Allocated() + parmSize.Allocated(); } /* ================ function_t::SetName ================ */ void function_t::SetName( const char* name ) { this->name = name; } /* ================ function_t::Name ================ */ const char* function_t::Name() const { return name; } /* ================ function_t::Clear ================ */ void function_t::Clear() { eventdef = NULL; def = NULL; type = NULL; firstStatement = 0; numStatements = 0; parmTotal = 0; locals = 0; filenum = 0; name.Clear(); parmSize.Clear(); } /*********************************************************************** idTypeDef ***********************************************************************/ /* ================ idTypeDef::idTypeDef ================ */ idTypeDef::idTypeDef( etype_t etype, idVarDef* edef, const char* ename, int esize, idTypeDef* aux ) { name = ename; type = etype; def = edef; size = esize; auxType = aux; parmTypes.SetGranularity( 1 ); parmNames.SetGranularity( 1 ); functions.SetGranularity( 1 ); } /* ================ idTypeDef::idTypeDef ================ */ idTypeDef::idTypeDef( const idTypeDef& other ) { *this = other; } /* ================ idTypeDef::operator= ================ */ void idTypeDef::operator=( const idTypeDef& other ) { type = other.type; def = other.def; name = other.name; size = other.size; auxType = other.auxType; parmTypes = other.parmTypes; parmNames = other.parmNames; functions = other.functions; } /* ================ idTypeDef::Allocated ================ */ size_t idTypeDef::Allocated() const { size_t memsize; int i; memsize = name.Allocated() + parmTypes.Allocated() + parmNames.Allocated() + functions.Allocated(); for( i = 0; i < parmTypes.Num(); i++ ) { memsize += parmNames[ i ].Allocated(); } return memsize; } /* ================ idTypeDef::Inherits Returns true if basetype is an ancestor of this type. ================ */ bool idTypeDef::Inherits( const idTypeDef* basetype ) const { idTypeDef* superType; if( type != ev_object ) { return false; } if( this == basetype ) { return true; } for( superType = auxType; superType != NULL; superType = superType->auxType ) { if( superType == basetype ) { return true; } } return false; } /* ================ idTypeDef::MatchesType Returns true if both types' base types and parameters match ================ */ bool idTypeDef::MatchesType( const idTypeDef& matchtype ) const { int i; if( this == &matchtype ) { return true; } if( ( type != matchtype.type ) || ( auxType != matchtype.auxType ) ) { return false; } if( parmTypes.Num() != matchtype.parmTypes.Num() ) { return false; } for( i = 0; i < matchtype.parmTypes.Num(); i++ ) { if( parmTypes[ i ] != matchtype.parmTypes[ i ] ) { return false; } } return true; } /* ================ idTypeDef::MatchesVirtualFunction Returns true if both functions' base types and parameters match ================ */ bool idTypeDef::MatchesVirtualFunction( const idTypeDef& matchfunc ) const { int i; if( this == &matchfunc ) { return true; } if( ( type != matchfunc.type ) || ( auxType != matchfunc.auxType ) ) { return false; } if( parmTypes.Num() != matchfunc.parmTypes.Num() ) { return false; } if( parmTypes.Num() > 0 ) { if( !parmTypes[ 0 ]->Inherits( matchfunc.parmTypes[ 0 ] ) ) { return false; } } for( i = 1; i < matchfunc.parmTypes.Num(); i++ ) { if( parmTypes[ i ] != matchfunc.parmTypes[ i ] ) { return false; } } return true; } /* ================ idTypeDef::AddFunctionParm Adds a new parameter for a function type. ================ */ void idTypeDef::AddFunctionParm( idTypeDef* parmtype, const char* name ) { if( type != ev_function ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::AddFunctionParm : tried to add parameter on non-function type" ); #else gameLocal.Error( "idTypeDef::AddFunctionParm : tried to add parameter on non-function type" ); #endif } parmTypes.Append( parmtype ); idStr& parmName = parmNames.Alloc(); parmName = name; } /* ================ idTypeDef::AddField Adds a new field to an object type. ================ */ void idTypeDef::AddField( idTypeDef* fieldtype, const char* name ) { if( type != ev_object ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::AddField : tried to add field to non-object type" ); #else gameLocal.Error( "idTypeDef::AddField : tried to add field to non-object type" ); #endif } parmTypes.Append( fieldtype ); idStr& parmName = parmNames.Alloc(); parmName = name; if( fieldtype->FieldType()->Inherits( &type_object ) ) { size += type_object.Size(); } else { size += fieldtype->FieldType()->Size(); } } /* ================ idTypeDef::SetName ================ */ void idTypeDef::SetName( const char* newname ) { name = newname; } /* ================ idTypeDef::Name ================ */ const char* idTypeDef::Name() const { return name; } /* ================ idTypeDef::Type ================ */ etype_t idTypeDef::Type() const { return type; } /* ================ idTypeDef::Size ================ */ int idTypeDef::Size() const { return size; } /* ================ idTypeDef::SuperClass If type is an object, then returns the object's superclass ================ */ idTypeDef* idTypeDef::SuperClass() const { if( type != ev_object ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::SuperClass : tried to get superclass of a non-object type" ); #else gameLocal.Error( "idTypeDef::SuperClass : tried to get superclass of a non-object type" ); #endif } return auxType; } /* ================ idTypeDef::ReturnType If type is a function, then returns the function's return type ================ */ idTypeDef* idTypeDef::ReturnType() const { if( type != ev_function ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::ReturnType: tried to get return type on non-function type" ); #else gameLocal.Error( "idTypeDef::ReturnType: tried to get return type on non-function type" ); #endif } return auxType; } /* ================ idTypeDef::SetReturnType If type is a function, then sets the function's return type ================ */ void idTypeDef::SetReturnType( idTypeDef* returntype ) { if( type != ev_function ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::SetReturnType: tried to set return type on non-function type" ); #else gameLocal.Error( "idTypeDef::SetReturnType: tried to set return type on non-function type" ); #endif } auxType = returntype; } /* ================ idTypeDef::FieldType If type is a field, then returns it's type ================ */ idTypeDef* idTypeDef::FieldType() const { if( type != ev_field ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::FieldType: tried to get field type on non-field type" ); #else gameLocal.Error( "idTypeDef::FieldType: tried to get field type on non-field type" ); #endif } return auxType; } /* ================ idTypeDef::SetFieldType If type is a field, then sets the function's return type ================ */ void idTypeDef::SetFieldType( idTypeDef* fieldtype ) { if( type != ev_field ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::SetFieldType: tried to set return type on non-function type" ); #else gameLocal.Error( "idTypeDef::SetFieldType: tried to set return type on non-function type" ); #endif } auxType = fieldtype; } /* ================ idTypeDef::PointerType If type is a pointer, then returns the type it points to ================ */ idTypeDef* idTypeDef::PointerType() const { if( type != ev_pointer ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::PointerType: tried to get pointer type on non-pointer" ); #else gameLocal.Error( "idTypeDef::PointerType: tried to get pointer type on non-pointer" ); #endif } return auxType; } /* ================ idTypeDef::SetPointerType If type is a pointer, then sets the pointer's type ================ */ void idTypeDef::SetPointerType( idTypeDef* pointertype ) { if( type != ev_pointer ) { #if defined(USE_EXCEPTIONS) throw idCompileError( "idTypeDef::SetPointerType: tried to set type on non-pointer" ); #else gameLocal.Error( "idTypeDef::SetPointerType: tried to set type on non-pointer" ); #endif } auxType = pointertype; } /* ================ idTypeDef::NumParameters ================ */ int idTypeDef::NumParameters() const { return parmTypes.Num(); } /* ================ idTypeDef::GetParmType ================ */ idTypeDef* idTypeDef::GetParmType( int parmNumber ) const { assert( parmNumber >= 0 ); assert( parmNumber < parmTypes.Num() ); return parmTypes[ parmNumber ]; } /* ================ idTypeDef::GetParmName ================ */ const char* idTypeDef::GetParmName( int parmNumber ) const { assert( parmNumber >= 0 ); assert( parmNumber < parmTypes.Num() ); return parmNames[ parmNumber ]; } /* ================ idTypeDef::NumFunctions ================ */ int idTypeDef::NumFunctions() const { return functions.Num(); } /* ================ idTypeDef::GetFunctionNumber ================ */ int idTypeDef::GetFunctionNumber( const function_t* func ) const { int i; for( i = 0; i < functions.Num(); i++ ) { if( functions[ i ] == func ) { return i; } } return -1; } /* ================ idTypeDef::GetFunction ================ */ const function_t* idTypeDef::GetFunction( int funcNumber ) const { assert( funcNumber >= 0 ); assert( funcNumber < functions.Num() ); return functions[ funcNumber ]; } /* ================ idTypeDef::AddFunction ================ */ void idTypeDef::AddFunction( const function_t* func ) { int i; for( i = 0; i < functions.Num(); i++ ) { if( !strcmp( functions[ i ]->def->Name(), func->def->Name() ) ) { if( func->def->TypeDef()->MatchesVirtualFunction( *functions[ i ]->def->TypeDef() ) ) { functions[ i ] = func; return; } } } functions.Append( func ); } /*********************************************************************** idVarDef ***********************************************************************/ /* ================ idVarDef::idVarDef() ================ */ idVarDef::idVarDef( idTypeDef* typeptr ) { typeDef = typeptr; num = 0; scope = NULL; numUsers = 0; initialized = idVarDef::uninitialized; memset( &value, 0, sizeof( value ) ); name = NULL; next = NULL; } /* ============ idVarDef::~idVarDef ============ */ idVarDef::~idVarDef() { if( name ) { name->RemoveDef( this ); } } /* ============ idVarDef::Name ============ */ const char* idVarDef::Name() const { return name->Name(); } /* ============ idVarDef::GlobalName ============ */ const char* idVarDef::GlobalName() const { if( scope != &def_namespace ) { return va( "%s::%s", scope->GlobalName(), name->Name() ); } else { return name->Name(); } } /* ============ idVarDef::DepthOfScope ============ */ int idVarDef::DepthOfScope( const idVarDef* otherScope ) const { const idVarDef* def; int depth; depth = 1; for( def = otherScope; def != NULL; def = def->scope ) { if( def == scope ) { return depth; } depth++; } return 0; } /* ============ idVarDef::SetFunction ============ */ void idVarDef::SetFunction( function_t* func ) { assert( typeDef ); initialized = initializedConstant; assert( typeDef->Type() == ev_function ); value.functionPtr = func; } /* ============ idVarDef::SetObject ============ */ void idVarDef::SetObject( idScriptObject* object ) { assert( typeDef ); initialized = initialized; assert( typeDef->Inherits( &type_object ) ); *value.objectPtrPtr = object; } /* ============ idVarDef::SetValue ============ */ void idVarDef::SetValue( const eval_t& _value, bool constant ) { assert( typeDef ); if( constant ) { initialized = initializedConstant; } else { initialized = initializedVariable; } switch( typeDef->Type() ) { case ev_pointer : case ev_boolean : case ev_field : *value.intPtr = _value._int; break; case ev_jumpoffset : value.jumpOffset = _value._int; break; case ev_argsize : value.argSize = _value._int; break; case ev_entity : *value.entityNumberPtr = _value.entity; break; case ev_string : idStr::Copynz( value.stringPtr, _value.stringPtr, MAX_STRING_LEN ); break; case ev_float : *value.floatPtr = _value._float; break; case ev_vector : value.vectorPtr->x = _value.vector[ 0 ]; value.vectorPtr->y = _value.vector[ 1 ]; value.vectorPtr->z = _value.vector[ 2 ]; break; case ev_function : value.functionPtr = _value.function; break; case ev_virtualfunction : value.virtualFunction = _value._int; break; case ev_object : *value.entityNumberPtr = _value.entity; break; default : #if defined(USE_EXCEPTIONS) throw idCompileError( va( "weird type on '%s'", Name() ) ); #else gameLocal.Error( "weird type on '%s'", Name() ); #endif break; } } /* ============ idVarDef::SetString ============ */ void idVarDef::SetString( const char* string, bool constant ) { if( constant ) { initialized = initializedConstant; } else { initialized = initializedVariable; } assert( typeDef && ( typeDef->Type() == ev_string ) ); idStr::Copynz( value.stringPtr, string, MAX_STRING_LEN ); } /* ============ idVarDef::PrintInfo ============ */ void idVarDef::PrintInfo( idFile* file, int instructionPointer ) const { statement_t* jumpst; int jumpto; etype_t etype; int i; int len; const char* ch; if( initialized == initializedConstant ) { file->Printf( "const " ); } etype = typeDef->Type(); switch( etype ) { case ev_jumpoffset : jumpto = instructionPointer + value.jumpOffset; jumpst = &gameLocal.program.GetStatement( jumpto ); file->Printf( "address %d [%s(%d)]", jumpto, gameLocal.program.GetFilename( jumpst->file ), jumpst->linenumber ); break; case ev_function : if( value.functionPtr->eventdef ) { file->Printf( "event %s", GlobalName() ); } else { file->Printf( "function %s", GlobalName() ); } break; case ev_field : file->Printf( "field %d", value.ptrOffset ); break; case ev_argsize: file->Printf( "args %d", value.argSize ); break; default: file->Printf( "%s ", typeDef->Name() ); if( initialized == initializedConstant ) { switch( etype ) { case ev_string : file->Printf( "\"" ); len = strlen( value.stringPtr ); ch = value.stringPtr; for( i = 0; i < len; i++, ch++ ) { if( idStr::CharIsPrintable( *ch ) ) { file->Printf( "%c", *ch ); } else if( *ch == '\n' ) { file->Printf( "\\n" ); } else { file->Printf( "\\x%.2x", static_cast( *ch ) ); } } file->Printf( "\"" ); break; case ev_vector : file->Printf( "'%s'", value.vectorPtr->ToString() ); break; case ev_float : file->Printf( "%f", *value.floatPtr ); break; case ev_virtualfunction : file->Printf( "vtable[ %d ]", value.virtualFunction ); break; default : file->Printf( "%d", *value.intPtr ); break; } } else if( initialized == stackVariable ) { file->Printf( "stack[%d]", value.stackOffset ); } else { file->Printf( "global[%d]", num ); } break; } } /*********************************************************************** idVarDef ***********************************************************************/ /* ============ idVarDefName::AddDef ============ */ void idVarDefName::AddDef( idVarDef* def ) { assert( def->next == NULL ); def->name = this; def->next = defs; defs = def; } /* ============ idVarDefName::RemoveDef ============ */ void idVarDefName::RemoveDef( idVarDef* def ) { if( defs == def ) { defs = def->next; } else { for( idVarDef* d = defs; d->next != NULL; d = d->next ) { if( d->next == def ) { d->next = def->next; break; } } } def->next = NULL; def->name = NULL; } /*********************************************************************** idScriptObject ***********************************************************************/ /* ============ idScriptObject::idScriptObject ============ */ idScriptObject::idScriptObject() { data = NULL; type = &type_object; } /* ============ idScriptObject::~idScriptObject ============ */ idScriptObject::~idScriptObject() { Free(); } /* ============ idScriptObject::Free ============ */ void idScriptObject::Free() { if( data ) { Mem_Free( data ); } data = NULL; type = &type_object; } /* ================ idScriptObject::Save ================ */ void idScriptObject::Save( idSaveGame* savefile ) const { // RB: 64 bit fix, changed size_t to int int size; // RB end if( type == &type_object && data == NULL ) { // Write empty string for uninitialized object savefile->WriteString( "" ); } else { savefile->WriteString( type->Name() ); size = type->Size(); savefile->WriteInt( size ); savefile->Write( data, size ); } } /* ================ idScriptObject::Restore ================ */ void idScriptObject::Restore( idRestoreGame* savefile ) { idStr typeName; // RB: 64 bit fix, changed size_t to int int size; // RB end savefile->ReadString( typeName ); // Empty string signals uninitialized object if( typeName.Length() == 0 ) { return; } if( !SetType( typeName ) ) { savefile->Error( "idScriptObject::Restore: failed to restore object of type '%s'.", typeName.c_str() ); } // RB: 64 bit fix, changed size_t to int savefile->ReadInt( size ); // RB end if( size != type->Size() ) { savefile->Error( "idScriptObject::Restore: size of object '%s' doesn't match size in save game.", typeName.c_str() ); } savefile->Read( data, size ); } /* ============ idScriptObject::SetType Allocates an object and initializes memory. ============ */ bool idScriptObject::SetType( const char* typeName ) { size_t size; idTypeDef* newtype; // lookup the type newtype = gameLocal.program.FindType( typeName ); // only allocate memory if the object type changes if( newtype != type ) { Free(); if( !newtype ) { gameLocal.Warning( "idScriptObject::SetType: Unknown type '%s'", typeName ); return false; } if( !newtype->Inherits( &type_object ) ) { gameLocal.Warning( "idScriptObject::SetType: Can't create object of type '%s'. Must be an object type.", newtype->Name() ); return false; } // set the type type = newtype; // allocate the memory size = type->Size(); data = ( byte* )Mem_Alloc( size, TAG_SCRIPT ); } // init object memory ClearObject(); return true; } /* ============ idScriptObject::ClearObject Resets the memory for the script object without changing its type. ============ */ void idScriptObject::ClearObject() { size_t size; if( type != &type_object ) { // init object memory size = type->Size(); memset( data, 0, size ); } } /* ============ idScriptObject::HasObject ============ */ bool idScriptObject::HasObject() const { return ( type != &type_object ); } /* ============ idScriptObject::GetTypeDef ============ */ idTypeDef* idScriptObject::GetTypeDef() const { return type; } /* ============ idScriptObject::GetTypeName ============ */ const char* idScriptObject::GetTypeName() const { return type->Name(); } /* ============ idScriptObject::GetConstructor ============ */ const function_t* idScriptObject::GetConstructor() const { const function_t* func; func = GetFunction( "init" ); return func; } /* ============ idScriptObject::GetDestructor ============ */ const function_t* idScriptObject::GetDestructor() const { const function_t* func; func = GetFunction( "destroy" ); return func; } /* ============ idScriptObject::GetFunction ============ */ const function_t* idScriptObject::GetFunction( const char* name ) const { const function_t* func; if( type == &type_object ) { return NULL; } func = gameLocal.program.FindFunction( name, type ); return func; } /* ============ idScriptObject::GetVariable ============ */ byte* idScriptObject::GetVariable( const char* name, etype_t etype ) const { int i; int pos; const idTypeDef* t = type; const idTypeDef* parm; if( t == &type_object || t == NULL ) { return NULL; } do { if( t->SuperClass() != &type_object ) { pos = t->SuperClass()->Size(); } else { pos = 0; } for( i = 0; i < t->NumParameters(); i++ ) { parm = t->GetParmType( i ); if( !strcmp( t->GetParmName( i ), name ) ) { if( etype != parm->FieldType()->Type() ) { return NULL; } return &data[ pos ]; } if( parm->FieldType()->Inherits( &type_object ) ) { pos += type_object.Size(); } else { pos += parm->FieldType()->Size(); } } t = t->SuperClass(); } while( t != NULL && ( t != &type_object ) ); return NULL; } /*********************************************************************** idProgram ***********************************************************************/ /* ============ idProgram::AllocType ============ */ idTypeDef* idProgram::AllocType( idTypeDef& type ) { idTypeDef* newtype = new( TAG_SCRIPT ) idTypeDef( type ); typesHash.Add( idStr::Hash( type.Name() ), types.Append( newtype ) ); return newtype; } /* ============ idProgram::AllocType ============ */ idTypeDef* idProgram::AllocType( etype_t etype, idVarDef* edef, const char* ename, int esize, idTypeDef* aux ) { idTypeDef* newtype = new( TAG_SCRIPT ) idTypeDef( etype, edef, ename, esize, aux ); typesHash.Add( idStr::Hash( ename ), types.Append( newtype ) ); return newtype; } /* ============ idProgram::GetType Returns a preexisting complex type that matches the parm, or allocates a new one and copies it out. ============ */ idTypeDef* idProgram::GetType( idTypeDef& type, bool allocate ) { for( int i = typesHash.First( idStr::Hash( type.Name() ) ); i != -1; i = typesHash.Next( i ) ) { if( types[ i ]->MatchesType( type ) && !strcmp( types[ i ]->Name(), type.Name() ) ) { return types[ i ]; } } if( !allocate ) { return NULL; } // allocate a new one return AllocType( type ); } /* ============ idProgram::FindType Returns a preexisting complex type that matches the name, or returns NULL if not found ============ */ idTypeDef* idProgram::FindType( const char* name ) { for( int i = typesHash.First( idStr::Hash( name ) ); i != -1; i = typesHash.Next( i ) ) { idTypeDef* check = types[ i ]; if( !strcmp( check->Name(), name ) ) { return check; } } return NULL; } /* ============ idProgram::GetDefList ============ */ idVarDef* idProgram::GetDefList( const char* name ) const { int i, hash; hash = varDefNameHash.GenerateKey( name, true ); for( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { if( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { return varDefNames[i]->GetDefs(); } } return NULL; } /* ============ idProgram::AddDefToNameList ============ */ void idProgram::AddDefToNameList( idVarDef* def, const char* name ) { int i, hash; hash = varDefNameHash.GenerateKey( name, true ); for( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { if( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { break; } } if( i == -1 ) { i = varDefNames.Append( new( TAG_SCRIPT ) idVarDefName( name ) ); varDefNameHash.Add( hash, i ); } varDefNames[i]->AddDef( def ); } // RB: moved from AllocDef byte* idProgram::ReserveDefMemory( int size ) { byte* mem = &variables[ numVariables ]; numVariables += size; if( numVariables > sizeof( variables ) ) { #if defined(USE_EXCEPTIONS) throw idCompileError( va( "Exceeded global memory size (%zd bytes)", sizeof( variables ) ) ); #else gameLocal.Error( "Exceeded global memory size (%zd bytes)", sizeof( variables ) ); #endif } memset( mem, 0, size ); return mem; } // RB end // RB: moved from AllocDef idVarDef* idProgram::AllocVarDef( idTypeDef* type, const char* name, idVarDef* scope ) { // allocate a new def idVarDef* def = new( TAG_SCRIPT ) idVarDef( type ); def->scope = scope; def->numUsers = 1; def->num = varDefs.Append( def ); // add the def to the list with defs with this name and set the name pointer AddDefToNameList( def, name ); return def; } // RB end /* ============ idProgram::AllocDef ============ */ idVarDef* idProgram::AllocDef( idTypeDef* type, const char* name, idVarDef* scope, bool constant ) { idVarDef* def; idStr element; idVarDef* def_x; idVarDef* def_y; idVarDef* def_z; // allocate a new def def = AllocVarDef( type, name, scope ); if( ( type->Type() == ev_vector ) || ( ( type->Type() == ev_field ) && ( type->FieldType()->Type() == ev_vector ) ) ) { // // vector // if( !strcmp( name, RESULT_STRING ) ) { // vector defs don't need the _x, _y and _z components assert( scope->Type() == ev_function ); def->value.stackOffset = scope->value.functionPtr->locals; def->initialized = idVarDef::stackVariable; scope->value.functionPtr->locals += type->Size(); } else if( scope->TypeDef()->Inherits( &type_object ) ) { idTypeDef newtype( ev_field, NULL, "float field", 0, &type_float ); // RB: changed local type to ftype idTypeDef* ftype = GetType( newtype, true ); // set the value to the variable's position in the object def->value.ptrOffset = scope->TypeDef()->Size(); // make automatic defs for the vectors elements // origin can be accessed as origin_x, origin_y, and origin_z sprintf( element, "%s_x", def->Name() ); def_x = AllocDef( ftype, element, scope, constant ); sprintf( element, "%s_y", def->Name() ); def_y = AllocDef( ftype, element, scope, constant ); def_y->value.ptrOffset = def_x->value.ptrOffset + sizeof( float ); sprintf( element, "%s_z", def->Name() ); def_z = AllocDef( ftype, element, scope, constant ); def_z->value.ptrOffset = def_y->value.ptrOffset + sizeof( float ); // RB end } else { // RB: from dhewm3 idTypeDef newtype( ev_float, &def_float, "vector float", 0, NULL ); idTypeDef* ftype = GetType( newtype, true ); // make automatic defs for the vectors elements // origin can be accessed as origin_x, origin_y, and origin_z sprintf( element, "%s_x", def->Name() ); def_x = AllocVarDef( ftype, element, scope ); sprintf( element, "%s_y", def->Name() ); def_y = AllocVarDef( ftype, element, scope ); sprintf( element, "%s_z", def->Name() ); def_z = AllocVarDef( ftype, element, scope ); // get the memory for the full vector and point the _x, _y and _z // defs at the vector member offsets if( scope->Type() == ev_function ) { // vector on stack def->value.stackOffset = scope->value.functionPtr->locals; def->initialized = idVarDef::stackVariable; scope->value.functionPtr->locals += type->Size(); def_x->value.stackOffset = def->value.stackOffset; def_y->value.stackOffset = def_x->value.stackOffset + sizeof( float ); def_z->value.stackOffset = def_y->value.stackOffset + sizeof( float ); } else { // global vector def->value.bytePtr = ReserveDefMemory( type->Size() ); def_x->value.bytePtr = def->value.bytePtr; def_y->value.bytePtr = def_x->value.bytePtr + sizeof( float ); def_z->value.bytePtr = def_y->value.bytePtr + sizeof( float ); } def_x->initialized = def->initialized; def_y->initialized = def->initialized; def_z->initialized = def->initialized; // RB end } } else if( scope->TypeDef()->Inherits( &type_object ) ) { // // object variable // // set the value to the variable's position in the object def->value.ptrOffset = scope->TypeDef()->Size(); } else if( scope->Type() == ev_function ) { // // stack variable // // since we don't know how many local variables there are, // we have to have them go backwards on the stack def->value.stackOffset = scope->value.functionPtr->locals; def->initialized = idVarDef::stackVariable; if( type->Inherits( &type_object ) ) { // objects only have their entity number on the stack, not the entire object scope->value.functionPtr->locals += type_object.Size(); } else { scope->value.functionPtr->locals += type->Size(); } } else { // // global variable // // RB begin def->value.bytePtr = ReserveDefMemory( def->TypeDef()->Size() ); // RB end //memset( def->value.bytePtr, 0, def->TypeDef()->Size() ); } return def; } /* ============ idProgram::GetDef If type is NULL, it will match any type ============ */ idVarDef* idProgram::GetDef( const idTypeDef* type, const char* name, const idVarDef* scope ) const { idVarDef* def; idVarDef* bestDef; int bestDepth; int depth; bestDepth = 0; bestDef = NULL; for( def = GetDefList( name ); def != NULL; def = def->Next() ) { if( def->scope->Type() == ev_namespace ) { depth = def->DepthOfScope( scope ); if( !depth ) { // not in the same namespace continue; } } else if( def->scope != scope ) { // in a different function continue; } else { depth = 1; } if( !bestDef || ( depth < bestDepth ) ) { bestDepth = depth; bestDef = def; } } // see if the name is already in use for another type if( bestDef && type && ( bestDef->TypeDef() != type ) ) { #if defined(USE_EXCEPTIONS) throw idCompileError( va( "Type mismatch on redeclaration of %s", name ) ); #else gameLocal.Error( "Type mismatch on redeclaration of %s", name ); #endif } return bestDef; } /* ============ idProgram::FreeDef ============ */ void idProgram::FreeDef( idVarDef* def, const idVarDef* scope ) { idVarDef* e; int i; if( def->Type() == ev_vector ) { idStr name; sprintf( name, "%s_x", def->Name() ); e = GetDef( NULL, name, scope ); if( e ) { FreeDef( e, scope ); } sprintf( name, "%s_y", def->Name() ); e = GetDef( NULL, name, scope ); if( e ) { FreeDef( e, scope ); } sprintf( name, "%s_z", def->Name() ); e = GetDef( NULL, name, scope ); if( e ) { FreeDef( e, scope ); } } varDefs.RemoveIndex( def->num ); for( i = def->num; i < varDefs.Num(); i++ ) { varDefs[ i ]->num = i; } delete def; } /* ============ idProgram::FindFreeResultDef ============ */ idVarDef* idProgram::FindFreeResultDef( idTypeDef* type, const char* name, idVarDef* scope, const idVarDef* a, const idVarDef* b ) { idVarDef* def; for( def = GetDefList( name ); def != NULL; def = def->Next() ) { if( def == a || def == b ) { continue; } if( def->TypeDef() != type ) { continue; } if( def->scope != scope ) { continue; } if( def->numUsers <= 1 ) { continue; } return def; } return AllocDef( type, name, scope, false ); } /* ================ idProgram::FindFunction Searches for the specified function in the currently loaded script. A full namespace should be specified if not in the global namespace. Returns 0 if function not found. Returns >0 if function found. ================ */ function_t* idProgram::FindFunction( const char* name ) const { int start; int pos; idVarDef* namespaceDef; idVarDef* def; assert( name ); idStr fullname = name; start = 0; namespaceDef = &def_namespace; do { pos = fullname.Find( "::", true, start ); if( pos < 0 ) { break; } idStr namespaceName = fullname.Mid( start, pos - start ); def = GetDef( NULL, namespaceName, namespaceDef ); if( !def ) { // couldn't find namespace return NULL; } namespaceDef = def; // skip past the :: start = pos + 2; } while( def->Type() == ev_namespace ); idStr funcName = fullname.Right( fullname.Length() - start ); def = GetDef( NULL, funcName, namespaceDef ); if( !def ) { // couldn't find function return NULL; } if( ( def->Type() == ev_function ) && ( def->value.functionPtr->eventdef == NULL ) ) { return def->value.functionPtr; } // is not a function, or is an eventdef return NULL; } /* ================ idProgram::FindFunction Searches for the specified object function in the currently loaded script. Returns 0 if function not found. Returns >0 if function found. ================ */ function_t* idProgram::FindFunction( const char* name, const idTypeDef* type ) const { const idVarDef* tdef; const idVarDef* def; // look for the function def = NULL; for( tdef = type->def; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { def = GetDef( NULL, name, tdef ); if( def ) { return def->value.functionPtr; } } return NULL; } /* ================ idProgram::AllocFunction ================ */ function_t& idProgram::AllocFunction( idVarDef* def ) { if( functions.Num() >= functions.Max() ) { #if defined(USE_EXCEPTIONS) throw idCompileError( va( "Exceeded maximum allowed number of functions (%d)", functions.Max() ) ); #else gameLocal.Error( "Exceeded maximum allowed number of functions (%d)", functions.Max() ); #endif } // fill in the dfunction function_t& func = *functions.Alloc(); func.eventdef = NULL; func.def = def; func.type = def->TypeDef(); func.firstStatement = 0; func.numStatements = 0; func.parmTotal = 0; func.locals = 0; func.filenum = filenum; func.parmSize.SetGranularity( 1 ); func.SetName( def->GlobalName() ); def->SetFunction( &func ); return func; } /* ================ idProgram::SetEntity ================ */ void idProgram::SetEntity( const char* name, idEntity* ent ) { idVarDef* def; idStr defName( "$" ); defName += name; def = GetDef( &type_entity, defName, &def_namespace ); if( def != NULL && ( def->initialized != idVarDef::stackVariable ) ) { // 0 is reserved for NULL entity if( !ent ) { *def->value.entityNumberPtr = 0; } else { *def->value.entityNumberPtr = ent->entityNumber + 1; } } } /* ================ idProgram::AllocStatement ================ */ statement_t* idProgram::AllocStatement() { if( statements.Num() >= statements.Max() ) { #if defined(USE_EXCEPTIONS) throw idCompileError( va( "Exceeded maximum allowed number of statements (%d)", statements.Max() ) ); #else gameLocal.Error( "Exceeded maximum allowed number of statements (%d)", statements.Max() ); #endif } return statements.Alloc(); } /* ============== idProgram::BeginCompilation called before compiling a batch of files, clears the pr struct ============== */ void idProgram::BeginCompilation() { statement_t* statement; FreeData(); #if defined(USE_EXCEPTIONS) try #endif { // make the first statement a return for a "NULL" function statement = AllocStatement(); statement->linenumber = 0; statement->file = 0; statement->op = OP_RETURN; statement->a = NULL; statement->b = NULL; statement->c = NULL; // define NULL //AllocDef( &type_void, "", &def_namespace, true ); // define the return def returnDef = AllocDef( &type_vector, "", &def_namespace, false ); // define the return def for strings returnStringDef = AllocDef( &type_string, "", &def_namespace, false ); // define the sys object sysDef = AllocDef( &type_void, "sys", &def_namespace, true ); } #if defined(USE_EXCEPTIONS) catch( idCompileError& err ) { gameLocal.Error( "%s", err.GetError() ); } #endif } /* ============== idProgram::DisassembleStatement ============== */ void idProgram::DisassembleStatement( idFile* file, int instructionPointer ) const { // RB: added const const opcode_t* op; // RB end const statement_t* statement; statement = &statements[ instructionPointer ]; op = &idCompiler::opcodes[ statement->op ]; file->Printf( "%20s(%d):\t%6d: %15s\t", fileList[ statement->file ].c_str(), statement->linenumber, instructionPointer, op->opname ); if( statement->a ) { file->Printf( "\ta: " ); statement->a->PrintInfo( file, instructionPointer ); } if( statement->b ) { file->Printf( "\tb: " ); statement->b->PrintInfo( file, instructionPointer ); } if( statement->c ) { file->Printf( "\tc: " ); statement->c->PrintInfo( file, instructionPointer ); } file->Printf( "\n" ); } /* ============== idProgram::Disassemble ============== */ void idProgram::Disassemble() const { int i; int instructionPointer; const function_t* func; idFile* file; file = fileSystem->OpenFileByMode( "script/disasm.txt", FS_WRITE ); for( i = 0; i < functions.Num(); i++ ) { func = &functions[ i ]; if( func->eventdef ) { // skip eventdefs continue; } file->Printf( "\nfunction %s() %d stack used, %d parms, %d locals {\n", func->Name(), func->locals, func->parmTotal, func->locals - func->parmTotal ); for( instructionPointer = 0; instructionPointer < func->numStatements; instructionPointer++ ) { DisassembleStatement( file, func->firstStatement + instructionPointer ); } file->Printf( "}\n" ); } fileSystem->CloseFile( file ); } /* ============== idProgram::FinishCompilation Called after all files are compiled to check for errors ============== */ void idProgram::FinishCompilation() { int i; top_functions = functions.Num(); top_statements = statements.Num(); top_types = types.Num(); top_defs = varDefs.Num(); top_files = fileList.Num(); variableDefaults.Clear(); variableDefaults.SetNum( numVariables ); for( i = 0; i < numVariables; i++ ) { variableDefaults[ i ] = variables[ i ]; } } /* ============== idProgram::CompileStats called after all files are compiled to report memory usage. ============== */ void idProgram::CompileStats() { int memused; int memallocated; int numdefs; int stringspace; int funcMem; int i; gameLocal.Printf( "---------- Compile stats ----------\n" ); gameLocal.DPrintf( "Files loaded:\n" ); stringspace = 0; for( i = 0; i < fileList.Num(); i++ ) { gameLocal.DPrintf( " %s\n", fileList[ i ].c_str() ); stringspace += fileList[ i ].Allocated(); } stringspace += fileList.Size(); numdefs = varDefs.Num(); memused = varDefs.Num() * sizeof( idVarDef ); memused += types.Num() * sizeof( idTypeDef ); memused += stringspace; for( i = 0; i < types.Num(); i++ ) { memused += types[ i ]->Allocated(); } funcMem = functions.MemoryUsed(); for( i = 0; i < functions.Num(); i++ ) { funcMem += functions[ i ].Allocated(); } memallocated = funcMem + memused + sizeof( idProgram ); memused += statements.MemoryUsed(); memused += functions.MemoryUsed(); // name and filename of functions are shared, so no need to include them memused += sizeof( variables ); gameLocal.Printf( "\nMemory usage:\n" ); gameLocal.Printf( " Strings: %d, %d bytes\n", fileList.Num(), stringspace ); gameLocal.Printf( " Statements: %d, %d bytes\n", statements.Num(), statements.MemoryUsed() ); gameLocal.Printf( " Functions: %d, %d bytes\n", functions.Num(), funcMem ); gameLocal.Printf( " Variables: %d bytes\n", numVariables ); gameLocal.Printf( " Mem used: %d bytes\n", memused ); gameLocal.Printf( " Static data: %d bytes\n", sizeof( idProgram ) ); gameLocal.Printf( " Allocated: %d bytes\n", memallocated ); gameLocal.Printf( " Thread size: %d bytes\n\n", sizeof( idThread ) ); } /* ================ idProgram::CompileText ================ */ bool idProgram::CompileText( const char* source, const char* text, bool console ) { idCompiler compiler; int i; idVarDef* def; idStr ospath; // use a full os path for GetFilenum since it calls OSPathToRelativePath to convert filenames from the parser ospath = fileSystem->RelativePathToOSPath( source ); filenum = GetFilenum( ospath ); #if defined(USE_EXCEPTIONS) try #endif { compiler.CompileFile( text, filename, console ); // check to make sure all functions prototyped have code for( i = 0; i < varDefs.Num(); i++ ) { def = varDefs[ i ]; if( ( def->Type() == ev_function ) && ( ( def->scope->Type() == ev_namespace ) || def->scope->TypeDef()->Inherits( &type_object ) ) ) { if( !def->value.functionPtr->eventdef && !def->value.functionPtr->firstStatement ) { #if defined(USE_EXCEPTIONS) throw idCompileError( va( "function %s was not defined\n", def->GlobalName() ) ); #else gameLocal.Error( "function %s was not defined\n", def->GlobalName() ); #endif } } } } #if defined(USE_EXCEPTIONS) catch( idCompileError& err ) { if( console ) { gameLocal.Printf( "%s\n", err.GetError() ); return false; } else { gameLocal.Error( "%s\n", err.GetError() ); } }; #endif if( !console ) { CompileStats(); } return true; } /* ================ idProgram::CompileFunction ================ */ const function_t* idProgram::CompileFunction( const char* functionName, const char* text ) { bool result; result = CompileText( functionName, text, false ); if( g_disasm.GetBool() ) { Disassemble(); } if( !result ) { gameLocal.Error( "Compile failed." ); } return FindFunction( functionName ); } /* ================ idProgram::CompileFile ================ */ void idProgram::CompileFile( const char* filename ) { char* src; bool result; if( fileSystem->ReadFile( filename, ( void** )&src, NULL ) < 0 ) { gameLocal.Error( "Couldn't load %s\n", filename ); } result = CompileText( filename, src, false ); fileSystem->FreeFile( src ); if( g_disasm.GetBool() ) { Disassemble(); } if( !result ) { gameLocal.Error( "Compile failed in file %s.", filename ); } } /* ================ idProgram::FreeData ================ */ void idProgram::FreeData() { int i; // free the defs varDefs.DeleteContents( true ); varDefNames.DeleteContents( true ); varDefNameHash.Free(); returnDef = NULL; returnStringDef = NULL; sysDef = NULL; // free any special types we've created types.DeleteContents( true ); typesHash.Free(); filenum = 0; numVariables = 0; memset( variables, 0, sizeof( variables ) ); // clear all the strings in the functions so that it doesn't look like we're leaking memory. for( i = 0; i < functions.Num(); i++ ) { functions[ i ].Clear(); } filename.Clear(); fileList.Clear(); statements.Clear(); functions.Clear(); top_functions = 0; top_statements = 0; top_types = 0; top_defs = 0; top_files = 0; filename = ""; } /* ================ idProgram::Startup ================ */ void idProgram::Startup( const char* defaultScript ) { gameLocal.Printf( "Initializing scripts\n" ); // make sure all data is freed up idThread::Restart(); // get ready for loading scripts BeginCompilation(); // load the default script if( defaultScript && *defaultScript ) { CompileFile( defaultScript ); } FinishCompilation(); } /* ================ idProgram::Save ================ */ void idProgram::Save( idSaveGame* savefile ) const { int i; int currentFileNum = top_files; savefile->WriteInt( ( fileList.Num() - currentFileNum ) ); while( currentFileNum < fileList.Num() ) { savefile->WriteString( fileList[ currentFileNum ] ); currentFileNum++; } for( i = 0; i < variableDefaults.Num(); i++ ) { if( variables[i] != variableDefaults[i] ) { savefile->WriteInt( i ); savefile->WriteByte( variables[i] ); } } // Mark the end of the diff with default variables with -1 savefile->WriteInt( -1 ); savefile->WriteInt( numVariables ); for( i = variableDefaults.Num(); i < numVariables; i++ ) { savefile->WriteByte( variables[i] ); } int checksum = CalculateChecksum(); savefile->WriteInt( checksum ); } /* ================ idProgram::Restore ================ */ bool idProgram::Restore( idRestoreGame* savefile ) { int i, num, index; bool result = true; idStr scriptname; savefile->ReadInt( num ); for( i = 0; i < num; i++ ) { savefile->ReadString( scriptname ); CompileFile( scriptname ); } savefile->ReadInt( index ); while( index >= 0 ) { savefile->ReadByte( variables[index] ); savefile->ReadInt( index ); } savefile->ReadInt( num ); for( i = variableDefaults.Num(); i < num; i++ ) { savefile->ReadByte( variables[i] ); } int saved_checksum, checksum; savefile->ReadInt( saved_checksum ); checksum = CalculateChecksum(); if( saved_checksum != checksum ) { result = false; } return result; } /* ================ idProgram::CalculateChecksum ================ */ int idProgram::CalculateChecksum() const { int i, result; typedef struct { unsigned short op; int a; int b; int c; unsigned short linenumber; unsigned short file; } statementBlock_t; statementBlock_t* statementList = new( TAG_SCRIPT ) statementBlock_t[ statements.Num() ]; memset( statementList, 0, ( sizeof( statementBlock_t ) * statements.Num() ) ); // Copy info into new list, using the variable numbers instead of a pointer to the variable for( i = 0; i < statements.Num(); i++ ) { statementList[i].op = statements[i].op; if( statements[i].a ) { statementList[i].a = statements[i].a->num; } else { statementList[i].a = -1; } if( statements[i].b ) { statementList[i].b = statements[i].b->num; } else { statementList[i].b = -1; } if( statements[i].c ) { statementList[i].c = statements[i].c->num; } else { statementList[i].c = -1; } statementList[i].linenumber = statements[i].linenumber; statementList[i].file = statements[i].file; } result = MD4_BlockChecksum( statementList, ( sizeof( statementBlock_t ) * statements.Num() ) ); delete [] statementList; return result; } /* ============== idProgram::Restart Restores all variables to their initial value ============== */ void idProgram::Restart() { int i; idThread::Restart(); // // since there may have been a script loaded by the map or the user may // have typed "script" from the console, free up any types and vardefs that // have been allocated after the initial startup // for( i = top_types; i < types.Num(); i++ ) { delete types[ i ]; } types.SetNum( top_types ); typesHash.Free(); for( i = 0; i < types.Num(); i++ ) { typesHash.Add( idStr::Hash( types[i]->Name() ), i ); } for( i = top_defs; i < varDefs.Num(); i++ ) { delete varDefs[ i ]; } varDefs.SetNum( top_defs ); for( i = top_functions; i < functions.Num(); i++ ) { functions[ i ].Clear(); } functions.SetNum( top_functions ); statements.SetNum( top_statements ); fileList.SetNum( top_files ); filename.Clear(); // reset the variables to their default values numVariables = variableDefaults.Num(); for( i = 0; i < numVariables; i++ ) { variables[ i ] = variableDefaults[ i ]; } } /* ================ idProgram::GetFilenum ================ */ int idProgram::GetFilenum( const char* name ) { if( filename == name ) { return filenum; } idStr strippedName; strippedName = fileSystem->OSPathToRelativePath( name ); if( !strippedName.Length() ) { // not off the base path so just use the full path filenum = fileList.AddUnique( name ); } else { filenum = fileList.AddUnique( strippedName ); } // save the unstripped name so that we don't have to strip the incoming name every time we call GetFilenum filename = name; return filenum; } /* ================ idProgram::idProgram ================ */ idProgram::idProgram() { varDefs.SetGranularity( 256 ); varDefNames.SetGranularity( 256 ); FreeData(); } /* ================ idProgram::~idProgram ================ */ idProgram::~idProgram() { FreeData(); } /* ================ idProgram::ReturnEntity ================ */ void idProgram::ReturnEntity( idEntity* ent ) { if( ent ) { *returnDef->value.entityNumberPtr = ent->entityNumber + 1; } else { *returnDef->value.entityNumberPtr = 0; } }