From 6268e9ed197579b1d62a2bcaf1139f82e89d9397 Mon Sep 17 00:00:00 2001 From: Irineu Date: Thu, 24 Sep 2026 01:40:23 -0300 Subject: [PATCH 1/3] Repurpose g_aSpawnedProps, and remove some no longer needed stuff --- PropSpawner/scripting/PropSpawner.sp | 46 +++------------------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/PropSpawner/scripting/PropSpawner.sp b/PropSpawner/scripting/PropSpawner.sp index 42fcfa1..c67e43d 100644 --- a/PropSpawner/scripting/PropSpawner.sp +++ b/PropSpawner/scripting/PropSpawner.sp @@ -27,7 +27,7 @@ ConVar g_cvNotifyShot; ConVar g_cvNotifyTimer; ConVar g_cvUseMode; -ArrayList g_aSpawnedProps; // Tracks entity indices of props spawned by this plugin so we can unhook them +ArrayList g_aSpawnedProps; // Tracks entity indices of props spawned by this plugin ArrayList g_aNotifyList; int g_aPlayerPropColor[MAXPLAYERS + 1][3]; @@ -142,8 +142,6 @@ public void OnPluginStart() g_aSpawnedProps = new ArrayList(); g_aNotifyList = new ArrayList(); - - HookEvent("round_end", Event_RoundEnd); } public void OnEntityCreated(int entity, const char[] classname) @@ -154,8 +152,6 @@ public void OnEntityCreated(int entity, const char[] classname) public void OnEntityDestroyed(int entity) { - // Safety net: if a tracked prop is removed by any means other than DeleteProp() - // (e.g. an explosion, another plugin, map cleanup), stop tracking it here too. if (entity <= 0 || g_aSpawnedProps == null) return; @@ -166,27 +162,6 @@ public void OnEntityDestroyed(int entity) } } -public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) -{ - UnhookAllSpawnedProps(); -} - -void UnhookAllSpawnedProps() -{ - if (g_aSpawnedProps == null || !g_cvNotifyShot.BoolValue) - return; - - for (int i = 0; i < g_aSpawnedProps.Length; i++) - { - int prop = g_aSpawnedProps.Get(i); - if (IsValidEntity(prop)) - { - SDKUnhook(prop, SDKHook_OnTakeDamage, OnPropTakeDamage); - } - } - g_aSpawnedProps.Clear(); -} - public void OnMapStart() { g_Models.Rewind(); @@ -226,8 +201,6 @@ public void OnMapEnd() delete(g_NpcMenu); g_NpcMenu = null; } - - UnhookAllSpawnedProps(); } /* ----------------------------------- */ @@ -545,15 +518,6 @@ void DeleteProp(int client){ GetEdictClassname(DeleteIndex, classname, sizeof(classname)); if(isAProp(classname)) { - if (g_cvNotifyShot.BoolValue) - { - int propIndex = g_aSpawnedProps.FindValue(DeleteIndex); - if (propIndex != -1) - { - SDKUnhook(DeleteIndex, SDKHook_OnTakeDamage, OnPropTakeDamage); - g_aSpawnedProps.Erase(propIndex); - } - } AcceptEntityInput(DeleteIndex, "Kill", -1, -1, 0); ShowActivity2(client, "\x01[SM] \x04", "deleted a prop."); LogAction(client, -1, "\"%L\" deleted a prop.", client); @@ -719,15 +683,16 @@ void prop_any_create(int client) AcceptEntityInput(prop, "TurnOn", prop, prop, 0); AcceptEntityInput(prop, "EnableCollision"); - // Hook the prop so we can detect when it gets shot/damaged, and track it so we can unhook later + // Hook the prop so we can detect when it gets shot/damaged if (g_cvNotifyShot.BoolValue) { SetEntityFlags(prop, FL_CLIENT); //Now youre thinking with portals SetEntProp(prop, Prop_Data, "m_iTeamNum", CS_TEAM_CT); // So it works with filtered triggers SDKHook(prop, SDKHook_OnTakeDamage, OnPropTakeDamage); - g_aSpawnedProps.Push(prop); } + g_aSpawnedProps.Push(prop); + SetEntityRenderMode(prop, RENDER_TRANSCOLOR); if (g_aPlayerPropColor[client][0] == 256) // Rainbow { @@ -932,7 +897,6 @@ MRESReturn PlayerUse_Hook(int pThis) { int entHit = TR_GetEntityIndex(tr); - // This currently only works if sm_propspawner_notify_shot is enabled if (g_aSpawnedProps.FindValue(entHit) != -1) { switch (g_cvUseMode.IntValue) @@ -981,11 +945,9 @@ bool UsePushFilter(int entity, int contentsMask) return true; } -// No convar for this one rn, didn't find it necessary MRESReturn ForceVPhysicsCollide_Hook(int pThis, DHookReturn hReturn, DHookParam hParams) { int ent = hParams.Get(1); - // Also only works with sm_propspawner_notify_shot 1 cause of this if (g_aSpawnedProps.FindValue(ent) != -1) { hReturn.Value = false; -- 2.47.3 From b7b7828f6559efc288a6d7a91171fc62e828e6b6 Mon Sep 17 00:00:00 2001 From: Irineu Date: Sun, 27 Sep 2026 07:07:15 -0300 Subject: [PATCH 2/3] Limited notify hud msg to 80 chars, limited it to once per frame and added a 2nd mode to sm_propspawner_notify_shot that will send the message to console instead --- PropSpawner/scripting/PropSpawner.sp | 70 +++++++++++++++++++--------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/PropSpawner/scripting/PropSpawner.sp b/PropSpawner/scripting/PropSpawner.sp index c67e43d..0866ff6 100644 --- a/PropSpawner/scripting/PropSpawner.sp +++ b/PropSpawner/scripting/PropSpawner.sp @@ -134,7 +134,7 @@ public void OnPluginStart() RegAdminCmd("sm_propspawner", Command_MainMenu, ADMFLAG_CHANGEMAP); //g RegAdminCmd("sm_setpropcolor", Command_SetPropColor, ADMFLAG_CHANGEMAP, "Set PropSpawner color by RGB value, or \"rainbow\" for that."); - g_cvNotifyShot = CreateConVar("sm_propspawner_notify_shot", "0", "If set to 1, announces in chat when a plugin-spawned prop is shot.", FCVAR_NOTIFY, true, 0.0, true, 1.0); + g_cvNotifyShot = CreateConVar("sm_propspawner_notify_shot", "0", "If set to 1, announces in chat when a plugin-spawned prop is shot.", FCVAR_NOTIFY, true, 0.0, true, 2.0); g_cvNotifyTimer = CreateConVar("sm_propspawner_notify_shot_timer", "0.5", "How long a player stays in the \"shooting couch\" list."); g_cvUseMode = CreateConVar("sm_propspawner_use_mode", "0", "What should happen when a prop is +use'd. " ... "0 = Normal. 1 = Disallow CTs moving plugin spawned props. 2 = Add player to notify list."); @@ -724,18 +724,16 @@ public Action OnPropTakeDamage(int victim, int &attacker, int &inflictor, float return Plugin_Continue; } +char g_szNotifyText[80]; + void DisplayNotifyList() { - static int channel = -1; - static float lastDisplayTime = 0.0; + if (g_cvNotifyShot.IntValue == 2) + return; - // Change the channel every once in a while, to avoid conflicts with the map's game_text entities - // (not really sure if sourcemod accounts for them when using -1 as channel but hopefully it does) - if ((GetEngineTime() - lastDisplayTime) > g_cvNotifyTimer.FloatValue) - channel = -1; + static int tick = 0; - char text[255]; - strcopy(text, sizeof(text), "Shooting Couch:\n"); + strcopy(g_szNotifyText, sizeof(g_szNotifyText), "Shooting Couch:\n"); for (int i = g_aNotifyList.Length - 1; i >= 0; i--) { @@ -753,18 +751,16 @@ void DisplayNotifyList() char clientName[32]; GetClientName(buf.client, clientName, sizeof(clientName)); - StrCat(text, sizeof(text), clientName); - StrCat(text, sizeof(text), "\n"); + StrCat(g_szNotifyText, sizeof(g_szNotifyText), clientName); + StrCat(g_szNotifyText, sizeof(g_szNotifyText), "\n"); } - SetHudTextParams(0.8, 0.2, g_cvNotifyTimer.FloatValue, 255, 255, 255, 255, 0, 0.0, 0.0, 0.0); - for (int i = 1; i <= MaxClients; i++) + if (tick != GetGameTickCount()) { - if (IsClientInGame(i)) - channel = ShowHudText(i, channel, text); + // Wait a frame since other players might still shoot the couch this frame + RequestFrame(DisplayNotifyList2); + tick = GetGameTickCount(); } - - lastDisplayTime = GetEngineTime(); } void OnNotifyTimer(Handle timer, any data) @@ -830,6 +826,15 @@ void AddToNotifyList(int client) if (GetClientTeam(client) != CS_TEAM_CT) return; + // Just in case the buffer overflow in net message issue doesn't go away with the other fixes + if (g_cvNotifyShot.IntValue == 2) + { + char clientName[32]; + GetClientName(client, clientName, sizeof(clientName)); + PrintToConsoleAll("[SM] %s is shooting the couch.", clientName); + return; + } + Handle timer = CreateTimer(g_cvNotifyTimer.FloatValue, OnNotifyTimer, client); int idx = FindInNotifyList(client); if (idx == -1) @@ -897,7 +902,7 @@ MRESReturn PlayerUse_Hook(int pThis) { int entHit = TR_GetEntityIndex(tr); - if (g_aSpawnedProps.FindValue(entHit) != -1) + if (GetEntityMoveType(entHit) == MOVETYPE_VPHYSICS && g_aSpawnedProps.FindValue(entHit) != -1) { switch (g_cvUseMode.IntValue) { @@ -911,8 +916,11 @@ MRESReturn PlayerUse_Hook(int pThis) // Add to the shooting couch list case 2: { - AddToNotifyList(pThis); - DisplayNotifyList(); + if (g_cvNotifyShot.BoolValue) + { + AddToNotifyList(pThis); + DisplayNotifyList(); + } } } } @@ -948,7 +956,7 @@ bool UsePushFilter(int entity, int contentsMask) MRESReturn ForceVPhysicsCollide_Hook(int pThis, DHookReturn hReturn, DHookParam hParams) { int ent = hParams.Get(1); - if (g_aSpawnedProps.FindValue(ent) != -1) + if (GetEntityMoveType(ent) == MOVETYPE_VPHYSICS && g_aSpawnedProps.FindValue(ent) != -1) { hReturn.Value = false; return MRES_Supercede; @@ -956,3 +964,23 @@ MRESReturn ForceVPhysicsCollide_Hook(int pThis, DHookReturn hReturn, DHookParam return MRES_Ignored; } + +void DisplayNotifyList2() +{ + static int channel = -1; + static float lastDisplayTime = 0.0; + + // Change the channel every once in a while, to avoid conflicts with the map's game_text entities + // (not really sure if sourcemod accounts for them when using -1 as channel but hopefully it does) + if ((GetEngineTime() - lastDisplayTime) > (g_cvNotifyTimer.FloatValue * 2.0)) + channel = -1; + + SetHudTextParams(0.8, 0.2, g_cvNotifyTimer.FloatValue, 255, 255, 255, 255, 0, 0.0, 0.0, 0.0); + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i)) + channel = ShowHudText(i, channel, g_szNotifyText); + } + + lastDisplayTime = GetEngineTime(); +} -- 2.47.3 From 9c1a8846430af028a9503bc5ac7cfdc1378c059c Mon Sep 17 00:00:00 2001 From: Irineu Date: Mon, 28 Sep 2026 04:15:19 -0300 Subject: [PATCH 3/3] Added sm_propspawner_tier_requirement and sm_propspawner_button_tier_requirement --- PropSpawner/scripting/PropSpawner.sp | 40 ++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/PropSpawner/scripting/PropSpawner.sp b/PropSpawner/scripting/PropSpawner.sp index 0866ff6..d1031e0 100644 --- a/PropSpawner/scripting/PropSpawner.sp +++ b/PropSpawner/scripting/PropSpawner.sp @@ -4,6 +4,7 @@ #include #include #include +#include public Plugin myinfo = { @@ -26,6 +27,8 @@ KeyValues g_Models; ConVar g_cvNotifyShot; ConVar g_cvNotifyTimer; ConVar g_cvUseMode; +ConVar g_cvPropShootTier; +ConVar g_cvButtonPressTier; ArrayList g_aSpawnedProps; // Tracks entity indices of props spawned by this plugin ArrayList g_aNotifyList; @@ -138,6 +141,8 @@ public void OnPluginStart() g_cvNotifyTimer = CreateConVar("sm_propspawner_notify_shot_timer", "0.5", "How long a player stays in the \"shooting couch\" list."); g_cvUseMode = CreateConVar("sm_propspawner_use_mode", "0", "What should happen when a prop is +use'd. " ... "0 = Normal. 1 = Disallow CTs moving plugin spawned props. 2 = Add player to notify list."); + g_cvPropShootTier = CreateConVar("sm_propspawner_tier_requirement", "-1", "Tier needed for players to be able to move PropSpawner props."); + g_cvButtonPressTier = CreateConVar("sm_propspawner_button_tier_requirement", "-1", "Tier needed for players to be able to press buttons."); AutoExecConfig(true, "PropSpawner"); g_aSpawnedProps = new ArrayList(); @@ -147,7 +152,13 @@ public void OnPluginStart() public void OnEntityCreated(int entity, const char[] classname) { if (StrEqual(classname, "func_clip_vphysics") && g_ForceVPhysicsCollideSetup != INVALID_HANDLE) + { DHookEntity(g_ForceVPhysicsCollideSetup, false, entity); + } + else if (StrEqual(classname, "func_button") && g_cvButtonPressTier.IntValue > -1) + { + SDKHook(entity, SDKHook_Use, OnButtonUse); + } } public void OnEntityDestroyed(int entity) @@ -684,7 +695,7 @@ void prop_any_create(int client) AcceptEntityInput(prop, "EnableCollision"); // Hook the prop so we can detect when it gets shot/damaged - if (g_cvNotifyShot.BoolValue) + if (g_cvNotifyShot.BoolValue || g_cvPropShootTier.IntValue > -1) { SetEntityFlags(prop, FL_CLIENT); //Now youre thinking with portals SetEntProp(prop, Prop_Data, "m_iTeamNum", CS_TEAM_CT); // So it works with filtered triggers @@ -712,10 +723,13 @@ void prop_any_create(int client) public Action OnPropTakeDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { - if (!g_cvNotifyShot.BoolValue) + if (attacker < 1 || attacker > MaxClients || !IsClientInGame(attacker)) return Plugin_Continue; - if (attacker < 1 || attacker > MaxClients || !IsClientInGame(attacker)) + if (GetClientTeam(client) == CS_TEAM_CT && GetPlayerTier_native(attacker) < g_cvPropShootTier.IntValue) + return Plugin_Handled; + + if (!g_cvNotifyShot.BoolValue) return Plugin_Continue; AddToNotifyList(attacker); @@ -874,7 +888,7 @@ MRESReturn PlayerUse_Hook(int pThis) if (!sv_turbophysics.BoolValue) return MRES_Ignored; - if (g_cvUseMode.IntValue != 1 && g_cvUseMode.IntValue != 2) + if (g_cvUseMode.IntValue != 1 && g_cvUseMode.IntValue != 2 && g_cvPropShootTier.IntValue <= -1) return MRES_Ignored; // ZMs can always push it @@ -904,7 +918,11 @@ MRESReturn PlayerUse_Hook(int pThis) if (GetEntityMoveType(entHit) == MOVETYPE_VPHYSICS && g_aSpawnedProps.FindValue(entHit) != -1) { - switch (g_cvUseMode.IntValue) + int mode = g_cvUseMode.IntValue; + if (GetPlayerTier_native(pThis) < g_cvPropShootTier.IntValue) + mode = 1; + + switch (mode) { // Prevent the prop from being moved case 1: @@ -984,3 +1002,15 @@ void DisplayNotifyList2() lastDisplayTime = GetEngineTime(); } + +// To prevent people from early triggering on couch events. Not every map uses buttons for triggers however. +Action OnButtonUse(int entity, int activator, int caller, UseType type, float value) +{ + if (activator < 1 || activator > MaxClients || !IsClientInGame(activator)) + return Plugin_Continue; + + if (GetClientTeam(client) == CS_TEAM_CT && GetPlayerTier_native(activator) < g_cvButtonPressTier.IntValue) + return Plugin_Handled; + + return Plugin_Continue; +} -- 2.47.3