diff --git a/PropSpawner/scripting/PropSpawner.sp b/PropSpawner/scripting/PropSpawner.sp index 42fcfa1..d1031e0 100644 --- a/PropSpawner/scripting/PropSpawner.sp +++ b/PropSpawner/scripting/PropSpawner.sp @@ -4,6 +4,7 @@ #include #include #include +#include public Plugin myinfo = { @@ -26,8 +27,10 @@ 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 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]; @@ -134,28 +137,32 @@ 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."); + 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(); g_aNotifyList = new ArrayList(); - - HookEvent("round_end", Event_RoundEnd); } 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) { - // 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 +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() { g_Models.Rewind(); @@ -226,8 +212,6 @@ public void OnMapEnd() delete(g_NpcMenu); g_NpcMenu = null; } - - UnhookAllSpawnedProps(); } /* ----------------------------------- */ @@ -545,15 +529,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 +694,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 - if (g_cvNotifyShot.BoolValue) + // Hook the prop so we can detect when it gets shot/damaged + 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 SDKHook(prop, SDKHook_OnTakeDamage, OnPropTakeDamage); - g_aSpawnedProps.Push(prop); } + g_aSpawnedProps.Push(prop); + SetEntityRenderMode(prop, RENDER_TRANSCOLOR); 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) { - 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); @@ -759,18 +738,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--) { @@ -788,18 +765,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) @@ -865,6 +840,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) @@ -904,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 @@ -932,10 +916,13 @@ 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) + 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: @@ -947,8 +934,11 @@ MRESReturn PlayerUse_Hook(int pThis) // Add to the shooting couch list case 2: { - AddToNotifyList(pThis); - DisplayNotifyList(); + if (g_cvNotifyShot.BoolValue) + { + AddToNotifyList(pThis); + DisplayNotifyList(); + } } } } @@ -981,12 +971,10 @@ 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) + if (GetEntityMoveType(ent) == MOVETYPE_VPHYSICS && g_aSpawnedProps.FindValue(ent) != -1) { hReturn.Value = false; return MRES_Supercede; @@ -994,3 +982,35 @@ 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(); +} + +// 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; +}