diff --git a/PropSpawner/gamedata/PropSpawner.txt b/PropSpawner/gamedata/PropSpawner.txt new file mode 100644 index 0000000..7451d47 --- /dev/null +++ b/PropSpawner/gamedata/PropSpawner.txt @@ -0,0 +1,14 @@ +"Games" +{ + "cstrike" + { + "Offsets" + { + "PlayerUse" + { + "linux" "414" + "windows" "413" + } + } + } +} \ No newline at end of file diff --git a/PropSpawner/scripting/PropSpawner.sp b/PropSpawner/scripting/PropSpawner.sp index db646b7..7fe3170 100644 --- a/PropSpawner/scripting/PropSpawner.sp +++ b/PropSpawner/scripting/PropSpawner.sp @@ -2,6 +2,8 @@ #include #include #include +#include +#include public Plugin myinfo = { @@ -23,12 +25,15 @@ KeyValues g_Models; 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_aNotifyList; int g_aPlayerPropColor[MAXPLAYERS + 1][3]; +Handle g_PlayerUseSetup = INVALID_HANDLE; + enum struct NotifyListData { int client; @@ -93,12 +98,34 @@ public void OnPluginStart() return; } g_Models.Rewind(); // Go back to the root node + + GameData gd = new GameData("PropSpawner"); + if (gd == null) + { + LogError("PropSpawner: Missing gamedata file"); + } + else + { + int PlayerUseOffset = gd.GetOffset("PlayerUse"); + if (PlayerUseOffset == -1) + { + LogError("PropSpawner: Missing \"PlayerUse\" offset in gamedata file"); + } + else + { + g_PlayerUseSetup = DHookCreate(PlayerUseOffset, HookType_Entity, ReturnType_Void, ThisPointer_CBaseEntity, PlayerUse_Hook); + } + + delete gd; + } 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_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."); AutoExecConfig(true, "PropSpawner"); g_aSpawnedProps = new ArrayList(); @@ -707,26 +734,7 @@ public Action OnPropTakeDamage(int victim, int &attacker, int &inflictor, float if (attacker < 1 || attacker > MaxClients || !IsClientInGame(attacker)) return Plugin_Continue; - Handle timer = CreateTimer(g_cvNotifyTimer.FloatValue, OnNotifyTimer, attacker); - int idx = FindInNotifyList(attacker); - if (idx == -1) - { - NotifyListData data; - data.client = attacker; - data.timer = timer; - g_aNotifyList.PushArray(data, sizeof(NotifyListData)); - } - else - { - NotifyListData buf; - g_aNotifyList.GetArray(idx, buf, sizeof(NotifyListData)); - if (buf.timer != INVALID_HANDLE) - KillTimer(buf.timer); - - buf.timer = timer; - g_aNotifyList.SetArray(idx, buf, sizeof(NotifyListData)); - } - + AddToNotifyList(attacker); DisplayNotifyList(); return Plugin_Continue; @@ -793,6 +801,8 @@ int FindInNotifyList(int client) public void OnClientPutInServer(int client) { g_aPlayerPropColor[client] = {255, 255, 255}; + DHookEntity(g_PlayerUseSetup, false, client); + DHookEntity(g_PlayerUseSetup, true, client, INVALID_FUNCTION, PlayerUse_HookPost); } void OnRainbowTimer(Handle timer, any data) @@ -818,3 +828,124 @@ void ApplyRainbow(int ent, float Frequency) SetEntityRenderColor(ent, Red, Green, Blue, 255); } + +void AddToNotifyList(int client) +{ + if (GetClientTeam(client) != CS_TEAM_CT) + return; + + Handle timer = CreateTimer(g_cvNotifyTimer.FloatValue, OnNotifyTimer, client); + int idx = FindInNotifyList(client); + if (idx == -1) + { + NotifyListData data; + data.client = client; + data.timer = timer; + g_aNotifyList.PushArray(data, sizeof(NotifyListData)); + } + else + { + NotifyListData buf; + g_aNotifyList.GetArray(idx, buf, sizeof(NotifyListData)); + if (buf.timer != INVALID_HANDLE) + KillTimer(buf.timer); + + buf.timer = timer; + g_aNotifyList.SetArray(idx, buf, sizeof(NotifyListData)); + } +} + +ConVar sv_pushaway_force = null; +float g_flPushawayRestore = 0.0; + +MRESReturn PlayerUse_Hook(int pThis) +{ + static ConVar sv_turbophysics = null; + if (!sv_pushaway_force) + sv_pushaway_force = FindConVar("sv_pushaway_force"); + + if (!sv_turbophysics) + sv_turbophysics = FindConVar("sv_turbophysics"); + + if (!sv_pushaway_force || !sv_turbophysics) + return MRES_Ignored; + + if (!sv_turbophysics.BoolValue) + return MRES_Ignored; + + if (g_cvUseMode.IntValue != 1 && g_cvUseMode.IntValue != 2) + return MRES_Ignored; + + // ZMs can always push it + if (GetClientTeam(pThis) != CS_TEAM_CT) + return MRES_Ignored; + + if ((GetClientButtons(pThis) & IN_USE) == 0) + return MRES_Ignored; + + float eyePos[3]; + float eyeAng[3]; + float eyeFwd[3]; + float endPos[3]; + GetClientEyePosition(pThis, eyePos); + GetClientEyeAngles(pThis, eyeAng); + GetAngleVectors(eyeAng, eyeFwd, NULL_VECTOR, NULL_VECTOR); + + endPos[0] = eyePos[0] + (eyeFwd[0] * 96.0); + endPos[1] = eyePos[1] + (eyeFwd[1] * 96.0); + endPos[2] = eyePos[2] + (eyeFwd[2] * 96.0); + + Handle tr = TR_TraceRayFilterEx(eyePos, endPos, MASK_SOLID, RayType_EndPoint, UsePushFilter, 0, TRACE_ENTITIES_ONLY); + + if (TR_DidHit(tr)) + { + 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) + { + // Prevent the prop from being moved + case 1: + { + g_flPushawayRestore = sv_pushaway_force.FloatValue; + sv_pushaway_force.FloatValue = 0.0; + } + + // Add to the shooting couch list + case 2: + { + AddToNotifyList(pThis); + DisplayNotifyList(); + } + } + } + } + + CloseHandle(tr); + return MRES_Handled; +} + +MRESReturn PlayerUse_HookPost(int pThis) +{ + if (sv_pushaway_force && g_flPushawayRestore != 0.0) + { + sv_pushaway_force.FloatValue = g_flPushawayRestore; + g_flPushawayRestore = 0.0; + } + + return MRES_Handled; +} + +bool UsePushFilter(int entity, int contentsMask) +{ + // Checking if the entity has a physics object + int offs = FindDataMapInfo(entity, "m_pPhysicsObject"); + int pt1 = LoadFromAddress(GetEntityAddress(entity) + view_as
(offs), NumberType_Int32); + int pt2 = view_as(Address_PointerSize) == 8 ? LoadFromAddress(GetEntityAddress(entity) + view_as
(offs + 4), NumberType_Int32) : 0; + if (pt1 == 0 && pt2 == 0) + return false; + + return true; +}