From 01922d94f137301a835428d85b58865a8feae675 Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 1 Nov 2023 15:50:59 -0300 Subject: [PATCH 1/2] Add can_change for console variables --- src/command.c | 30 ++++++---------- src/command.h | 4 +++ src/lua_consolelib.c | 44 ++++++++++++++++++++++-- src/netcode/d_netcmd.c | 77 ++++++++++++++++++++++++------------------ 4 files changed, 100 insertions(+), 55 deletions(-) diff --git a/src/command.c b/src/command.c index e0deff8e1..32400c875 100644 --- a/src/command.c +++ b/src/command.c @@ -1379,8 +1379,8 @@ void CV_RegisterVar(consvar_t *variable) #ifdef PARANOIA if ((variable->flags & CV_NOINIT) && !(variable->flags & CV_CALL)) I_Error("variable %s has CV_NOINIT without CV_CALL\n", variable->name); - if ((variable->flags & CV_CALL) && !variable->func) - I_Error("variable %s has CV_CALL without a function\n", variable->name); + if ((variable->flags & CV_CALL) && !(variable->func || variable->can_change)) + I_Error("variable %s has CV_CALL without any callbacks\n", variable->name); #endif if (variable->flags & CV_NOINIT) @@ -1446,12 +1446,13 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth) boolean override = false; INT32 overrideval = 0; - // If we want messages informing us if cheats have been enabled or disabled, - // we need to rework the consvars a little bit. This call crashes the game - // on load because not all variables will be registered at that time. -/* boolean prevcheats = false; - if (var->flags & CV_CHEAT) - prevcheats = CV_CheatsEnabled(); */ + // raise 'can change' code + LUA_CVarChanged(var); // let consolelib know what cvar this is. + if (var->flags & CV_CALL && var->can_change && !stealth) + { + if (!var->can_change(valstr)) + return; + } if (var->PossibleValue) { @@ -1613,16 +1614,6 @@ found: } finish: - // See the note above. -/* if (var->flags & CV_CHEAT) - { - boolean newcheats = CV_CheatsEnabled(); - - if (!prevcheats && newcheats) - CONS_Printf(M_GetText("Cheats have been enabled.\n")); - else if (prevcheats && !newcheats) - CONS_Printf(M_GetText("Cheats have been disabled.\n")); - } */ if (var->flags & CV_SHOWMODIFONETIME || var->flags & CV_SHOWMODIF) { @@ -1635,8 +1626,7 @@ finish: } var->flags |= CV_MODIFIED; // raise 'on change' code - LUA_CVarChanged(var); // let consolelib know what cvar this is. - if (var->flags & CV_CALL && !stealth) + if (var->flags & CV_CALL && var->func && !stealth) var->func(); return; diff --git a/src/command.h b/src/command.h index 619d8c1dc..f0dc62418 100644 --- a/src/command.h +++ b/src/command.h @@ -136,6 +136,7 @@ typedef struct consvar_s //NULL, NULL, 0, NULL, NULL |, 0, NULL, NULL, 0, 0, NUL INT32 flags; // flags see cvflags_t above CV_PossibleValue_t *PossibleValue; // table of possible values void (*func)(void); // called on change, if CV_CALL set + boolean (*can_change)(const char*); // called before change, if CV_CALL set INT32 value; // for INT32 and fixed_t const char *string; // value in string char *zstring; // Either NULL or same as string. @@ -158,6 +159,9 @@ typedef struct consvar_s //NULL, NULL, 0, NULL, NULL |, 0, NULL, NULL, 0, 0, NUL /* name, defaultvalue, flags, PossibleValue, func */ #define CVAR_INIT( ... ) \ +{ __VA_ARGS__, NULL, 0, NULL, NULL, {0, {NULL}}, 0U, (char)0, NULL } + +#define CVAR_INIT_WITH_CALLBACKS( ... ) \ { __VA_ARGS__, 0, NULL, NULL, {0, {NULL}}, 0U, (char)0, NULL } #ifdef OLD22DEMOCOMPAT diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c index 9b3fd2ea0..454e1931f 100644 --- a/src/lua_consolelib.c +++ b/src/lua_consolelib.c @@ -299,6 +299,30 @@ static void Lua_OnChange(void) lua_remove(gL, 1); // remove LUA_GetErrorMessage } +static boolean Lua_CanChange(const char *valstr) +{ + lua_pushcfunction(gL, LUA_GetErrorMessage); + lua_insert(gL, 1); // Because LUA_Call wants it at index 1. + + // From CV_CanChange registry field, get the function for this cvar by name. + lua_getfield(gL, LUA_REGISTRYINDEX, "CV_CanChange"); + I_Assert(lua_istable(gL, -1)); + lua_pushlightuserdata(gL, this_cvar); + lua_rawget(gL, -2); // get function + + LUA_RawPushUserdata(gL, this_cvar); + lua_pushstring(gL, valstr); + + boolean result; + + LUA_Call(gL, 2, 1, 1); // call function(cvar, valstr) + result = lua_toboolean(gL, -1); + lua_pop(gL, 1); // pop CV_CanChange table + lua_remove(gL, 1); // remove LUA_GetErrorMessage + + return result; +} + static int lib_cvRegisterVar(lua_State *L) { const char *k; @@ -457,6 +481,20 @@ static int lib_cvRegisterVar(lua_State *L) lua_pop(L, 1); cvar->func = Lua_OnChange; } + else if (cvar->flags & CV_CALL && (k && fasticmp(k, "can_change"))) + { + if (!lua_isfunction(L, 4)) + { + TYPEERROR("func", LUA_TFUNCTION) + } + lua_getfield(L, LUA_REGISTRYINDEX, "CV_CanChange"); + I_Assert(lua_istable(L, 5)); + lua_pushlightuserdata(L, cvar); + lua_pushvalue(L, 4); + lua_rawset(L, 5); + lua_pop(L, 1); + cvar->can_change = Lua_CanChange; + } lua_pop(L, 1); } @@ -478,9 +516,9 @@ static int lib_cvRegisterVar(lua_State *L) return luaL_error(L, M_GetText("Variable %s has CV_NOINIT without CV_CALL"), cvar->name); } - if ((cvar->flags & CV_CALL) && !cvar->func) + if ((cvar->flags & CV_CALL) && !(cvar->func || cvar->can_change)) { - return luaL_error(L, M_GetText("Variable %s has CV_CALL without a function"), cvar->name); + return luaL_error(L, M_GetText("Variable %s has CV_CALL without any callbacks"), cvar->name); } cvar->flags |= CV_ALLOWLUA; @@ -674,6 +712,8 @@ int LUA_ConsoleLib(lua_State *L) lua_setfield(L, LUA_REGISTRYINDEX, "CV_PossibleValue"); lua_newtable(L); lua_setfield(L, LUA_REGISTRYINDEX, "CV_OnChange"); + lua_newtable(L); + lua_setfield(L, LUA_REGISTRYINDEX, "CV_CanChange"); // Push opaque CV_PossibleValue pointers // Because I don't care enough to bother. diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c index 52df53b5a..9cb7a37a1 100644 --- a/src/netcode/d_netcmd.c +++ b/src/netcode/d_netcmd.c @@ -109,6 +109,9 @@ static void Color2_OnChange(void); static void DummyConsvar_OnChange(void); static void SoundTest_OnChange(void); +static boolean Skin_CanChange(const char *valstr); +static boolean Skin2_CanChange(const char *valstr); + #ifdef NETGAME_DEVMODE static void Fishcake_OnChange(void); #endif @@ -227,7 +230,6 @@ consvar_t cv_seenames = CVAR_INIT ("seenames", "Ally/Foe", CV_SAVE|CV_ALLOWLUA, consvar_t cv_allowseenames = CVAR_INIT ("allowseenames", "Yes", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, CV_YesNo, NULL); // names -static char *lastskinnames[2]; consvar_t cv_playername = CVAR_INIT ("name", "Sonic", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name_OnChange); consvar_t cv_playername2 = CVAR_INIT ("name2", "Tails", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name2_OnChange); // player colors @@ -235,8 +237,8 @@ UINT16 lastgoodcolor = SKINCOLOR_BLUE, lastgoodcolor2 = SKINCOLOR_BLUE; consvar_t cv_playercolor = CVAR_INIT ("color", "Blue", CV_CALL|CV_NOINIT|CV_ALLOWLUA, Color_cons_t, Color_OnChange); consvar_t cv_playercolor2 = CVAR_INIT ("color2", "Orange", CV_CALL|CV_NOINIT|CV_ALLOWLUA, Color_cons_t, Color2_OnChange); // player's skin, saved for commodity, when using a favorite skins wad.. -consvar_t cv_skin = CVAR_INIT ("skin", DEFAULTSKIN, CV_CALL|CV_NOINIT|CV_ALLOWLUA, NULL, Skin_OnChange); -consvar_t cv_skin2 = CVAR_INIT ("skin2", DEFAULTSKIN2, CV_CALL|CV_NOINIT|CV_ALLOWLUA, NULL, Skin2_OnChange); +consvar_t cv_skin = CVAR_INIT_WITH_CALLBACKS ("skin", DEFAULTSKIN, CV_CALL|CV_NOINIT|CV_ALLOWLUA, NULL, Skin_OnChange, Skin_CanChange); +consvar_t cv_skin2 = CVAR_INIT_WITH_CALLBACKS ("skin2", DEFAULTSKIN2, CV_CALL|CV_NOINIT|CV_ALLOWLUA, NULL, Skin2_OnChange, Skin2_CanChange); // saved versions of the above six consvar_t cv_defaultplayercolor = CVAR_INIT ("defaultcolor", "Blue", CV_SAVE, Color_cons_t, NULL); @@ -4741,6 +4743,41 @@ static void Name2_OnChange(void) SendNameAndColor2(); } +static boolean Skin_CanChange(const char *valstr) +{ + (void)valstr; + + if (!Playing()) + return true; // do whatever you want + + if (!(multiplayer || netgame)) // In single player. + return true; + + if (CanChangeSkin(consoleplayer) && !P_PlayerMoving(consoleplayer)) + return true; + else + { + CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); + return false; + } +} + +static boolean Skin2_CanChange(const char *valstr) +{ + (void)valstr; + + if (!Playing() || !splitscreen) + return true; // do whatever you want + + if (CanChangeSkin(secondarydisplayplayer) && !P_PlayerMoving(secondarydisplayplayer)) + return true; + else + { + CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); + return false; + } +} + /** Sends a skin change for the console player, unless that player is moving. * \sa cv_skin, Skin2_OnChange, Color_OnChange * \author Graue @@ -4748,10 +4785,7 @@ static void Name2_OnChange(void) static void Skin_OnChange(void) { if (!Playing()) - return; // do whatever you want - - if (lastskinnames[0] == NULL) - lastskinnames[0] = Z_StrDup(cv_skin.string); + return; if (!(multiplayer || netgame)) // In single player. { @@ -4767,17 +4801,7 @@ static void Skin_OnChange(void) return; } - if (CanChangeSkin(consoleplayer) && !P_PlayerMoving(consoleplayer)) - { - SendNameAndColor(); - Z_Free(lastskinnames[0]); - lastskinnames[0] = Z_StrDup(cv_skin.string); - } - else - { - CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin, lastskinnames[0]); - } + SendNameAndColor(); } /** Sends a skin change for the secondary splitscreen player, unless that @@ -4788,22 +4812,9 @@ static void Skin_OnChange(void) static void Skin2_OnChange(void) { if (!Playing() || !splitscreen) - return; // do whatever you want + return; - if (lastskinnames[1] == NULL) - lastskinnames[1] = Z_StrDup(cv_skin2.string); - - if (CanChangeSkin(secondarydisplayplayer) && !P_PlayerMoving(secondarydisplayplayer)) - { - SendNameAndColor2(); - Z_Free(lastskinnames[1]); - lastskinnames[1] = Z_StrDup(cv_skin.string); - } - else - { - CONS_Alert(CONS_NOTICE, M_GetText("You can't change your skin at the moment.\n")); - CV_StealthSet(&cv_skin2, lastskinnames[1]); - } + SendNameAndColor2(); } /** Sends a color change for the console player, unless that player is moving. From 075dab924c9e2c55f7546d05059aac2f878180bc Mon Sep 17 00:00:00 2001 From: Lactozilla Date: Wed, 1 Nov 2023 16:16:59 -0300 Subject: [PATCH 2/2] Handle edge case where the callback returns false while registering a variable --- src/command.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/command.c b/src/command.c index 32400c875..49dac887b 100644 --- a/src/command.c +++ b/src/command.c @@ -1451,7 +1451,29 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth) if (var->flags & CV_CALL && var->can_change && !stealth) { if (!var->can_change(valstr)) - return; + { + // The callback refused the default value on register. How naughty... + // So we just use some fallback value. + if (var->string == NULL) + { + if (var->PossibleValue) + { + // Use PossibleValue + valstr = var->PossibleValue[0].strvalue; + } + else + { + // Else, use an empty string + valstr = ""; + } + } + else + { + // Callback returned false, and the game is not registering this variable, + // so we can return safely. + return; + } + } } if (var->PossibleValue)