From 19b23f2cf31de74f3c1f71ed82322e997ec7ec38 Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@zdoom.fake>
Date: Sun, 25 Oct 2009 02:19:51 +0000
Subject: [PATCH] - 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.

SVN r1933 (trunk)
---
 docs/rh-log.txt                      |   4 +
 src/actor.h                          |   1 -
 src/dobject.cpp                      |  82 ++++++++++++++++++
 src/dobject.h                        |   1 +
 src/dobjtype.cpp                     |  17 +++-
 src/dobjtype.h                       |   6 +-
 src/farchive.cpp                     |   8 +-
 src/p_acs.cpp                        | 120 +++++++++++++++++++++++++--
 src/p_mobj.cpp                       |   8 +-
 src/sc_man.h                         |   1 +
 src/sc_man_scanner.re                |   1 +
 src/thingdef/thingdef_codeptr.cpp    |  67 +++++++++++++--
 src/thingdef/thingdef_expression.cpp |   1 -
 src/thingdef/thingdef_parse.cpp      |  79 ++++++++++++++++--
 wadsrc/static/actors/actor.txt       |   6 +-
 15 files changed, 374 insertions(+), 28 deletions(-)

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<AActor> 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<PSymbolVariable *>(sym);
+					if (var->bUserVar)
+					{
+						count = var->ValueType.Type == VAL_Array ? var->ValueType.size : 1;
+						varloc = (int *)(reinterpret_cast<BYTE *>(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<PSymbolVariable *>(sym);
+
+				if (var->bUserVar)
+				{
+					wanted = var->ValueType.Type == VAL_Array ? var->ValueType.size : 1;
+					varloc = (int *)(reinterpret_cast<BYTE *>(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 <extension> 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<PSymbol *> 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<AActor *>(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<AActor *>(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<PSymbolVariable *>(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<BYTE *>(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<PSymbolVariable *>(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<BYTE *>(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<AActor> iterator (args[0]);
+					TActorIterator<AActor> 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<AActor> 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<PSymbolVariable *>(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<BYTE *>(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<PSymbolVariable *>(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<BYTE *>(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");