From dcdbdec2bf8fffbc4b2d99ac89612cb9516cfcb9 Mon Sep 17 00:00:00 2001
From: Sally Coolatta <tehrealsalt@gmail.com>
Date: Sun, 6 Feb 2022 15:16:30 -0500
Subject: [PATCH] Make super for A_Actions act as described

---
 src/deh_lua.c     | 61 +++++++++++++++++++++++++++++++-------------
 src/deh_tables.h  |  1 +
 src/dehacked.h    |  5 ++--
 src/info.h        |  3 ++-
 src/lua_infolib.c | 64 ++++++++++++++++++++++++++++++++++-------------
 src/lua_script.c  | 14 ++++++++++-
 6 files changed, 109 insertions(+), 39 deletions(-)

diff --git a/src/deh_lua.c b/src/deh_lua.c
index a2ffca95b0..43e25a49d1 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -195,25 +195,32 @@ static inline int lib_freeslot(lua_State *L)
 // Arguments: mobj_t actor, int var1, int var2
 static int action_call(lua_State *L)
 {
-	//actionf_t *action = lua_touserdata(L,lua_upvalueindex(1));
 	actionf_t *action = *((actionf_t **)luaL_checkudata(L, 1, META_ACTION));
 	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+
 	var1 = (INT32)luaL_optinteger(L, 3, 0);
 	var2 = (INT32)luaL_optinteger(L, 4, 0);
+
 	if (!actor)
+	{
 		return LUA_ErrInvalid(L, "mobj_t");
+	}
+
 	action->acp1(actor);
 	return 0;
 }
 
 // Hardcoded A_Action name to call for super() or NULL if super() would be invalid.
 // Set in lua_infolib.
-const char *superactions[MAXRECURSION];
+const char *luaactions[MAX_ACTION_RECURSION];
+UINT8 luaactionstack = 0;
 UINT8 superstack = 0;
 
 static int lib_dummysuper(lua_State *L)
 {
-	return luaL_error(L, "Can't call super() outside of hardcode-replacing A_Action functions being called by state changes!"); // convoluted, I know. @_@;;
+	// TODO: Now that the restriction on only being allowed in state changes was lifted,
+	// it'd be nice to have super extend to Lua A_ functions too :)
+	return luaL_error(L, "Can't call super() outside of hardcode-replacing A_Action functions!");
 }
 
 static inline int lib_getenum(lua_State *L)
@@ -500,42 +507,62 @@ static inline int lib_getenum(lua_State *L)
 	}
 	else if (!mathlib && fastncmp("A_",word,2)) {
 		char *caps;
-		// Try to get a Lua action first.
-		/// \todo Push a closure that sets superactions[] and superstack.
+
+		// Hardcoded actions come first.
+		// Trying to call them will invoke LUA_CallAction, which will handle super properly.
+		// Retrieving them from this metatable allows them to be case-insensitive!
+		for (i = 0; actionpointers[i].name; i++)
+		{
+			if (fasticmp(word, actionpointers[i].name))
+			{
+				// We push the actionf_t* itself as userdata!
+				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
+				return 1;
+			}
+		}
+
+		// Now try to get Lua actions.
+		/// \todo Push a closure that sets luaactions[] and luaactionstack.
+		/// This would be part one of a step to get super functions working for custom A_ functions.
+		/// Custom functions.
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_ACTIONS);
+
 		// actions are stored in all uppercase.
 		caps = Z_StrDup(word);
 		strupr(caps);
 		lua_getfield(L, -1, caps);
 		Z_Free(caps);
+
 		if (!lua_isnil(L, -1))
+		{
 			return 1; // Success! :D That was easy.
+		}
+
 		// Welp, that failed.
 		lua_pop(L, 2); // pop nil and LREG_ACTIONS
-
-		// Hardcoded actions as callable Lua functions!
-		// Retrieving them from this metatable allows them to be case-insensitive!
-		for (i = 0; actionpointers[i].name; i++)
-			if (fasticmp(word, actionpointers[i].name)) {
-				// We push the actionf_t* itself as userdata!
-				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
-				return 1;
-			}
 		return 0;
 	}
 	else if (!mathlib && fastcmp("super",word))
 	{
-		if (!superstack)
+		if (!luaactionstack)
 		{
+			// Not in A_ action routine
 			lua_pushcfunction(L, lib_dummysuper);
 			return 1;
 		}
+
 		for (i = 0; actionpointers[i].name; i++)
-			if (fasticmp(superactions[superstack-1], actionpointers[i].name)) {
+		{
+			if (fasticmp(luaactions[luaactionstack-1], actionpointers[i].name))
+			{
 				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
 				return 1;
 			}
-		return 0;
+		}
+
+		// Not a hardcoded A_ action.
+		lua_pushcfunction(L, lib_dummysuper);
+		return 1;
 	}
 
 	if (fastcmp(word, "BT_USE")) // Remove case when 2.3 nears release...
diff --git a/src/deh_tables.h b/src/deh_tables.h
index 1f265cc999..70347f7dd7 100644
--- a/src/deh_tables.h
+++ b/src/deh_tables.h
@@ -30,6 +30,7 @@ extern UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite
 	memset(FREE_MOBJS,0,sizeof(char *) * NUMMOBJFREESLOTS);\
 	memset(FREE_SKINCOLORS,0,sizeof(char *) * NUMCOLORFREESLOTS);\
 	memset(used_spr,0,sizeof(UINT8) * ((NUMSPRITEFREESLOTS / 8) + 1));\
+	memset(actionsoverridden, LUA_REFNIL, sizeof(actionsoverridden));\
 }
 
 struct flickytypes_s {
diff --git a/src/dehacked.h b/src/dehacked.h
index 1b200e2466..e29aef6ff8 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -40,8 +40,9 @@ extern boolean gamedataadded;
 extern boolean titlechanged;
 extern boolean introchanged;
 
-#define MAXRECURSION 30
-extern const char *superactions[MAXRECURSION];
+#define MAX_ACTION_RECURSION 30
+extern const char *luaactions[MAX_ACTION_RECURSION];
+extern UINT8 luaactionstack;
 extern UINT8 superstack;
 
 // If the dehacked patch does not match this version, we throw a warning
diff --git a/src/info.h b/src/info.h
index 031a08b431..6e9460beca 100644
--- a/src/info.h
+++ b/src/info.h
@@ -18,6 +18,7 @@
 #include "d_think.h"
 #include "sounds.h"
 #include "m_fixed.h"
+#include "dehacked.h" // MAX_ACTION_RECURSION
 
 // deh_tables.c now has lists for the more named enums! PLEASE keep them up to date!
 // For great modding!!
@@ -554,7 +555,7 @@ void A_DragonWing();
 void A_DragonSegment();
 void A_ChangeHeight();
 
-extern boolean actionsoverridden[NUMACTIONS];
+extern int actionsoverridden[NUMACTIONS][MAX_ACTION_RECURSION];
 
 // ratio of states to sprites to mobj types is roughly 6 : 1 : 1
 #define NUMMOBJFREESLOTS 512
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index af2d99a0c0..e20202495a 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -66,7 +66,7 @@ const char *const sfxinfo_wopt[] = {
 	"caption",
 	NULL};
 
-boolean actionsoverridden[NUMACTIONS] = {false};
+int actionsoverridden[NUMACTIONS][MAX_ACTION_RECURSION];
 
 //
 // Sprite Names
@@ -645,8 +645,8 @@ static void A_Lua(mobj_t *actor)
 		if (lua_rawequal(gL, -1, -4))
 		{
 			found = true;
-			superactions[superstack] = lua_tostring(gL, -2); // "A_ACTION"
-			++superstack;
+			luaactions[luaactionstack] = lua_tostring(gL, -2); // "A_ACTION"
+			++luaactionstack;
 			lua_pop(gL, 2); // pop the name and function
 			break;
 		}
@@ -661,8 +661,8 @@ static void A_Lua(mobj_t *actor)
 
 	if (found)
 	{
-		--superstack;
-		superactions[superstack] = NULL;
+		--luaactionstack;
+		luaactions[luaactionstack] = NULL;
 	}
 }
 
@@ -816,18 +816,46 @@ boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor)
 {
 	I_Assert(actor != NULL);
 
-	if (!actionsoverridden[actionnum]) // The action is not overriden,
-		return false; // action not called.
+	if (actionsoverridden[actionnum][0] == LUA_REFNIL)
+	{
+		// The action was not overridden at all,
+		// so call the hardcoded version.
+		return false;
+	}
+
+	if (luaactionstack && fasticmp(actionpointers[actionnum].name, luaactions[luaactionstack-1]))
+	{
+		// The action is calling itself,
+		// so look up the next Lua reference in its stack.
+
+		// 0 is just the reference to the one we're calling,
+		// so we increment here.
+		superstack++;
+
+		if (superstack >= MAX_ACTION_RECURSION)
+		{
+			CONS_Alert(CONS_WARNING, "Max Lua super recursion reached! Cool it on calling super!\n");
+			return false;
+		}
+	}
+	else
+	{
+		// Not calling itself, reset the super counter.
+		superstack = 0;
+	}
 
-	if (superstack && fasticmp(actionpointers[actionnum].name, superactions[superstack-1])) // the action is calling itself,
-		return false; // let it call the hardcoded function instead.
+	if (actionsoverridden[actionnum][superstack] == LUA_REFNIL)
+	{
+		// No Lua reference beyond this point.
+		// Let it call the hardcoded function instead.
+		return false;
+	}
 
+	// Push error function
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
-	// grab function by uppercase name.
-	lua_getfield(gL, LUA_REGISTRYINDEX, LREG_ACTIONS);
-	lua_getfield(gL, -1, actionpointers[actionnum].name);
-	lua_remove(gL, -2); // pop LREG_ACTIONS
+	// Push function by reference.
+	lua_getref(gL, actionsoverridden[actionnum][superstack]);
 
 	if (lua_isnil(gL, -1)) // no match
 	{
@@ -835,7 +863,7 @@ boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor)
 		return false; // action not called.
 	}
 
-	if (superstack == MAXRECURSION)
+	if (luaactionstack >= MAX_ACTION_RECURSION)
 	{
 		CONS_Alert(CONS_WARNING, "Max Lua Action recursion reached! Cool it on the calling A_Action functions from inside A_Action functions!\n");
 		lua_pop(gL, 2); // pop function and error handler
@@ -849,14 +877,14 @@ boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor)
 	lua_pushinteger(gL, var1);
 	lua_pushinteger(gL, var2);
 
-	superactions[superstack] = actionpointers[actionnum].name;
-	++superstack;
+	luaactions[luaactionstack] = actionpointers[actionnum].name;
+	++luaactionstack;
 
 	LUA_Call(gL, 3, 0, -(2 + 3));
 	lua_pop(gL, -1); // Error handler
 
-	--superstack;
-	superactions[superstack] = NULL;
+	--luaactionstack;
+	luaactions[luaactionstack] = NULL;
 	return true; // action successfully called.
 }
 
diff --git a/src/lua_script.c b/src/lua_script.c
index a1376ca2e3..5441d315ed 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -485,7 +485,19 @@ static int setglobals(lua_State *L)
 
 		actionnum = LUA_GetActionNumByName(name);
 		if (actionnum < NUMACTIONS)
-			actionsoverridden[actionnum] = true;
+		{
+			int i;
+
+			for (i = MAX_ACTION_RECURSION-1; i > 0; i--)
+			{
+				// Move other references deeper.
+				actionsoverridden[actionnum][i] = actionsoverridden[actionnum][i - 1];
+			}
+
+			// Add the new reference.
+			lua_pushvalue(L, 2);
+			actionsoverridden[actionnum][0] = luaL_ref(L, LUA_REGISTRYINDEX);
+		}
 
 		Z_Free(name);
 		return 0;
-- 
GitLab