Merge pull request 'More PropSpawner changes' (#6) from Irineu/sm-plugins:master into master

Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
2026-09-28 09:35:23 +02:00
+88 -68
View File
@@ -4,6 +4,7 @@
#include <sdkhooks> #include <sdkhooks>
#include <dhooks> #include <dhooks>
#include <cstrike> #include <cstrike>
#include <unloze_playtime>
public Plugin myinfo = public Plugin myinfo =
{ {
@@ -26,8 +27,10 @@ KeyValues g_Models;
ConVar g_cvNotifyShot; ConVar g_cvNotifyShot;
ConVar g_cvNotifyTimer; ConVar g_cvNotifyTimer;
ConVar g_cvUseMode; ConVar g_cvUseMode;
ConVar g_cvPropShootTier;
ConVar g_cvButtonPressTier;
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; ArrayList g_aNotifyList;
int g_aPlayerPropColor[MAXPLAYERS + 1][3]; int g_aPlayerPropColor[MAXPLAYERS + 1][3];
@@ -134,28 +137,32 @@ public void OnPluginStart()
RegAdminCmd("sm_propspawner", Command_MainMenu, ADMFLAG_CHANGEMAP); //g 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."); 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_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. " ... 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."); "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"); AutoExecConfig(true, "PropSpawner");
g_aSpawnedProps = new ArrayList(); g_aSpawnedProps = new ArrayList();
g_aNotifyList = new ArrayList(); g_aNotifyList = new ArrayList();
HookEvent("round_end", Event_RoundEnd);
} }
public void OnEntityCreated(int entity, const char[] classname) public void OnEntityCreated(int entity, const char[] classname)
{ {
if (StrEqual(classname, "func_clip_vphysics") && g_ForceVPhysicsCollideSetup != INVALID_HANDLE) if (StrEqual(classname, "func_clip_vphysics") && g_ForceVPhysicsCollideSetup != INVALID_HANDLE)
{
DHookEntity(g_ForceVPhysicsCollideSetup, false, entity); 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) 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) if (entity <= 0 || g_aSpawnedProps == null)
return; return;
@@ -166,27 +173,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() public void OnMapStart()
{ {
g_Models.Rewind(); g_Models.Rewind();
@@ -226,8 +212,6 @@ public void OnMapEnd()
delete(g_NpcMenu); delete(g_NpcMenu);
g_NpcMenu = null; g_NpcMenu = null;
} }
UnhookAllSpawnedProps();
} }
/* ----------------------------------- */ /* ----------------------------------- */
@@ -545,15 +529,6 @@ void DeleteProp(int client){
GetEdictClassname(DeleteIndex, classname, sizeof(classname)); GetEdictClassname(DeleteIndex, classname, sizeof(classname));
if(isAProp(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); AcceptEntityInput(DeleteIndex, "Kill", -1, -1, 0);
ShowActivity2(client, "\x01[SM] \x04", "deleted a prop."); ShowActivity2(client, "\x01[SM] \x04", "deleted a prop.");
LogAction(client, -1, "\"%L\" deleted a prop.", client); LogAction(client, -1, "\"%L\" deleted a prop.", client);
@@ -719,15 +694,16 @@ void prop_any_create(int client)
AcceptEntityInput(prop, "TurnOn", prop, prop, 0); AcceptEntityInput(prop, "TurnOn", prop, prop, 0);
AcceptEntityInput(prop, "EnableCollision"); 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) if (g_cvNotifyShot.BoolValue || g_cvPropShootTier.IntValue > -1)
{ {
SetEntityFlags(prop, FL_CLIENT); //Now youre thinking with portals SetEntityFlags(prop, FL_CLIENT); //Now youre thinking with portals
SetEntProp(prop, Prop_Data, "m_iTeamNum", CS_TEAM_CT); // So it works with filtered triggers SetEntProp(prop, Prop_Data, "m_iTeamNum", CS_TEAM_CT); // So it works with filtered triggers
SDKHook(prop, SDKHook_OnTakeDamage, OnPropTakeDamage); SDKHook(prop, SDKHook_OnTakeDamage, OnPropTakeDamage);
g_aSpawnedProps.Push(prop);
} }
g_aSpawnedProps.Push(prop);
SetEntityRenderMode(prop, RENDER_TRANSCOLOR); SetEntityRenderMode(prop, RENDER_TRANSCOLOR);
if (g_aPlayerPropColor[client][0] == 256) // Rainbow if (g_aPlayerPropColor[client][0] == 256) // Rainbow
{ {
@@ -747,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) 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; 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; return Plugin_Continue;
AddToNotifyList(attacker); AddToNotifyList(attacker);
@@ -759,18 +738,16 @@ public Action OnPropTakeDamage(int victim, int &attacker, int &inflictor, float
return Plugin_Continue; return Plugin_Continue;
} }
char g_szNotifyText[80];
void DisplayNotifyList() void DisplayNotifyList()
{ {
static int channel = -1; if (g_cvNotifyShot.IntValue == 2)
static float lastDisplayTime = 0.0; return;
// Change the channel every once in a while, to avoid conflicts with the map's game_text entities static int tick = 0;
// (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;
char text[255]; strcopy(g_szNotifyText, sizeof(g_szNotifyText), "Shooting Couch:\n");
strcopy(text, sizeof(text), "Shooting Couch:\n");
for (int i = g_aNotifyList.Length - 1; i >= 0; i--) for (int i = g_aNotifyList.Length - 1; i >= 0; i--)
{ {
@@ -788,18 +765,16 @@ void DisplayNotifyList()
char clientName[32]; char clientName[32];
GetClientName(buf.client, clientName, sizeof(clientName)); GetClientName(buf.client, clientName, sizeof(clientName));
StrCat(text, sizeof(text), clientName); StrCat(g_szNotifyText, sizeof(g_szNotifyText), clientName);
StrCat(text, sizeof(text), "\n"); 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); if (tick != GetGameTickCount())
for (int i = 1; i <= MaxClients; i++)
{ {
if (IsClientInGame(i)) // Wait a frame since other players might still shoot the couch this frame
channel = ShowHudText(i, channel, text); RequestFrame(DisplayNotifyList2);
tick = GetGameTickCount();
} }
lastDisplayTime = GetEngineTime();
} }
void OnNotifyTimer(Handle timer, any data) void OnNotifyTimer(Handle timer, any data)
@@ -865,6 +840,15 @@ void AddToNotifyList(int client)
if (GetClientTeam(client) != CS_TEAM_CT) if (GetClientTeam(client) != CS_TEAM_CT)
return; 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); Handle timer = CreateTimer(g_cvNotifyTimer.FloatValue, OnNotifyTimer, client);
int idx = FindInNotifyList(client); int idx = FindInNotifyList(client);
if (idx == -1) if (idx == -1)
@@ -904,7 +888,7 @@ MRESReturn PlayerUse_Hook(int pThis)
if (!sv_turbophysics.BoolValue) if (!sv_turbophysics.BoolValue)
return MRES_Ignored; 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; return MRES_Ignored;
// ZMs can always push it // ZMs can always push it
@@ -932,10 +916,13 @@ MRESReturn PlayerUse_Hook(int pThis)
{ {
int entHit = TR_GetEntityIndex(tr); int entHit = TR_GetEntityIndex(tr);
// This currently only works if sm_propspawner_notify_shot is enabled if (GetEntityMoveType(entHit) == MOVETYPE_VPHYSICS && g_aSpawnedProps.FindValue(entHit) != -1)
if (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 // Prevent the prop from being moved
case 1: case 1:
@@ -947,8 +934,11 @@ MRESReturn PlayerUse_Hook(int pThis)
// Add to the shooting couch list // Add to the shooting couch list
case 2: case 2:
{ {
AddToNotifyList(pThis); if (g_cvNotifyShot.BoolValue)
DisplayNotifyList(); {
AddToNotifyList(pThis);
DisplayNotifyList();
}
} }
} }
} }
@@ -981,12 +971,10 @@ bool UsePushFilter(int entity, int contentsMask)
return true; return true;
} }
// No convar for this one rn, didn't find it necessary
MRESReturn ForceVPhysicsCollide_Hook(int pThis, DHookReturn hReturn, DHookParam hParams) MRESReturn ForceVPhysicsCollide_Hook(int pThis, DHookReturn hReturn, DHookParam hParams)
{ {
int ent = hParams.Get(1); int ent = hParams.Get(1);
// Also only works with sm_propspawner_notify_shot 1 cause of this if (GetEntityMoveType(ent) == MOVETYPE_VPHYSICS && g_aSpawnedProps.FindValue(ent) != -1)
if (g_aSpawnedProps.FindValue(ent) != -1)
{ {
hReturn.Value = false; hReturn.Value = false;
return MRES_Supercede; return MRES_Supercede;
@@ -994,3 +982,35 @@ MRESReturn ForceVPhysicsCollide_Hook(int pThis, DHookReturn hReturn, DHookParam
return MRES_Ignored; 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();
}
// 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;
}