From 52913f5dcfb508384c0102bbb55836c049d9c77c Mon Sep 17 00:00:00 2001 From: Boondorl Date: Wed, 26 Feb 2025 18:55:03 -0500 Subject: [PATCH] Allow external handling of internal ACS functions from other ports For ACS functions that should not be baked into the engine, allow hooking into them externally so that ZScript mods can still make use of them. --- src/d_main.cpp | 2 + src/events.cpp | 29 ++++++++++ src/events.h | 3 ++ src/playsim/p_acs.cpp | 96 ++++++++++++++++++++++++++++++++- wadsrc/static/zscript/events.zs | 66 +++++++++++++++++++++++ 5 files changed, 194 insertions(+), 2 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 34ae5b3696..a6cefc0bef 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -158,6 +158,7 @@ void I_ShutdownInput(); void SetConsoleNotifyBuffer(); void I_UpdateDiscordPresence(bool SendPresence, const char* curstatus, const char* appid, const char* steamappid); bool M_SetSpecialMenu(FName& menu, int param); // game specific checks +void SetHandledACSFunctions(); const FIWADInfo *D_FindIWAD(TArray &wadfiles, const char *iwad, const char *basewad); void InitWidgetResources(const char* basewad); @@ -3086,6 +3087,7 @@ static int FileSystemPrintf(FSMessageLevel level, const char* fmt, ...) static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allwads, std::vector& pwads) { NetworkEntityManager::InitializeNetworkEntities(); + SetHandledACSFunctions(); if (!restart) { diff --git a/src/events.cpp b/src/events.cpp index a45bacd8b7..23f160999a 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -975,6 +975,18 @@ void EventManager::NetCommand(FNetworkCommand& cmd) handler->NetCommandProcess(cmd); } +int EventManager::ProcessACSFunction(int func, const TArray* args) +{ + int res = 0; + if (ShouldCallStatic(false)) + res = staticEventManager.ProcessACSFunction(func, args); + + for (DStaticEventHandler* handler = FirstEventHandler; handler; handler = handler->next) + handler->ACSFunctionProcess(func, args, res); + + return res; +} + void EventManager::Console(int player, FString name, int arg1, int arg2, int arg3, bool manual, bool ui) { if (ShouldCallStatic(false)) staticEventManager.Console(player, name, arg1, arg2, arg3, manual, ui); @@ -2274,6 +2286,23 @@ void DStaticEventHandler::NetCommandProcess(FNetworkCommand& cmd) } } +void DStaticEventHandler::ACSFunctionProcess(int code, const TArray* args, int& res) +{ + IFVIRTUAL(DStaticEventHandler, ACSFunctionProcess) + { + if (isEmpty(func)) + return; + + TArray funcArgs = {}; + if (args != nullptr) + funcArgs = *args; + + VMValue params[] = { this, code, &funcArgs }; + VMReturn returns[] = { &res }; + VMCall(func, params, 3, returns, 1); + } +} + void DStaticEventHandler::ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3, bool manual, bool ui) { if (player < 0) diff --git a/src/events.h b/src/events.h index b1310a9b88..fc4296136e 100644 --- a/src/events.h +++ b/src/events.h @@ -345,6 +345,7 @@ public: // void ConsoleProcess(int player, FString name, int arg1, int arg2, int arg3, bool manual, bool ui); void NetCommandProcess(FNetworkCommand& cmd); + void ACSFunctionProcess(int func, const TArray* args, int& res); // void CheckReplacement(PClassActor* replacee, PClassActor** replacement, bool* final); @@ -539,6 +540,8 @@ struct EventManager void Console(int player, FString name, int arg1, int arg2, int arg3, bool manual, bool ui); // This reads from ZScript network commands. void NetCommand(FNetworkCommand& cmd); + // Custom handling for known but unsupported ACS events. + int ProcessACSFunction(int func, const TArray* args); // called when looking up the replacement for an actor class bool CheckReplacement(PClassActor* replacee, PClassActor** replacement); diff --git a/src/playsim/p_acs.cpp b/src/playsim/p_acs.cpp index 6eb9cb4e63..88d4a289e0 100644 --- a/src/playsim/p_acs.cpp +++ b/src/playsim/p_acs.cpp @@ -78,6 +78,7 @@ #include "s_music.h" #include "v_video.h" #include "texturemanager.h" +#include "events.h" // P-codes for ACS scripts enum @@ -4823,6 +4824,77 @@ enum EACSFunctions ACSF_SetTeamScore, // (int team, int value }; +// Op code -> minimum arg count +static TMap HandledACSFunctions = {}; + +void SetHandledACSFunctions() +{ + // Zandronum + HandledACSFunctions[100] = 0; // ResetMap + HandledACSFunctions[101] = 1; // PlayerIsSpectator + // ConsolePlayerNumber will be intentionally left out until proper client-side + // ACS scripts are implemented. Right now it'd just leave the game prone to desyncs. + HandledACSFunctions[103] = 2; // GetTeamProperty + HandledACSFunctions[104] = 1; // GetPlayersLivesLeft + HandledACSFunctions[105] = 2; // SetPlayerLivesLeft + HandledACSFunctions[106] = 2; // KickFromGame + HandledACSFunctions[107] = 0; // GetGamemodeState + HandledACSFunctions[108] = 3; // SetDBEntry + HandledACSFunctions[109] = 2; // GetDBEntry + HandledACSFunctions[110] = 3; // SetDBEntryString + HandledACSFunctions[111] = 2; // GetDBEntryString + HandledACSFunctions[112] = 3; // IncrementDBEntry + HandledACSFunctions[113] = 1; // PlayerIsLoggedIn + HandledACSFunctions[114] = 1; // GetPlayerAccountName + HandledACSFunctions[115] = 4; // SortDBEntries + HandledACSFunctions[116] = 1; // CountDBResults + HandledACSFunctions[117] = 1; // FreeDBResults + HandledACSFunctions[118] = 2; // GetDBResultKeyString + HandledACSFunctions[119] = 2; // GetDBResultValueString + HandledACSFunctions[120] = 2; // GetDBResultValue + HandledACSFunctions[121] = 3; // GetDBEntryRank + HandledACSFunctions[122] = 4; // RequestScriptPuke + HandledACSFunctions[123] = 0; // BeginDBTransaction + HandledACSFunctions[124] = 0; // EndDBTransaction + HandledACSFunctions[125] = 1; // GetDBEntries + HandledACSFunctions[126] = 1; // NamedRequestScriptPuke + // System time functions are intentionally left out since they're prone to causing desyncs. Can be added + // in if client/server ever becomes a thing. + HandledACSFunctions[130] = 2; // SetDeadSpectator + HandledACSFunctions[131] = 1; // SetActivatorToPlayer + HandledACSFunctions[132] = 1; // SetCurrentGamemode + HandledACSFunctions[133] = 0; // GetCurrentGamemode + HandledACSFunctions[134] = 2; // SetGamemodeLimit + // Player class handling isn't implemented yet. + HandledACSFunctions[136] = 2; // SetPlayerChasecam + HandledACSFunctions[137] = 1; // GetPlayerChasecam + HandledACSFunctions[138] = 3; // SetPlayerScore + HandledACSFunctions[139] = 2; // GetPlayerScore + HandledACSFunctions[140] = 0; // InDemoMode + // Client-side scripts aren't implemented yet for ClientScript functions. + HandledACSFunctions[146] = 2; // SendNetworkString + HandledACSFunctions[147] = 2; // NamedSendNetworkString + HandledACSFunctions[148] = 2; // GetChatMessage + HandledACSFunctions[149] = 0; // GetMapRotationSize + HandledACSFunctions[150] = 2; // GetMapRotationInfo + HandledACSFunctions[151] = 0; // GetCurrentMapPosition + HandledACSFunctions[152] = 0; // GetEventResult + HandledACSFunctions[153] = 2; // GetActorSectorLocation + HandledACSFunctions[154] = 3; // ChangeTeamScore + HandledACSFunctions[155] = 2; // SetGameplaySettings + HandledACSFunctions[156] = 3; // SetCustomPlayerValue + HandledACSFunctions[157] = 2; // GetCustomPlayerValue + HandledACSFunctions[158] = 2; // ResetCustomDataToDefault + HandledACSFunctions[159] = 1; // LumpOpen + HandledACSFunctions[160] = 2; // LumpRead + HandledACSFunctions[161] = 2; // LumpReadString + HandledACSFunctions[166] = 2; // LumpGetInfo + HandledACSFunctions[167] = 1; // LumpClose + + // Eternity + HandledACSFunctions[302] = 1; // SetAirFriction +} + int DLevelScript::SideFromID(int id, int side) { if (side != 0 && side != 1) return -1; @@ -7249,8 +7321,28 @@ int DLevelScript::RunScript() int argCount = NEXTBYTE; int funcIndex = NEXTSHORT; - int retval, minCount = 0; - retval = CallFunction(argCount, funcIndex, &STACK(argCount), minCount); + int retval = 0, minCount = 0; + auto undefined = HandledACSFunctions.CheckKey(funcIndex); + if (undefined != nullptr) + { + if (argCount >= *undefined || (Level->i_compatflags2 & COMPATF2_NOACSARGCHECK)) + { + TArray args = {}; + auto argStart = &STACK(argCount); + for (size_t p = 0u; p < argCount; ++p) + args.Push(argStart[p]); + + retval = primaryLevel->localEventManager->ProcessACSFunction(funcIndex, &args); + } + else + { + minCount = *undefined; + } + } + else + { + retval = CallFunction(argCount, funcIndex, &STACK(argCount), minCount); + } if (minCount != 0) { Printf("Called ACS function index %d with too few args: %d (need %d)\n", funcIndex, argCount, minCount); diff --git a/wadsrc/static/zscript/events.zs b/wadsrc/static/zscript/events.zs index 883e8c3604..ab645fac93 100644 --- a/wadsrc/static/zscript/events.zs +++ b/wadsrc/static/zscript/events.zs @@ -144,6 +144,71 @@ struct ReplacedEvent native version("3.7") native bool IsFinal; } +enum EACSFunction +{ + ACSF_ResetMap = 100, + ACSF_PlayerIsSpectator, + + ACSF_GetTeamProperty = 103, + ACSF_GetPlayerLivesLeft, + ACSF_SetPlayerLivesLeft, + ACSF_KickFromGame, + ACSF_GetGamemodeState, + ACSF_SetDBEntry, + ACSF_GetDBEntry, + ACSF_SetDBEntryString, + ACSF_GetDBEntryString, + ACSF_IncrementDBEntry, + ACSF_PlayerIsLoggedIn, + ACSF_GetPlayerAccountName, + ACSF_SortDBEntries, + ACSF_CountDBResults, + ACSF_FreeDBResults, + ACSF_GetDBResultKeyString, + ACSF_GetDBResultValueString, + ACSF_GetDBResultValue, + ACSF_GetDBEntryRank, + ACSF_RequestScriptPuke, + ACSF_BeginDBTransaction, + ACSF_EndDBTransaction, + ACSF_GetDBEntries, + ACSF_NamedRequestScriptPuke, + + ACSF_SetDeadSpectator = 130, + ACSF_SetActivatorToPlayer, + ACSF_SetCurrentGamemode, + ACSF_GetCurrentGamemode, + ACSF_SetGamemodeLimit, + + ACSF_SetPlayerChasecam = 136, + ACSF_GetPlayerChasecam, + ACSF_SetPlayerScore, + ACSF_GetPlayerScore, + ACSF_InDemoMode, + + ACSF_SendNetworkString = 146, + ACSF_NamedSendNetworkString, + ACSF_GetChatMessage, + ACSF_GetMapRotationSize, + ACSF_GetMapRotationInfo, + ACSF_GetCurrentMapPosition, + ACSF_GetEventResult, + ACSF_GetActorSectorLocation, + ACSF_ChangeTeamScore, + ACSF_SetGameplaySettings, + ACSF_SetCustomPlayerValue, + ACSF_GetCustomPlayerValue, + ACSF_ResetCustomDataToDefault, + ACSF_LumpOpen, + ACSF_LumpRead, + ACSF_LumpReadString, + + ACSF_LumpGetInfo = 166, + ACSF_LumpClose, + + ACSF_SetAirFriction = 302, +} + class StaticEventHandler : Object native play version("2.4") { // static event handlers CAN register other static event handlers. @@ -200,6 +265,7 @@ class StaticEventHandler : Object native play version("2.4") virtual ui void InterfaceProcess(ConsoleEvent e) {} virtual void NetworkProcess(ConsoleEvent e) {} version("4.12") virtual void NetworkCommandProcess(NetworkCommand cmd) {} + version("4.15") virtual int ACSFunctionProcess(EACSFunction func, Array args) { return 0; } // virtual void CheckReplacement(ReplaceEvent e) {}