diff --git a/docs/rh-log.txt b/docs/rh-log.txt index e719c3193..3764a67d4 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,8 @@ October 24, 2009 +- Removed the Actor uservar array and replaced it with user-defined variables. + A_SetUserVar/SetUserVariable/GetUserVariable now take a variable name + instead of an array index. A_SetUserArray/SetUserArray/GetUserArray + have been added to access elements in user-defined arrays. - Rewrote wide sky texture scaling again. This time, it should work for any size textures at any scale. I also tried doing sky scrolling on the sky cylinder, but that didn't look so good, so I left it in screen space. diff --git a/src/actor.h b/src/actor.h index 2c3b80108..730b9006f 100644 --- a/src/actor.h +++ b/src/actor.h @@ -789,7 +789,6 @@ public: int tid; // thing identifier int special; // special int args[5]; // special arguments - int uservar[10]; // user variables, accessible by DECORATE and ACS AActor *inext, **iprev;// Links to other mobjs in same bucket TObjPtr goal; // Monster's goal if not chasing anything diff --git a/src/dobject.cpp b/src/dobject.cpp index d71f87ed7..0ccdd130b 100644 --- a/src/dobject.cpp +++ b/src/dobject.cpp @@ -506,6 +506,88 @@ size_t DObject::StaticPointerSubstitution (DObject *old, DObject *notOld) return changed; } +void DObject::SerializeUserVars(FArchive &arc) +{ + PSymbolTable *symt; + FName varname; + DWORD count, j; + int *varloc; + + if (SaveVersion < 1933) + { + return; + } + + symt = &GetClass()->Symbols; + + if (arc.IsStoring()) + { + // Write all user variables. + for (; symt != NULL; symt = symt->ParentSymbolTable) + { + for (unsigned i = 0; i < symt->Symbols.Size(); ++i) + { + PSymbol *sym = symt->Symbols[i]; + if (sym->SymbolType == SYM_Variable) + { + PSymbolVariable *var = static_cast(sym); + if (var->bUserVar) + { + count = var->ValueType.Type == VAL_Array ? var->ValueType.size : 1; + varloc = (int *)(reinterpret_cast(this) + var->offset); + + arc << var->SymbolName; + arc.WriteCount(count); + for (j = 0; j < count; ++j) + { + arc << varloc[j]; + } + } + } + } + } + // Write terminator. + varname = NAME_None; + arc << varname; + } + else + { + // Read user variables until 'None' is encountered. + arc << varname; + while (varname != NAME_None) + { + PSymbol *sym = symt->FindSymbol(varname, true); + DWORD wanted = 0; + + if (sym != NULL && sym->SymbolType == SYM_Variable) + { + PSymbolVariable *var = static_cast(sym); + + if (var->bUserVar) + { + wanted = var->ValueType.Type == VAL_Array ? var->ValueType.size : 1; + varloc = (int *)(reinterpret_cast(this) + var->offset); + } + } + count = arc.ReadCount(); + for (j = 0; j < MIN(wanted, count); ++j) + { + arc << varloc[j]; + } + if (wanted < count) + { + // Ignore remaining values from archive. + for (; j < count; ++j) + { + int foo; + arc << foo; + } + } + arc << varname; + } + } +} + void DObject::Serialize (FArchive &arc) { ObjectFlags |= OF_SerialSuccess; diff --git a/src/dobject.h b/src/dobject.h index 39ccbc576..a2354aa80 100644 --- a/src/dobject.h +++ b/src/dobject.h @@ -456,6 +456,7 @@ public: inline bool IsKindOf (const PClass *base) const; inline bool IsA (const PClass *type) const; + void SerializeUserVars(FArchive &arc); virtual void Serialize (FArchive &arc); // For catching Serialize functions in derived classes diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index 03eecc7a9..2243023d0 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -118,7 +118,7 @@ void PClass::StaticFreeData (PClass *type) { if (type->Defaults != NULL) { - delete[] type->Defaults; + M_Free(type->Defaults); type->Defaults = NULL; } type->FreeStateList (); @@ -281,7 +281,7 @@ PClass *PClass::CreateDerivedClass (FName name, unsigned int size) type->Meta = Meta; // Set up default instance of the new class. - type->Defaults = new BYTE[size]; + type->Defaults = (BYTE *)M_Malloc(size); memcpy (type->Defaults, Defaults, Size); if (size > Size) { @@ -315,6 +315,19 @@ PClass *PClass::CreateDerivedClass (FName name, unsigned int size) return type; } +// Add bytes to the end of this class. Returns the +// previous size of the class. +unsigned int PClass::Extend(unsigned int extension) +{ + assert(this->bRuntimeClass); + + unsigned int oldsize = Size; + Size += extension; + Defaults = (BYTE *)M_Realloc(Defaults, Size); + memset(Defaults + oldsize, 0, extension); + return oldsize; +} + // Like FindClass but creates a placeholder if no class // is found. CreateDerivedClass will automatcally fill in // the placeholder when the actual class is defined. diff --git a/src/dobjtype.h b/src/dobjtype.h index 88defaefe..dd35e7e47 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -46,8 +46,9 @@ struct PSymbolConst : public PSymbol struct PSymbolVariable : public PSymbol { FExpressionType ValueType; - int size; + //int size; intptr_t offset; + bool bUserVar; PSymbolVariable(FName name) : PSymbol(name, SYM_Variable) {} }; @@ -113,6 +114,8 @@ public: private: PSymbolTable *ParentSymbolTable; TArray Symbols; + + friend class DObject; }; // Meta-info for every class derived from DObject --------------------------- @@ -143,6 +146,7 @@ struct PClass void InsertIntoHash (); DObject *CreateNew () const; PClass *CreateDerivedClass (FName name, unsigned int size); + unsigned int Extend(unsigned int extension); void InitializeActorInfo (); void BuildFlatPointers (); void FreeStateList(); diff --git a/src/farchive.cpp b/src/farchive.cpp index f8992b81c..9497bc50e 100644 --- a/src/farchive.cpp +++ b/src/farchive.cpp @@ -3,7 +3,7 @@ ** Implements an archiver for DObject serialization. ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2009 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -1074,6 +1074,7 @@ FArchive &FArchive::WriteObject (DObject *obj) WriteClass (type); // Printf ("Make class %s (%u)\n", type->Name, m_File->Tell()); MapObject (obj); + obj->SerializeUserVars (*this); obj->Serialize (*this); obj->CheckIfSerialized (); } @@ -1105,6 +1106,7 @@ FArchive &FArchive::WriteObject (DObject *obj) WriteCount (m_TypeMap[type->ClassIndex].toArchive); // Printf ("Reuse class %s (%u)\n", type->Name, m_File->Tell()); MapObject (obj); + obj->SerializeUserVars (*this); obj->Serialize (*this); obj->CheckIfSerialized (); } @@ -1160,6 +1162,7 @@ FArchive &FArchive::ReadObject (DObject* &obj, PClass *wanttype) // stored in the archive. AActor *tempobj = static_cast(type->CreateNew ()); MapObject (obj != NULL ? obj : tempobj); + tempobj->SerializeUserVars (*this); tempobj->Serialize (*this); tempobj->CheckIfSerialized (); // If this player is not present anymore, keep the new body @@ -1187,6 +1190,7 @@ FArchive &FArchive::ReadObject (DObject* &obj, PClass *wanttype) // Printf ("New class: %s (%u)\n", type->Name, m_File->Tell()); obj = type->CreateNew (); MapObject (obj); + obj->SerializeUserVars (*this); obj->Serialize (*this); obj->CheckIfSerialized (); break; @@ -1201,6 +1205,7 @@ FArchive &FArchive::ReadObject (DObject* &obj, PClass *wanttype) AActor *tempobj = static_cast(type->CreateNew ()); MapObject (obj != NULL ? obj : tempobj); + tempobj->SerializeUserVars (*this); tempobj->Serialize (*this); tempobj->CheckIfSerialized (); if (obj != NULL) @@ -1225,6 +1230,7 @@ FArchive &FArchive::ReadObject (DObject* &obj, PClass *wanttype) // Printf ("Use class: %s (%u)\n", type->Name, m_File->Tell()); obj = type->CreateNew (); MapObject (obj); + obj->SerializeUserVars (*this); obj->Serialize (*this); obj->CheckIfSerialized (); break; diff --git a/src/p_acs.cpp b/src/p_acs.cpp index f7a5ef070..a0d2811ad 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -2908,6 +2908,8 @@ enum EACSFunctions ACSF_GetUserVariable, ACSF_Radius_Quake2, ACSF_CheckActorClass, + ACSF_SetUserArray, + ACSF_GetUserArray, }; int DLevelScript::SideFromID(int id, int side) @@ -2942,6 +2944,67 @@ int DLevelScript::LineFromID(int id) } } +static void SetUserVariable(AActor *self, FName varname, int index, int value) +{ + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + int max; + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar) + { + return; + } + if (var->ValueType.Type == VAL_Int) + { + max = 1; + } + else if (var->ValueType.Type == VAL_Array && var->ValueType.BaseType == VAL_Int) + { + max = var->ValueType.size; + } + else + { + return; + } + // Set the value of the specified user variable. + if (index >= 0 && index < max) + { + ((int *)(reinterpret_cast(self) + var->offset))[index] = value; + } +} + +static int GetUserVariable(AActor *self, FName varname, int index) +{ + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + int max; + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar) + { + return 0; + } + if (var->ValueType.Type == VAL_Int) + { + max = 1; + } + else if (var->ValueType.Type == VAL_Array && var->ValueType.BaseType == VAL_Int) + { + max = var->ValueType.size; + } + else + { + return 0; + } + // Get the value of the specified user variable. + if (index >= 0 && index < max) + { + return ((int *)(reinterpret_cast(self) + var->offset))[index]; + } + return 0; +} + int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) { AActor *actor; @@ -3109,23 +3172,24 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) case ACSF_SetUserVariable: { int cnt = 0; - if (args[1] >= 0 && args[1] < 10) + FName varname(FBehavior::StaticLookupString(args[1]), true); + if (varname != NAME_None) { if (args[0] == 0) { if (activator != NULL) { - activator->uservar[args[1]] = args[2]; + SetUserVariable(activator, varname, 0, args[2]); } cnt++; } else { - TActorIterator iterator (args[0]); + TActorIterator iterator(args[0]); - while ( (actor = iterator.Next ()) ) + while ( (actor = iterator.Next()) ) { - actor->uservar[args[1]] = args[2]; + SetUserVariable(actor, varname, 0, args[2]); cnt++; } } @@ -3135,12 +3199,52 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) case ACSF_GetUserVariable: { - if (args[1] >= 0 && args[1] < 10) + FName varname(FBehavior::StaticLookupString(args[1]), true); + if (varname != NAME_None) { AActor *a = args[0] == 0 ? (AActor *)activator : SingleActorFromTID(args[0], NULL); - return a != NULL? a->uservar[args[1]] : 0; + return a != NULL ? GetUserVariable(a, varname, 0) : 0; } - else return 0; + return 0; + } + + case ACSF_SetUserArray: + { + int cnt = 0; + FName varname(FBehavior::StaticLookupString(args[1]), true); + if (varname != NAME_None) + { + if (args[0] == 0) + { + if (activator != NULL) + { + SetUserVariable(activator, varname, args[2], args[3]); + } + cnt++; + } + else + { + TActorIterator iterator(args[0]); + + while ( (actor = iterator.Next()) ) + { + SetUserVariable(actor, varname, args[2], args[3]); + cnt++; + } + } + } + return cnt; + } + + case ACSF_GetUserArray: + { + FName varname(FBehavior::StaticLookupString(args[1]), true); + if (varname != NAME_None) + { + AActor *a = args[0] == 0 ? (AActor *)activator : SingleActorFromTID(args[0], NULL); + return a != NULL ? GetUserVariable(a, varname, args[2]) : 0; + } + return 0; } case ACSF_Radius_Quake2: diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 5a00bcc1f..f03bdbd46 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -314,7 +314,13 @@ void AActor::Serialize (FArchive &arc) arc << DamageFactor; } - for(int i=0; i<10; i++) arc << uservar[i]; + // Skip past uservar array in old savegames + if (SaveVersion < 1933) + { + int foo; + for (int i = 0; i < 10; ++i) + arc << foo; + } if (arc.IsStoring ()) { diff --git a/src/sc_man.h b/src/sc_man.h index cffc07e5d..1e5dc289d 100644 --- a/src/sc_man.h +++ b/src/sc_man.h @@ -175,6 +175,7 @@ enum TK_Exec, TK_DefaultProperties, TK_Native, + TK_Var, TK_Out, TK_Ref, TK_Event, diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 904e9b7d5..ff0945db3 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -109,6 +109,7 @@ std2: 'exec' { RET(TK_Exec); } 'defaultproperties' { RET(TK_DefaultProperties); } 'native' { RET(TK_Native); } + 'var' { RET(TK_Var); } 'out' { RET(TK_Out); } 'ref' { RET(TK_Ref); } 'event' { RET(TK_Event); } diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 481b6e04c..c5410ef6c 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -1742,6 +1742,19 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) Printf("%s\n", text); } +//=========================================================================== +// +// A_LogInt +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(num, 0); + Printf("%d\n", num); +} + //=========================================================================== // // A_SetTranslucent @@ -2900,21 +2913,63 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) //=========================================================================== // -// A_SetVar +// A_SetUserVar // //=========================================================================== DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) { ACTION_PARAM_START(2); - ACTION_PARAM_INT(pos, 0); + ACTION_PARAM_NAME(varname, 0); ACTION_PARAM_INT(value, 1); - if (pos < 0 || pos > 9) + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar || + var->ValueType.Type != VAL_Int) + { + Printf("%s is not a user variable in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); return; - - // Set the value of the specified arg - self->uservar[pos] = value; + } + // Set the value of the specified user variable. + *(int *)(reinterpret_cast(self) + var->offset) = value; +} + +//=========================================================================== +// +// A_SetUserArray +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_NAME(varname, 0); + ACTION_PARAM_INT(pos, 1); + ACTION_PARAM_INT(value, 2); + + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar || + var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int) + { + Printf("%s is not a user array in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + if (pos < 0 || pos >= var->ValueType.size) + { + Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + // Set the value of the specified user array at index pos. + ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; } //=========================================================================== diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index d68ecfcd6..eee2b3608 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -78,7 +78,6 @@ DEFINE_MEMBER_VARIABLE_ALIAS(momy, vely, AActor) DEFINE_MEMBER_VARIABLE_ALIAS(momz, velz, AActor) DEFINE_MEMBER_VARIABLE(Damage, AActor) DEFINE_MEMBER_VARIABLE(Score, AActor) -DEFINE_MEMBER_VARIABLE(uservar, AActor) //========================================================================== // diff --git a/src/thingdef/thingdef_parse.cpp b/src/thingdef/thingdef_parse.cpp index ddc0c8eb9..cc9b3284c 100644 --- a/src/thingdef/thingdef_parse.cpp +++ b/src/thingdef/thingdef_parse.cpp @@ -248,13 +248,13 @@ static void ParseEnum (FScanner &sc, PSymbolTable *symt, PClass *cls) //========================================================================== // -// ActorConstDef +// ParseNativeVariable // -// Parses a constant definition. +// Parses a native variable declaration. // //========================================================================== -static void ParseVariable (FScanner &sc, PSymbolTable * symt, PClass *cls) +static void ParseNativeVariable (FScanner &sc, PSymbolTable * symt, PClass *cls) { FExpressionType valuetype; @@ -319,6 +319,7 @@ static void ParseVariable (FScanner &sc, PSymbolTable * symt, PClass *cls) PSymbolVariable *sym = new PSymbolVariable(symname); sym->offset = vi->address; // todo sym->ValueType = valuetype; + sym->bUserVar = false; if (symt->AddSymbol (sym) == NULL) { @@ -328,6 +329,70 @@ static void ParseVariable (FScanner &sc, PSymbolTable * symt, PClass *cls) } } +//========================================================================== +// +// ParseUserVariable +// +// Parses a user variable declaration. +// +//========================================================================== + +static void ParseUserVariable (FScanner &sc, PSymbolTable *symt, PClass *cls) +{ + FExpressionType valuetype; + + // Only non-native classes may have user variables. + if (!cls->bRuntimeClass) + { + sc.ScriptError("Native classes may not have user variables"); + } + + // Read the type and make sure it's int. + sc.MustGetAnyToken(); + if (sc.TokenType == TK_Int) + { + valuetype = VAL_Int; + } + else + { + sc.ScriptError("User variables must be of type int"); + } + + sc.MustGetToken(TK_Identifier); + // For now, restrict user variables to those that begin with "user_" to guarantee + // no clashes with internal member variable names. + if (sc.StringLen < 6 || strnicmp("user_", sc.String, 5) != 0) + { + sc.ScriptError("User variable names must begin with \"user_\""); + } + + FName symname = sc.String; + if (sc.CheckToken('[')) + { + FxExpression *expr = ParseExpression(sc, cls); + int maxelems = expr->EvalExpression(NULL).GetInt(); + delete expr; + sc.MustGetToken(']'); + if (maxelems <= 0) + { + sc.ScriptError("Array size must be positive"); + } + valuetype.MakeArray(maxelems); + } + sc.MustGetToken(';'); + + PSymbolVariable *sym = new PSymbolVariable(symname); + sym->offset = cls->Extend(sizeof(int) * (valuetype.Type == VAL_Array ? valuetype.size : 1)); + sym->ValueType = valuetype; + sym->bUserVar = true; + if (symt->AddSymbol(sym) == NULL) + { + delete sym; + sc.ScriptError ("'%s' is already defined in '%s'.", + symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global"); + } +} + //========================================================================== // // Parses a flag name @@ -1064,7 +1129,11 @@ static void ParseActor(FScanner &sc) break; case TK_Native: - ParseVariable (sc, &info->Class->Symbols, info->Class); + ParseNativeVariable (sc, &info->Class->Symbols, info->Class); + break; + + case TK_Var: + ParseUserVariable (sc, &info->Class->Symbols, info->Class); break; case TK_Identifier: @@ -1125,7 +1194,7 @@ void ParseDecorate (FScanner &sc) break; case TK_Native: - ParseVariable(sc, &GlobalSymbols, NULL); + ParseNativeVariable(sc, &GlobalSymbols, NULL); break; case ';': diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index fbbab0a40..51513e10c 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -27,7 +27,6 @@ ACTOR Actor native //: Thinker native fixed_t alpha; native angle_t angle; native int args[5]; - native int uservar[10]; native fixed_t ceilingz; native fixed_t floorz; native int health; @@ -47,6 +46,7 @@ ACTOR Actor native //: Thinker native fixed_t momy; // alias for vely native fixed_t momz; // alias for velz native int score; + // Meh, MBF redundant functions. Only for DeHackEd support. action native A_Turn(float angle = 0); action native A_LineEffect(int boomspecial = 0, int tag = 0); @@ -195,6 +195,7 @@ ACTOR Actor native //: Thinker action native A_Print(string whattoprint, float time = 0, string fontname = ""); action native A_PrintBold(string whattoprint, float time = 0, string fontname = ""); action native A_Log(string whattoprint); + action native A_LogInt(int whattoprint); action native A_SetTranslucent(float alpha, int style = 0); action native A_FadeIn(float reduce = 0.1); action native A_FadeOut(float reduce = 0.1, bool remove = true); @@ -261,7 +262,8 @@ ACTOR Actor native //: Thinker action native A_ScaleVelocity(float scale); action native A_ChangeVelocity(float x = 0, float y = 0, float z = 0, int flags = 0); action native A_SetArg(int pos, int value); - action native A_SetUserVar(int pos, int value); + action native A_SetUserVar(name varname, int value); + action native A_SetUserArray(name varname, int index, int value); action native A_SetSpecial(int spec, int arg0 = 0, int arg1 = 0, int arg2 = 0, int arg3 = 0, int arg4 = 0); action native A_Quake(int intensity, int duration, int damrad, int tremrad, sound sfx = "world/quake");