diff --git a/PropSpawner/gamedata/PropSpawner.txt b/PropSpawner/gamedata/PropSpawner.txt new file mode 100644 index 0000000..d94464d --- /dev/null +++ b/PropSpawner/gamedata/PropSpawner.txt @@ -0,0 +1,20 @@ +"Games" +{ + "cstrike" + { + "Offsets" + { + "PlayerUse" + { + "linux" "414" + "windows" "413" + } + + "ForceVPhysicsCollide" + { + "linux" "160" + "windows" "159" + } + } + } +} \ No newline at end of file diff --git a/PropSpawner/scripting/PropSpawner.sp b/PropSpawner/scripting/PropSpawner.sp index 4b533b7..42fcfa1 100644 --- a/PropSpawner/scripting/PropSpawner.sp +++ b/PropSpawner/scripting/PropSpawner.sp @@ -2,6 +2,8 @@ #include #include #include +#include +#include public Plugin myinfo = { @@ -17,21 +19,66 @@ Menu g_RotationMenu = null; Menu g_PhysicMenu = null; Menu g_DynamicMenu = null; Menu g_NpcMenu = null; +Menu g_ColorMenu = null; 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; +Handle g_ForceVPhysicsCollideSetup = INVALID_HANDLE; + enum struct NotifyListData { int client; Handle timer; } +enum +{ + PropColors_Default, + PropColors_Black, + PropColors_Red, + PropColors_Purple, + PropColors_Blue, + PropColors_Green, + PropColors_Yellow, + PropColors_Orange, + PropColors_Rainbow, + PropColors_Count +} + +char ColorToString[PropColors_Count][] = { + "Default", + "Black", + "Red", + "Purple", + "Blue", + "Green", + "Yellow", + "Orange", + "Rainbow" +}; + +int ColorToRGB[PropColors_Count][3] = { + {255, 255, 255}, + {0, 0, 0}, + {255, 0, 0}, + {128, 0, 128}, + {0, 0, 255}, + {0, 255, 0}, + {255, 255, 0}, + {255, 165, 0}, + {256, 256, 256} +}; + public void OnPluginStart() { LoadTranslations("common.phrases"); @@ -52,11 +99,45 @@ 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); + } + + int ForceVPhysicsCollideOffset = gd.GetOffset("ForceVPhysicsCollide"); + if (ForceVPhysicsCollideOffset == -1) + { + LogError("PropSpawner: Missing \"ForceVPhysicsCollide\" offset in gamedata file"); + } + else + { + g_ForceVPhysicsCollideSetup = DHookCreate(ForceVPhysicsCollideOffset, HookType_Entity, ReturnType_Bool, ThisPointer_CBaseEntity, ForceVPhysicsCollide_Hook); + DHookAddParam(g_ForceVPhysicsCollideSetup, HookParamType_CBaseEntity); + } + + 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(); @@ -65,6 +146,12 @@ public void OnPluginStart() 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); +} + public void OnEntityDestroyed(int entity) { // Safety net: if a tracked prop is removed by any means other than DeleteProp() @@ -109,6 +196,7 @@ public void OnMapStart() g_PhysicMenu = BuildModelsMenu("physic"); g_DynamicMenu = BuildModelsMenu("dynamic"); g_NpcMenu = BuildModelsMenu("npc"); + g_ColorMenu = BuildColorMenu(); } public void OnMapEnd() @@ -152,6 +240,7 @@ Menu BuildMainMenu(){ menu.AddItem("menu_prop_dynamic", "Dynamic/Static Props"); menu.AddItem("menu_prop_physic", "Physic Props"); menu.AddItem("menu_prop_npc", "NPCs"); + menu.AddItem("menu_prop_color", "Choose Color"); menu.ExitButton = true; return menu; } @@ -202,6 +291,20 @@ Menu BuildModelsMenu(char[] modelType){ return menu; } +Menu BuildColorMenu() +{ + Menu menu = new Menu(ColorHandler); + SetMenuTitle(menu, "Choose Prop Color"); + for (int i = 0; i < PropColors_Count; i++) + { + menu.AddItem(ColorToString[i], ColorToString[i]); + } + + menu.ExitBackButton = true; + menu.ExitButton = true; + return menu; +} + /* ----------------------------------- */ public Action Command_MainMenu(int client, int args) @@ -210,6 +313,35 @@ public Action Command_MainMenu(int client, int args) return Plugin_Handled; } +public Action Command_SetPropColor(int client, int args) +{ + if (args < 3) + { + if (args != 0) + { + char buf[32]; + GetCmdArg(1, buf, sizeof(buf)); + if (StrEqual(buf, "rainbow", false)) + { + g_aPlayerPropColor[client][0] = 256; + ReplyToCommand(client, "\x01[SM] Changed color to \x04Rainbow\x01."); + } + } + else + { + ReplyToCommand(client, "Usage: sm_setpropcolor "); + } + + return Plugin_Handled; + } + + int r = g_aPlayerPropColor[client][0] = GetCmdArgInt(1) % 256; + int g = g_aPlayerPropColor[client][1] = GetCmdArgInt(2) % 256; + int b = g_aPlayerPropColor[client][2] = GetCmdArgInt(3) % 256; + ReplyToCommand(client, "\x01[SM] Changed color to \x07%02x%02x%02x%d %d %d\x01.", r, g, b, r, g, b); + return Plugin_Handled; +} + public int MainMenuHandler(Menu menu, MenuAction action, int client, int param2) { char info[32]; @@ -233,6 +365,9 @@ public int MainMenuHandler(Menu menu, MenuAction action, int client, int param2) else if(StrEqual(info, "menu_prop_npc")){ g_NpcMenu.Display(client, MENU_TIME_FOREVER); } + else if(StrEqual(info, "menu_prop_color")){ + g_ColorMenu.Display(client, MENU_TIME_FOREVER); + } } } return 0; @@ -361,6 +496,37 @@ public NpcHandler(Menu menu, MenuAction action, int client, int param2) } } +public ColorHandler(Menu menu, MenuAction action, int client, int param2) +{ + char info[32]; + menu.GetItem(param2, info, sizeof(info)); + switch (action) + { + case MenuAction_Select: + { + int colorIdx = -1; + for (int i = 0; i < PropColors_Count; i++) + { + if (StrEqual(ColorToString[i], info)) + { + colorIdx = i; + break; + } + } + + if (colorIdx != -1) + { + g_aPlayerPropColor[client] = ColorToRGB[colorIdx]; + ReplyToCommand(client, "\x01[SM] Changed color to \x04%s\x01.", info); + g_MainMenu.Display(client, MENU_TIME_FOREVER); + } + } + + case MenuAction_Cancel: + if (param2 == MenuCancel_ExitBack) + g_MainMenu.Display(client, MENU_TIME_FOREVER); + } +} bool isAProp(char[] classname){ if(StrEqual(classname, "prop_physics") || StrEqual(classname, "prop_physics_override") || StrEqual(classname, "prop_dynamic") || StrEqual(classname, "prop_dynamic_override") || StrEqual(classname, "prop_physics_multiplayer") || StrEqual(classname, "prop_dynamic_ornament") || StrEqual(classname, "prop_static")){ @@ -548,7 +714,7 @@ void prop_any_create(int client) TeleportEntity(prop, VecOrigin, normal, NULL_VECTOR); AcceptEntityInput(prop, "TurnOn", prop, prop, 0); } - + DispatchSpawn(prop); AcceptEntityInput(prop, "TurnOn", prop, prop, 0); AcceptEntityInput(prop, "EnableCollision"); @@ -557,9 +723,21 @@ void prop_any_create(int client) 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); } + + SetEntityRenderMode(prop, RENDER_TRANSCOLOR); + if (g_aPlayerPropColor[client][0] == 256) // Rainbow + { + CreateTimer(0.2, OnRainbowTimer, EntIndexToEntRef(prop), TIMER_REPEAT); + ApplyRainbow(prop, 1.0); + } + else + { + SetEntityRenderColor(prop, g_aPlayerPropColor[client][0], g_aPlayerPropColor[client][1], g_aPlayerPropColor[client][2], 255); + } ShowActivity2(client, "\x01[SM] \x04", "\x01Spawned \x04%s\x01.", sModelName); LogAction(client, -1, "\"%L\" Spawned \"%s\".", client, sModelName); @@ -575,26 +753,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; @@ -603,6 +762,13 @@ public Action OnPropTakeDamage(int victim, int &attacker, int &inflictor, float void DisplayNotifyList() { 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) + channel = -1; + char text[255]; strcopy(text, sizeof(text), "Shooting Couch:\n"); @@ -626,12 +792,14 @@ void DisplayNotifyList() StrCat(text, sizeof(text), "\n"); } - SetHudTextParams(0.8, -1.0, g_cvNotifyTimer.FloatValue, 255, 255, 255, 255, 0, 0.0, 0.0, 0.0); + 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, text); } + + lastDisplayTime = GetEngineTime(); } void OnNotifyTimer(Handle timer, any data) @@ -657,3 +825,172 @@ int FindInNotifyList(int client) return -1; } + +public void OnClientPutInServer(int client) +{ + g_aPlayerPropColor[client] = {255, 255, 255}; + if (g_PlayerUseSetup != INVALID_HANDLE) + { + DHookEntity(g_PlayerUseSetup, false, client); + DHookEntity(g_PlayerUseSetup, true, client, INVALID_FUNCTION, PlayerUse_HookPost); + } +} + +void OnRainbowTimer(Handle timer, any data) +{ + int prop = EntRefToEntIndex(view_as(data)); + if (prop == -1) + { + KillTimer(timer); + return; + } + + ApplyRainbow(prop, 1.0); +} + +// Copied from GlowColors +void ApplyRainbow(int ent, float Frequency) +{ + float i = GetGameTime(); + + int Red = RoundFloat(Sine(Frequency * i + 0.0) * 127.0 + 128.0); + int Green = RoundFloat(Sine(Frequency * i + 2.0943951) * 127.0 + 128.0); + int Blue = RoundFloat(Sine(Frequency * i + 4.1887902) * 127.0 + 128.0); + + 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; +} + +// 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; + return MRES_Supercede; + } + + return MRES_Ignored; +}