diff --git a/src/d_main.cpp b/src/d_main.cpp index 97bb47ef73..83c1e2b1b6 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); @@ -3064,6 +3065,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 aa26c0163d..07f0723757 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 @@ -4854,6 +4855,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; @@ -7280,8 +7352,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) {}