diff --git a/plugins/adminpanel.smx b/plugins/adminpanel.smx index d6db31d..f2ff07b 100644 Binary files a/plugins/adminpanel.smx and b/plugins/adminpanel.smx differ diff --git a/plugins/l4d2_randomizer.smx b/plugins/l4d2_randomizer.smx index c21bbe7..a88885f 100644 Binary files a/plugins/l4d2_randomizer.smx and b/plugins/l4d2_randomizer.smx differ diff --git a/scripting/GrabEnt.sp b/scripting/GrabEnt.sp index 190620f..7c038b5 100644 --- a/scripting/GrabEnt.sp +++ b/scripting/GrabEnt.sp @@ -585,12 +585,10 @@ bool CheckBlacklist(int entity) { return false; } } - if(StrContains(buffer, "prop_") > -1) { - GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { - if(StrEqual(FORBIDDEN_MODELS[i], buffer)) { - return false; - } + GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); + for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { + if(StrEqual(FORBIDDEN_MODELS[i], buffer)) { + return false; } } GetEntPropString(entity, Prop_Data, "m_iName", buffer, sizeof(buffer)); diff --git a/scripting/adminpanel.sp b/scripting/adminpanel.sp index 39bc5b8..9ad431e 100644 --- a/scripting/adminpanel.sp +++ b/scripting/adminpanel.sp @@ -31,6 +31,7 @@ public Plugin myinfo = int LIVESTATUS_VERSION = 0; Regex CommandArgRegex; +ConVar cvar_flags; ConVar cvar_debug; ConVar cvar_gamemode; char gamemode[32]; ConVar cvar_difficulty; int gameDifficulty; @@ -58,6 +59,7 @@ int pendingTries = 3; bool lateLoaded; Socket g_socket; +int g_lastPayloadSent; enum AuthState { Auth_Fail = -1, Auth_Pending, @@ -70,6 +72,10 @@ enum GameState { State_Hibernating = 2, State_NewGame = 3 } +enum PanelSettings { + Setting_None = 0, + Setting_DisableWithNoViewers = 1 +} GameState g_gameState; #define BUFFER_SIZE 2048 Buffer sendBuffer; @@ -87,6 +93,7 @@ public void OnPluginStart() { g_socket.SetOption(SocketSendBuffer, BUFFER_SIZE); uptime = GetTime(); + cvar_flags = CreateConVar("sm_adminpanel_flags", "1", "Bit Flags.\n1=Disable when no viewers", FCVAR_NONE, true, 0.0); cvar_debug = CreateConVar("sm_adminpanel_debug", "0", "Turn on debug mode", FCVAR_DONTRECORD, true, 0.0, true, 1.0); cvar_authToken = CreateConVar("sm_adminpanel_authtoken", "", "The token for authentication", FCVAR_PROTECTED); @@ -237,7 +244,7 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int int client = GetClientOfUserId(userid); if(client > 0 && StartPayload(true)) { PrintToServer("[AdminPanel] Sync requested for #%d, performing", userid); - AddPlayerRecord(client, Client_Normal); + AddPlayerRecord(client, Client_Connected); SendPayload(); } } else { @@ -519,6 +526,8 @@ Action Command_PanelDebug(int client, int args) { ConnectSocket(); } else if(StrEqual(arg, "info")) { ReplyToCommand(client, "Connected: %b\tAuthenticated: %d\tState: %d", g_socket.Connected, authState, g_gameState); + int timeFromLastPayload = GetTime() - g_lastPayloadSent; + ReplyToCommand(client, "Last Payload: %ds", timeFromLastPayload); ReplyToCommand(client, "#Viewers: %d\t#Players: %d", numberOfViewers, numberOfPlayers); ReplyToCommand(client, "Target Host: %s:%d", serverIp, serverPort); ReplyToCommand(client, "Buffer Size: %d", BUFFER_SIZE); @@ -617,7 +626,7 @@ void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { public void Event_PlayerToBot(Handle event, char[] name, bool dontBroadcast) { int player = GetClientOfUserId(GetEventInt(event, "player")); int bot = GetClientOfUserId(GetEventInt(event, "bot")); - if(player > 0 && !IsFakeClient(player) && StartPayload(true)) { + if(player > 0 && !IsFakeClient(player) && StartPayload()) { AddSurvivorRecord(player); SendPayload(); } @@ -626,7 +635,7 @@ public void Event_PlayerToBot(Handle event, char[] name, bool dontBroadcast) { public void Event_BotToPlayer(Handle event, char[] name, bool dontBroadcast) { int player = GetClientOfUserId(GetEventInt(event, "player")); int bot = GetClientOfUserId(GetEventInt(event, "bot")); - if(player > 0 && !IsFakeClient(player) && StartPayload(true)) { + if(player > 0 && !IsFakeClient(player) && StartPayload()) { // Bot is going away, remove it: (prob unnecessary OnClientDisconnect happens) AddPlayerRecord(bot, Client_Disconnected); AddPlayerRecord(player, Client_Normal); @@ -658,7 +667,7 @@ void Event_HealInterrupted(Event event, const char[] name, bool dontBroadcast) { void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { RecalculatePlayerCount(); int client = GetClientOfUserId(event.GetInt("userid")); - if(client > 0 && StartPayload()) { + if(client > 0 && StartPayload(true)) { AddPlayerRecord(client, Client_Normal); SendPayload(); } @@ -678,7 +687,7 @@ void SendPlayers() { for(int i = 1; i <= MaxClients; i++) { if(IsClientInGame(i)) { if(StartPayload(true)) { - AddPlayerRecord(i, Client_Normal); + AddPlayerRecord(i, Client_Connected); SendPayload(); } } @@ -805,15 +814,14 @@ Action Timer_UpdateItems(Handle h, int client) { void SendNewClient(int client) { if(!IsClientInGame(client)) return; - if(StartPayload()) { - PrintToServer("SendNewClient(%N)", client); + if(StartPayload(true)) { AddPlayerRecord(client, Client_Connected); SendPayload(); } } public void OnClientDisconnect(int client) { - if(StartPayload()) { + if(StartPayload(true)) { // hopefully userid is valid here? AddPlayerRecord(client, Client_Disconnected); SendPayload(); @@ -1024,7 +1032,7 @@ bool CanSendPayload(bool ignorePause = false) { } return false; } - if(!ignorePause && (numberOfViewers == 0 || numberOfPlayers == 0)) return false; + if(cvar_flags.IntValue & view_as(Setting_DisableWithNoViewers) && !ignorePause && (numberOfViewers == 0 || numberOfPlayers == 0)) return false; return true; } @@ -1209,6 +1217,7 @@ void SendPayload() { if(cvar_debug.BoolValue) { PrintToServer("[AdminPanel] Sending %d bytes of data (records = %s)", len, pendingRecords); } + g_lastPayloadSent = GetTime(); g_socket.Send(sendBuffer.buffer, len); } diff --git a/scripting/include/epi/director.sp b/scripting/include/epi/director.sp index 2ea94f1..c685f93 100644 --- a/scripting/include/epi/director.sp +++ b/scripting/include/epi/director.sp @@ -4,7 +4,7 @@ #define DIRECTOR_WITCH_MIN_TIME 120 // The minimum amount of time to pass since last witch spawn for the next extra witch to spawn #define DIRECTOR_WITCH_CHECK_TIME 30.0 // How often to check if a witch should be spawned #define DIRECTOR_WITCH_MAX_WITCHES 5 // The maximum amount of extra witches to spawn -#define DIRECTOR_WITCH_ROLLS 3 // The number of dice rolls, increase if you want to increase freq +#define DIRECTOR_WITCH_ROLLS 4 // The number of dice rolls, increase if you want to increase freq #define DIRECTOR_MIN_SPAWN_TIME 13.0 // Possibly randomized, per-special, in seconds ConVar directorSpawnChance; // Base chance of a special spawning, changed by player stress #define DIRECTOR_CHANGE_LIMIT_CHANCE 0.05 // The chance that the maximum amount per-special is changed diff --git a/scripting/include/l4d_info_editor.inc b/scripting/include/l4d_info_editor.inc new file mode 100644 index 0000000..14a1fcf --- /dev/null +++ b/scripting/include/l4d_info_editor.inc @@ -0,0 +1,94 @@ +/* +* Info Editor +* Copyright (C) 2022 Silvers +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#if defined _info_editor_included + #endinput +#endif +#define _info_editor_included + + + +public SharedPlugin __pl_info_editor_ = +{ + name = "info_editor", + file = "l4d_info_editor.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_info_editor__SetNTVOptional() +{ + MarkNativeAsOptional("InfoEditor_GetString"); + MarkNativeAsOptional("InfoEditor_SetString"); + MarkNativeAsOptional("InfoEditor_ReloadData"); +} +#endif + + + +/** + * Retrieves the value of a specified key from the games mission info keyvalue system. "N/A" is returned when not found. + * + * @param pThis Enter the pThis value from OnGetMissionInfo/OnGetWeaponsInfo. Can specify 0 when reading Mission data. + * @param keyname Key name to check. + * @param dest Destination string buffer to copy to. + * @param destLen Destination buffer length (includes null terminator). + * + * @noreturn + */ +native void InfoEditor_GetString(int pThis, const char[] keyname, char[] dest, int destLen); + +/** + * Sets the value of a specified key from the games mission info keyvalue system. + * + * @param pThis Enter the pThis value from OnGetMissionInfo/OnGetWeaponsInfo. Can specify 0 when writing Mission data. + * @param keyname Key name to set. + * @param value Value to set. + * @param create Optionally create the keyvalue if it doesn't exist. + * + * @noreturn + */ +native void InfoEditor_SetString(int pThis, const char[] keyname, const char[] value, bool create = false); + +/** + * Reloads the mission and weapons data configs and forces the game to reload them. + * + * @noreturn + */ +native void InfoEditor_ReloadData(); + + + +/** + * @brief Fired multiple times when the mission info data is parsed. + * + * @param pThis This pointer used for InfoEditor_GetString/InfoEditor_SetString. + */ +forward void OnGetMissionInfo(int pThis); + +/** + * Fired multiple times when the weapon info data is parsed for a specific weapon classname. + * + * @param pThis This pointer used for InfoEditor_GetString/InfoEditor_SetString. + * @param classname Classname of the weapon being parsed. + */ +forward void OnGetWeaponsInfo(int pThis, const char[] classname); \ No newline at end of file diff --git a/scripting/include/overlay.inc b/scripting/include/overlay.inc index b448d99..ebf2068 100644 --- a/scripting/include/overlay.inc +++ b/scripting/include/overlay.inc @@ -2,6 +2,7 @@ #endinput #endif #define _overlay_included +#include public SharedPlugin __pl_overlay = { name = "overlay", @@ -25,19 +26,39 @@ native bool IsOverlayConnected(); // myplugin:action_name // Handles any action for actionNamespace and actionName -native void RegisterActionHandler(const char[] actionNamespace, const char[] actionName, ActionFallbackHandlerCallback cb); +native void RegisterActionHandler(const char[] actionNamespace, const char[] commandName, ActionFallbackHandlerCallback cb); // Handles all actions for namespace that were not caught by RegisterActionHandler native void RegisterActionAnyHandler(const char[] actionNamespace, ActionHandlerCallback cb); +enum struct ClientAction { + char steamid[32]; + char ns[64]; + char instanceId[64]; + char command[128]; + char input[512]; +} + +// Utility to get arguments from an action input. methodmap UIActionEvent { public UIActionEvent(ArrayList list) { return view_as(list); } + // 1 indexed. 0 returns full action string public void GetArg(int argNum, char[] output, int maxlen) { view_as(this).GetString(argNum, output, maxlen); } - + public int GetArgInt(int argNum) { + char buffer[32]; + this.GetArg(argNum, buffer, sizeof(buffer)); + return StringToInt(buffer); + } + public float GetArgFloat(int argNum) { + char buffer[32]; + this.GetArg(argNum, buffer, sizeof(buffer)); + return StringToFloat(buffer); + } + public void _Delete() { delete view_as(this); } @@ -48,10 +69,11 @@ methodmap UIActionEvent { } methodmap UIElement < JSONObject { - public UIElement(const char[] elemNamespace, const char[] elemId) { + public UIElement(const char[] elemNamespace, const char[] templateId, const char[] instanceId) { JSONObject obj = new JSONObject(); obj.SetString("namespace", elemNamespace); - obj.SetString("elem_id", elemId); + obj.SetString("instance_id", instanceId); + obj.SetString("template_id", templateId); obj.SetBool("visibility", false); obj.Set("steamids", new JSONArray()); obj.Set("variables", new JSONObject()); @@ -68,23 +90,31 @@ methodmap UIElement < JSONObject { } } - public void SetVariable(const char[] id, JSON json) { + public void GetTemplateId(char[] buffer, int maxlen) { + view_as(this).GetString("template_id", buffer, maxlen); + } + + public void GetInstanceId(char[] buffer, int maxlen) { + view_as(this).GetString("instance_id", buffer, maxlen); + } + + public void SetVar(const char[] id, JSON json) { view_as(this).Set(id, json); } - public void SetVariableInt(const char[] id, int value) { + public void SetVarInt(const char[] id, int value) { view_as(this).SetInt(id, value); } - public void SetVariableFloat(const char[] id, float value) { + public void SetVarFloat(const char[] id, float value) { view_as(this).SetFloat(id, value); } - public void SetVariableString(const char[] id, const char[] value) { + public void SetVarString(const char[] id, const char[] value) { view_as(this).SetString(id, value); } - public void SetVariableBool(const char[] id, bool value) { + public void SetVarBool(const char[] id, bool value) { view_as(this).SetBool(id, value); } @@ -376,6 +406,7 @@ enum AudioState { Audio_Play } +// List of clients to send an element to. If empty, it will default to all connected clients. methodmap ClientList < JSONArray { public ClientList() { return view_as(new JSONArray()); @@ -449,6 +480,8 @@ methodmap AudioResource < JSONObject { } } +native int FindClientBySteamId2(const char[] steamid); + #if !defined REQUIRE_PLUGIN public void __pl_overlay_SetNTVOptional() { MarkNativeAsOptional("IsOverlayConnected"); @@ -462,5 +495,7 @@ public void __pl_overlay_SetNTVOptional() { MarkNativeAsOptional("AudioResource.Play"); MarkNativeAsOptional("AudioResource.Stop"); MarkNativeAsOptional("AudioResource.Pause"); + + MarkNativeAsOptional("FindClientBySteamId2"); } #endif \ No newline at end of file diff --git a/scripting/include/randomizer/rbuild.sp b/scripting/include/randomizer/rbuild.sp index 2970678..082ac36 100644 --- a/scripting/include/randomizer/rbuild.sp +++ b/scripting/include/randomizer/rbuild.sp @@ -37,7 +37,14 @@ void OpenVariantsMenu(int client) { char id[8], display[32]; menu.AddItem("new", "New Variant"); menu.AddItem("-1", "Global Scene Variant"); - + if(!g_builder.selectedSceneData) { + ReplyToCommand(client, "Error: Missing scene data for %s", g_builder.selectedSceneId); + PrintToServer("[Randomizer] Warn: Scene %s has no data", g_builder.selectedSceneId); + } else if(!g_builder.selectedSceneData.HasKey("variants")) { + ReplyToCommand(client, "Error: Missing variants for %d", g_builder.selectedSceneId); + PrintToServer("[Randomizer] Warn: Selected scene %s has no variants", g_builder.selectedSceneId); + return; + } JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); JSONObject varObj; JSONArray entities; @@ -102,8 +109,11 @@ int BuilderHandler_ScenesMenu(Menu menu, MenuAction action, int client, int para FakeClientCommand(client, "sm_rbuild scenes new"); OpenScenesMenu(client); } else { - FakeClientCommand(client, "sm_rbuild scenes select %s", info); - OpenVariantsMenu(client); + if(g_builder.SelectScene(info)) { + OpenVariantsMenu(client); + } else { + PrintToChat(client, "Error: Scene not found"); + } } } else if(action == MenuAction_Cancel) { if(param2 == MenuCancel_ExitBack) { diff --git a/scripting/l4d2_hats.sp b/scripting/l4d2_hats.sp index 137ae88..9e761d1 100644 --- a/scripting/l4d2_hats.sp +++ b/scripting/l4d2_hats.sp @@ -10,7 +10,6 @@ static float EMPTY_ANG[3] = { 0.0, 0.0, 0.0 }; #include #include -#include #include #include #include @@ -399,35 +398,6 @@ public void Event_HatsEnableChanged(ConVar convar, const char[] sOldValue, const } } -ArrayList GetSpawnLocations() { - ArrayList list = new ArrayList(); - ArrayList newList = new ArrayList(); - L4D_GetAllNavAreas(list); - for(int i = 0; i < list.Length; i++) { - Address nav = list.Get(i); - if(L4D_GetNavArea_SpawnAttributes(nav) & NAV_SPAWN_THREAT) { - newList.Push(nav); - } - } - delete list; - PrintToServer("[Hats] Got %d valid locations", newList.Length); - return newList; -} - - -void ChooseRandomPosition(float pos[3], int ignoreClient = 0) { - if(NavAreas.Length > 0 && GetURandomFloat() > 0.5) { - int nav = NavAreas.Get(GetURandomInt() % (NavAreas.Length - 1)); - L4D_FindRandomSpot(nav, pos); - } else { - int survivor = GetRandomClient(5, 1); - if(ignoreClient > 0 && survivor == ignoreClient) survivor = GetRandomClient(5, 1); - if(survivor > 0) { - GetClientAbsOrigin(survivor, pos); - } - } -} - public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) { float tick = GetGameTime(); ////////////////////////////// @@ -492,12 +462,12 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 EquipHat(client, entity); } - // If bot is commandable and reversed (player reverse-hat common/survivor), change position: - if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { - float pos[3]; - ChooseRandomPosition(pos, client); - L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); - } + // // If bot is commandable and reversed (player reverse-hat common/survivor), change position: + // if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { + // float pos[3]; + // ChooseRandomPosition(pos, client); + // L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); + // } } // Detect E + R to offset hat or place down if(buttons & IN_USE && buttons & IN_RELOAD) { @@ -682,12 +652,10 @@ bool CheckBlacklist(int entity) { return false; } } - if(StrContains(buffer, "prop_") > -1) { - GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { - if(StrEqual(FORBIDDEN_MODELS[i], buffer)) { - return false; - } + GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); + for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { + if(StrEqual(FORBIDDEN_MODELS[i], buffer)) { + return false; } } GetEntPropString(entity, Prop_Data, "m_iName", buffer, sizeof(buffer)); @@ -726,16 +694,6 @@ stock bool FindGround(const float start[3], float end[3]) { return true; } -stock bool L4D_IsPlayerCapped(int client) { - if(GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0) - return true; - return false; -} stock void LookAtPoint(int entity, const float destination[3]){ float angles[3], pos[3], result[3]; GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); diff --git a/scripting/l4d2_randomizer.sp b/scripting/l4d2_randomizer.sp index b7b6ac6..26038fc 100644 --- a/scripting/l4d2_randomizer.sp +++ b/scripting/l4d2_randomizer.sp @@ -226,6 +226,10 @@ public Action Command_CycleRandom(int client, int args) { if(client > 0) PrintCenterText(client, "Cycled flags=%d", flags); } else { + if(g_MapData.activeScenes == null) { + ReplyToCommand(client, "No map data"); + return Plugin_Handled; + } ReplyToCommand(client, "Active Scenes (%d/%d):", g_MapData.activeScenes.Length, g_MapData.scenes.Length); ActiveSceneData scene; for(int i = 0; i < g_MapData.activeScenes.Length; i++) { @@ -389,6 +393,18 @@ Action Command_RandomizerBuild(int client, int args) { obj.SetString("model", "decals/checkpointarrow01_black.vmt"); g_builder.AddEntityData(obj); ReplyToCommand(client, "Added sprite to variant #%d", g_builder.selectedVariantIndex); + } else if(StrEqual(arg, "fire")) { + if(g_builder.selectedVariantData == null) { + ReplyToCommand(client, "Please load map data, select a scene and a variant."); + return Plugin_Handled; + } + float pos[3]; + GetLookingPosition(client, Filter_IgnorePlayer, pos); + JSONObject obj = new JSONObject(); + obj.SetString("type", "env_fire"); + obj.Set("origin", VecToArray(pos)); + g_builder.AddEntityData(obj); + ReplyToCommand(client, "Added fire to variant #%d", g_builder.selectedVariantIndex); } else { ReplyToCommand(client, "Unknown arg. Try: new, load, save, scenes, cursor"); } diff --git a/scripting/sm_overlay.sp b/scripting/sm_overlay.sp index baf0390..ba71fc3 100644 --- a/scripting/sm_overlay.sp +++ b/scripting/sm_overlay.sp @@ -66,6 +66,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("UIElement.SendTo", Native_UpdateUI); CreateNative("TempUI.SendAll", Native_UpdateTempUI); CreateNative("TempUI.SendTo", Native_UpdateTempUI); + + CreateNative("FindClientBySteamId2", Native_FindClientBySteamId2); return APLRes_Success; } @@ -128,8 +130,11 @@ Action Command_Overlay(int client, int args) { ReplyToCommand(client, "URL: %s", managerUrl); ReplyToCommand(client, "Socket Connected: %b | WS Connected: %b", g_ws.SocketOpen(), g_ws.WsOpen()); ReplyToCommand(client, "Auth State: %d", g_authState); + } else if(StrEqual(arg, "players")) { + SendAllPlayers(); } else if(StrEqual(arg, "test")) { SendAllPlayers(); + // TODO: server can send: steamids[], steamid, or none (manager knows who was connected) JSONObject temp = new JSONObject(); temp.SetString("type", "text"); @@ -276,38 +281,45 @@ stock int ExplodeStringToArrayList(const char[] text, const char[] split, ArrayL } void OnAction(JSONObject obj) { - char steamid[32]; - obj.GetString("steamid", steamid, sizeof(steamid)); - char ns[64]; - obj.GetString("namespace", ns, sizeof(ns)); - char id[64]; - obj.GetString("elem_id", id, sizeof(id)); - char action[256]; - obj.GetString("action", action, sizeof(action)); + ClientAction action; + obj.GetString("steamid", action.steamid, sizeof(action.steamid)); + obj.GetString("namespace", action.ns, sizeof(action.ns)); + obj.GetString("instance_id", action.instanceId, sizeof(action.instanceId)); + obj.GetString("command", action.command, sizeof(action.command)); + if(obj.HasKey("input")) + obj.GetString("input", action.input, sizeof(action.input)); - int client = FindClientBySteamId2(steamid); + int client = FindClientBySteamId2(action.steamid); + if(client <= 0) return; StringMap nsHandler; PrivateForward fwd; - if(!actionNamespaceHandlers.GetValue(ns, nsHandler) || !nsHandler.GetValue(id, fwd)) { - if(!actionFallbackHandlers.GetValue(ns, fwd)) { + if(!actionNamespaceHandlers.GetValue(action.ns, nsHandler) || !nsHandler.GetValue(action.command, fwd)) { + if(!actionFallbackHandlers.GetValue(action.ns, fwd)) { // No handler or catch all namespace handler + PrintToServer("[Overlay] Warn: No handler found for action \"%s:%s\"", action.ns, action.command); return; } } ArrayList args = new ArrayList(ACTION_ARG_LENGTH); - ExplodeStringToArrayList(action, " ", args, ACTION_ARG_LENGTH); + args.PushString(action.input); + ExplodeStringToArrayList(action.input, " ", args, ACTION_ARG_LENGTH); UIActionEvent event = UIActionEvent(args); Call_StartForward(fwd); Call_PushCell(event); Call_PushCell(client); Call_Finish(); + + if(StrEqual(action.ns, "game")) { + if(CheckCommandAccess(client, action.command, 0)) { + FakeClientCommand(client, "%s %s", action.command, action.input); + } + } event._Delete(); } - -int FindClientBySteamId2(const char[] steamid) { +int _FindClientBySteamId2(const char[] steamid) { for(int i = 1; i <= MaxClients; i++) { if(StrEqual(steamidCache[i], steamid)) { return i; @@ -316,7 +328,6 @@ int FindClientBySteamId2(const char[] steamid) { return -1; } - bool ConnectManager() { DisconnectManager(); if(authToken[0] == '\0') return false; @@ -536,4 +547,11 @@ any Native_ActionHandler(Handle plugin, int numParams) { actionFallbackHandlers.SetValue(ns, fwd); } return 1; +} + + +any Native_FindClientBySteamId2(Handle plugin, int numParams) { + char steamid[32]; + GetNativeString(1, steamid, sizeof(steamid)); + return _FindClientBySteamId2(steamid); } \ No newline at end of file