#pragma semicolon 1 #include #include #include #include #include public Plugin myinfo = { name = "Prop Spawner", author = "Moltard / LightningZLaser", description = "A plugin to spawn props", version = "0.1", url = "https://steamcommunity.com/id/0123456789ABC/" }; Menu g_MainMenu = null; 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"); char sModelsFile[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sModelsFile, sizeof(sModelsFile), "configs/PropSpawner.cfg"); if(!FileExists(sModelsFile)) { SetFailState("Could not find config: \"%s\"", sModelsFile); return; } g_Models = new KeyValues("models"); if(!g_Models.ImportFromFile(sModelsFile)) { delete g_Models; SetFailState("ImportFromFile() failed!"); 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(); 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); } 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; int index = g_aSpawnedProps.FindValue(entity); if (index != -1) { g_aSpawnedProps.Erase(index); } } 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(); g_MainMenu = BuildMainMenu(); g_RotationMenu = BuildRotationMenu(); g_PhysicMenu = BuildModelsMenu("physic"); g_DynamicMenu = BuildModelsMenu("dynamic"); g_NpcMenu = BuildModelsMenu("npc"); g_ColorMenu = BuildColorMenu(); } public void OnMapEnd() { if (g_MainMenu != null) { delete(g_MainMenu); g_MainMenu = null; } if (g_RotationMenu != null) { delete(g_RotationMenu); g_RotationMenu = null; } if (g_PhysicMenu != null) { delete(g_PhysicMenu); g_PhysicMenu = null; } if (g_DynamicMenu != null) { delete(g_DynamicMenu); g_DynamicMenu = null; } if (g_NpcMenu != null) { delete(g_NpcMenu); g_NpcMenu = null; } UnhookAllSpawnedProps(); } /* ----------------------------------- */ Menu BuildMainMenu(){ Menu menu = new Menu(MainMenuHandler); menu.SetTitle("Prop Spawner"); menu.AddItem("menu_prop_delete", "Delete Prop"); menu.AddItem("menu_prop_rotate", "Rotate Prop"); 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; } Menu BuildRotationMenu(){ Menu menu = new Menu(RotationHandler); SetMenuTitle(menu, "Rotate Menu"); menu.AddItem( "X_+45", "Rotate X +45 Degrees"); menu.AddItem( "X_-45", "Rotate X -45 Degrees"); menu.AddItem( "Y_+45", "Rotate Y +45 Degrees"); menu.AddItem( "Y_-45", "Rotate Y -45 Degrees"); menu.AddItem( "Z_+45", "Rotate Z +45 Degrees"); menu.AddItem( "Z_-45", "Rotate Z -45 Degrees"); menu.ExitBackButton = true; menu.ExitButton = true; return menu; } Menu BuildModelsMenu(char[] modelType){ Menu menu; if(StrEqual(modelType, "physic")){ menu = new Menu(PhysicHandler); SetMenuTitle(menu, "Physic Props"); } else if(StrEqual(modelType, "dynamic")){ menu = new Menu(DynamicHandler); SetMenuTitle(menu, "Dynamic Props"); } else if(StrEqual(modelType, "npc")){ menu = new Menu(NpcHandler); SetMenuTitle(menu, "NPCs"); } menu.AddItem("deleteProp", "Delete Prop"); if(g_Models.JumpToKey(modelType, false)){ for(int i = 0; i < 100000; i++){ char sName[32]; char sIndex[11]; IntToString(i,sIndex, sizeof(sIndex)); // Index i of the model if (!(g_Models.JumpToKey(sIndex, false))) // if the key doesnt exist break; // we stop the loop g_Models.GetString("name", sName, sizeof(sName)); // Name of the model menu.AddItem(sIndex,sName); g_Models.GoBack(); // First sub key of the prop category } } g_Models.Rewind(); // Go back to the root node menu.ExitBackButton = true; menu.ExitButton = true; 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) { g_MainMenu.Display(client, MENU_TIME_FOREVER); 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]; menu.GetItem(param2, info, sizeof(info)); // Get the string of the picked option switch(action) { case(MenuAction_Select): { if(StrEqual(info, "menu_prop_delete")){ DeleteProp(client); g_MainMenu.Display(client, MENU_TIME_FOREVER); } else if(StrEqual(info, "menu_prop_rotate")){ g_RotationMenu.Display(client, MENU_TIME_FOREVER); } else if(StrEqual(info, "menu_prop_dynamic")){ g_DynamicMenu.Display(client, MENU_TIME_FOREVER); } else if(StrEqual(info, "menu_prop_physic")){ g_PhysicMenu.Display(client, MENU_TIME_FOREVER); } 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; } public RotationHandler(Menu menu, MenuAction action, int client, int param2) { char info[32]; menu.GetItem(param2, info, sizeof(info)); // Get the string of the picked option switch(action) { case(MenuAction_Select): { if(StrEqual(info, "X_+45")) { RotateProp(45.0,0,client); } else if(StrEqual(info, "Y_+45")) { RotateProp(45.0,1,client); } else if(StrEqual(info, "Z_+45")) { RotateProp(45.0,2,client); } else if(StrEqual(info, "X_-45")) { RotateProp(-45.0,0,client); } else if(StrEqual(info, "Y_-45")) { RotateProp(-45.0,1,client); } else if(StrEqual(info, "Z_-45")) { RotateProp(-45.0,2,client); } g_RotationMenu.Display(client, MENU_TIME_FOREVER); } case(MenuAction_Cancel): { if(param2 == MenuCancel_ExitBack) { g_MainMenu.Display(client, MENU_TIME_FOREVER); } } } } public PhysicHandler(Menu menu, MenuAction action, int client, int param2) { char info[32]; menu.GetItem(param2, info, sizeof(info)); // Get the string of the picked option switch(action) { case(MenuAction_Select): { if(StrEqual(info, "deleteProp")) { DeleteProp(client); g_PhysicMenu.Display(client, MENU_TIME_FOREVER); } else{ // Info has the index of the model SpawnProp("physic",info,client); } } case(MenuAction_Cancel): { if(param2 == MenuCancel_ExitBack) { g_MainMenu.Display(client, MENU_TIME_FOREVER); } } } } public DynamicHandler(Menu menu, MenuAction action, int client, int param2) { char info[32]; menu.GetItem(param2, info, sizeof(info)); // Get the string of the picked option switch(action) { case(MenuAction_Select): { if(StrEqual(info, "deleteProp")) { DeleteProp(client); g_DynamicMenu.Display(client, MENU_TIME_FOREVER); // We display again the menu } else{ // Info has the index of the model in the array SpawnProp("dynamic",info,client); } } case(MenuAction_Cancel): { if(param2 == MenuCancel_ExitBack) { g_MainMenu.Display(client, MENU_TIME_FOREVER); } } } } public NpcHandler(Menu menu, MenuAction action, int client, int param2) { char info[32]; menu.GetItem(param2, info, sizeof(info)); // Get the string of the picked option switch(action) { case(MenuAction_Select): { if(StrEqual(info, "deleteProp")) { DeleteProp(client); g_NpcMenu.Display(client, MENU_TIME_FOREVER); // We display again the menu } else{ // Info has the index of the model in the array SpawnProp("npc",info,client); } } case(MenuAction_Cancel): { if(param2 == MenuCancel_ExitBack) { g_MainMenu.Display(client, MENU_TIME_FOREVER); } } } } 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")){ return true; } return false; } void DeleteProp(int client){ char classname[64]; new DeleteIndex = GetClientAimTarget(client, false); if (DeleteIndex != -1) { 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); } } if ((DeleteIndex == -1) || !isAProp(classname)) { ReplyToCommand(client, "[SM] No entity found or invalid entity."); } } void RotateProp(float RotateValue, int RotateAxis, int client){ char classname[64]; float RotateVec[3]; int RotateIndex = GetClientAimTarget(client, false); if (RotateIndex != -1) { GetEdictClassname(RotateIndex, classname, sizeof(classname)); if(isAProp(classname)) { GetEntPropVector(RotateIndex, Prop_Send, "m_angRotation", RotateVec); RotateVec[RotateAxis] = RotateVec[RotateAxis] + RotateValue; TeleportEntity(RotateIndex, NULL_VECTOR, RotateVec, NULL_VECTOR); AcceptEntityInput(RotateIndex, "EnableCollision"); AcceptEntityInput(RotateIndex, "TurnOn", RotateIndex, RotateIndex, 0); ShowActivity2(client, "\x01[SM] \x04", "rotated a prop."); LogAction(client, -1, "\"%L\" rotated a prop.", client); } } if ((RotateIndex == -1) || !isAProp(classname)) { ReplyToCommand(client, "[SM] No entity found or invalid entity."); } } void SpawnProp(char[] TypeProp, char[] PropId, int client){ if(StrEqual(TypeProp, "physic") || StrEqual(TypeProp, "dynamic") || StrEqual(TypeProp, "npc")){ if(g_Models.JumpToKey(TypeProp, false)){ if (g_Models.JumpToKey(PropId, false)){ prop_any_create(client); } } if(StrEqual(TypeProp, "physic")){ g_PhysicMenu.Display(client, MENU_TIME_FOREVER); } else if(StrEqual(TypeProp, "dynamic")){ g_DynamicMenu.Display(client, MENU_TIME_FOREVER); } else if(StrEqual(TypeProp, "npc")){ g_NpcMenu.Display(client, MENU_TIME_FOREVER); } int i_PropId = StringToInt(PropId)+1; // +1 cause of 'Delete Prop' in the menu int pageMenu = RoundToFloor(float(i_PropId)/7); // We get the quotient of the division for(int i = 1; i <= pageMenu; i++){ // We change the menu page until we are back where we were FakeClientCommandEx(client, "menuselect 9"); } } } bool TraceEntityFilter_FilterCaller(int entity, int contentsMask, int client) { return entity != client; } void prop_any_create(int client) { char sModelName[64]; g_Models.GetString("name", sModelName, sizeof(sModelName)); // Name of the model char sModelPath[128]; g_Models.GetString("model", sModelPath, sizeof(sModelPath)); // Path of the model int z_Offset = g_Models.GetNum("z_offset",0); // if "z_offset" is not set, then it's 0 char sModelHealth[11]; char sModelExplodeDamage[11]; char sModelExplodeRadius[11]; int iModelExplode = g_Models.GetNum("explode",0); // if "explode" is not set, then it's 0 g_Models.GetString("health", sModelHealth, sizeof(sModelHealth),"0"); // if "health" is not set, then its 0 g_Models.GetString("explodedamage", sModelExplodeDamage, sizeof(sModelExplodeDamage),"1"); g_Models.GetString("exploderadius", sModelExplodeRadius, sizeof(sModelExplodeRadius),"1"); if(g_Models.JumpToKey("precache", false)){ // Precache each model used by the prop for(int i = 0; i < 100000; i++){ char sIndex[11]; IntToString(i,sIndex, sizeof(sIndex)); // Index i of the model if (!(g_Models.JumpToKey(sIndex, false))) // if the key doesnt exist break; // we stop the loop char sModelPrecache[128]; g_Models.GetString(NULL_STRING, sModelPrecache, sizeof(sModelPrecache)); PrecacheModel(sModelPrecache,true); g_Models.GoBack(); } g_Models.GoBack(); // Back to the index of the prop } else{ // If "precache" doesnt exist, we precache only the model we spawn PrecacheModel(sModelPath,true); } g_Models.GoBack(); // Back to the type of prop (physic, dynamic, npc) char sPropType[32]; g_Models.GetSectionName(sPropType, sizeof(sPropType)); // physic, dynamic, npc int prop; if(StrEqual(sPropType,"physic")){ prop = CreateEntityByName("prop_physics_override"); } else if(StrEqual(sPropType,"dynamic") || StrEqual(sPropType,"npc")){ prop = CreateEntityByName("prop_dynamic_override"); } g_Models.Rewind(); // Back to root node DispatchKeyValue(prop, "model", sModelPath); if (!StrEqual(sModelHealth,"0")) { DispatchKeyValue(prop, "health", sModelHealth); } if (iModelExplode != 0) { DispatchKeyValue(prop, "explodedamage", sModelExplodeDamage); DispatchKeyValue(prop, "exploderadius", sModelExplodeRadius); } float VecOrigin[3]; float VecAngles[3]; float normal[3]; GetClientEyePosition(client, VecOrigin); GetClientEyeAngles(client, VecAngles); TR_TraceRayFilter(VecOrigin, VecAngles, MASK_SOLID, RayType_Infinite, TraceEntityFilter_FilterCaller, client); TR_GetEndPosition(VecOrigin); DispatchKeyValue(prop, "StartDisabled", "false"); DispatchKeyValue(prop, "Solid", "6"); SetEntProp(prop, Prop_Data, "m_CollisionGroup", 5); if(StrEqual(sPropType,"physic")){ VecAngles[0] = 0.0; VecAngles[2] = 0.0; VecOrigin[2] = VecOrigin[2] + z_Offset; SetEntityMoveType(prop, MOVETYPE_VPHYSICS); TeleportEntity(prop, VecOrigin, VecAngles, NULL_VECTOR); } else if(StrEqual(sPropType,"dynamic") || StrEqual(sPropType,"npc")){ TR_GetPlaneNormal(INVALID_HANDLE, normal); GetVectorAngles(normal, normal); normal[0] += 90.0; TeleportEntity(prop, VecOrigin, normal, NULL_VECTOR); AcceptEntityInput(prop, "TurnOn", prop, prop, 0); } DispatchSpawn(prop); 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) { 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); } /* ----------------------------------- */ 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) return Plugin_Continue; if (attacker < 1 || attacker > MaxClients || !IsClientInGame(attacker)) return Plugin_Continue; AddToNotifyList(attacker); DisplayNotifyList(); return Plugin_Continue; } 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"); for (int i = g_aNotifyList.Length - 1; i >= 0; i--) { NotifyListData buf; g_aNotifyList.GetArray(i, buf, sizeof(NotifyListData)); if (!IsClientInGame(buf.client)) { if (buf.timer != INVALID_HANDLE) KillTimer(buf.timer); g_aNotifyList.Erase(i); continue; } char clientName[32]; GetClientName(buf.client, clientName, sizeof(clientName)); StrCat(text, sizeof(text), clientName); StrCat(text, sizeof(text), "\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 (IsClientInGame(i)) channel = ShowHudText(i, channel, text); } lastDisplayTime = GetEngineTime(); } void OnNotifyTimer(Handle timer, any data) { int client = view_as(data); int idx = FindInNotifyList(client); if (idx != -1) g_aNotifyList.Erase(idx); if (g_aNotifyList.Length) DisplayNotifyList(); } int FindInNotifyList(int client) { for (int i = 0; i < g_aNotifyList.Length; i++) { NotifyListData buf; g_aNotifyList.GetArray(i, buf, sizeof(NotifyListData)); if (buf.client == client) return i; } 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; }