diff --git a/src/dehacked.c b/src/dehacked.c
index aa6f4f7f9715dabbbade2d9d208a1b98bf9951c9..071f1bcfe05567c2d7d8c765eaac273c599b2a97 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -8263,11 +8263,11 @@ static inline int lib_freeslot(lua_State *L)
 }
 
 // Wrapper for ALL A_Action functions.
-// Upvalue: actionf_t to represent
 // Arguments: mobj_t actor, int var1, int var2
-static inline int lib_action(lua_State *L)
+static int action_call(lua_State *L)
 {
-	actionf_t *action = lua_touserdata(L,lua_upvalueindex(1));
+	//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,1,META_MOBJ));
 	var1 = (INT32)luaL_optinteger(L,2,0);
 	var2 = (INT32)luaL_optinteger(L,3,0);
@@ -8547,9 +8547,8 @@ static inline int lib_getenum(lua_State *L)
 		// Retrieving them from this metatable allows them to be case-insensitive!
 		for (i = 0; actionpointers[i].name; i++)
 			if (fasticmp(word, actionpointers[i].name)) {
-				// push lib_action as a C closure with the actionf_t* as an upvalue.
-				lua_pushlightuserdata(L, &actionpointers[i].action);
-				lua_pushcclosure(L, lib_action, 1);
+				// We push the actionf_t* itself as userdata!
+				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
 				return 1;
 			}
 		return 0;
@@ -8563,8 +8562,7 @@ static inline int lib_getenum(lua_State *L)
 		}
 		for (i = 0; actionpointers[i].name; i++)
 			if (fasticmp(superactions[superstack-1], actionpointers[i].name)) {
-				lua_pushlightuserdata(L, &actionpointers[i].action);
-				lua_pushcclosure(L, lib_action, 1);
+				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
 				return 1;
 			}
 		return 0;
@@ -8688,9 +8686,30 @@ int LUA_EnumLib(lua_State *L)
 	return 0;
 }
 
+// getActionName(action) -> return action's string name
+static int lib_getActionName(lua_State *L)
+{
+	actionf_t *action = *((actionf_t **)luaL_checkudata(L, 1, META_ACTION));
+	const char *name = NULL;
+	if (!action)
+		return 0; // insert error here (or not?)
+	name = LUA_GetActionName(action);
+	if (!name) // that can't be right?
+		return 0;
+	lua_pushstring(L, name);
+	return 1;
+}
+
 int LUA_SOCLib(lua_State *L)
 {
 	lua_register(L,"freeslot",lib_freeslot);
+	lua_register(L,"getActionName",lib_getActionName);
+
+	luaL_newmetatable(L, META_ACTION);
+		lua_pushcfunction(L, action_call);
+		lua_setfield(L, -2, "__call");
+	lua_pop(L, 1);
+
 	return 0;
 }
 
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 93f2979fa181d6dea78253327dea6d70027c5d09..2a129c88a04a35fa28ed63811f280fa33a48063e 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -131,6 +131,8 @@ static const struct {
 	{META_PATCH,        "patch_t"},
 	{META_COLORMAP,     "colormap"},
 	{META_CAMERA,       "camera_t"},
+
+	{META_ACTION,       "action"},
 	{NULL,              NULL}
 };
 
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 9361abe94e9ec06cf622a7999820e169c2270316..3edd6cc2fffb29f4529389f16b4895f1f28f4907 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -244,6 +244,17 @@ static int lib_setState(lua_State *L)
 			case LUA_TSTRING: // It's a string, expect the name of a built-in action
 				LUA_SetActionByName(state, lua_tostring(L, 3));
 				break;
+			case LUA_TUSERDATA: // It's a userdata, expect META_ACTION of a built-in action
+			{
+				actionf_t *action = *((actionf_t **)luaL_checkudata(L, 3, META_ACTION));
+
+				if (!action)
+					return 0; //insert error here
+
+				state->action = *action;
+				state->action.acv = action->acv;
+				state->action.acp1 = action->acp1;
+			}
 			case LUA_TFUNCTION: // It's a function (a Lua function or a C function? either way!)
 				lua_getfield(L, LUA_REGISTRYINDEX, LREG_STATEACTION);
 				I_Assert(lua_istable(L, -1));
@@ -393,9 +404,8 @@ static int state_get(lua_State *L)
 			return 0; // Just what is this??
 		// get the function from the global
 		// because the metatable will trigger.
-		lua_getglobal(L, name); // actually gets from LREG_ACTIONS if applicable, and pushes a new C closure if not.
-		lua_pushstring(L, name); // push the name we found.
-		return 2; // return both the function and its name, in case somebody wanted to do a comparison by name or something?
+		lua_getglobal(L, name); // actually gets from LREG_ACTIONS if applicable, and pushes a META_ACTION userdata if not.
+		return 1; // return just the function
 	} else if (fastcmp(field,"var1"))
 		number = st->var1;
 	else if (fastcmp(field,"var2"))
@@ -439,6 +449,17 @@ static int state_set(lua_State *L)
 		case LUA_TSTRING: // It's a string, expect the name of a built-in action
 			LUA_SetActionByName(st, lua_tostring(L, 3));
 			break;
+		case LUA_TUSERDATA: // It's a userdata, expect META_ACTION of a built-in action
+		{
+			actionf_t *action = *((actionf_t **)luaL_checkudata(L, 3, META_ACTION));
+
+			if (!action)
+				return 0; //insert error here
+
+			st->action = *action;
+			st->action.acv = action->acv;
+			st->action.acp1 = action->acp1;
+		}
 		case LUA_TFUNCTION: // It's a function (a Lua function or a C function? either way!)
 			lua_getfield(L, LUA_REGISTRYINDEX, LREG_STATEACTION);
 			I_Assert(lua_istable(L, -1));
diff --git a/src/lua_libs.h b/src/lua_libs.h
index fd4937b6354207a63a73f1a4c9318d7e802083c7..b63a3ceed8421496335ef6f16a4bd417057914fe 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -60,6 +60,8 @@ extern lua_State *gL;
 #define META_COLORMAP "COLORMAP"
 #define META_CAMERA "CAMERA_T*"
 
+#define META_ACTION "ACTIONF_T*"
+
 boolean luaL_checkboolean(lua_State *L, int narg);
 
 int LUA_EnumLib(lua_State *L);