forked from UNLOZE/sm-plugins
997 lines
26 KiB
SourcePawn
997 lines
26 KiB
SourcePawn
#pragma semicolon 1
|
|
#include <sourcemod>
|
|
#include <sdktools>
|
|
#include <sdkhooks>
|
|
#include <dhooks>
|
|
#include <cstrike>
|
|
|
|
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 <r> <g> <b>");
|
|
}
|
|
|
|
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<int>(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<int>(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<Address>(offs), NumberType_Int32);
|
|
int pt2 = view_as<int>(Address_PointerSize) == 8 ? LoadFromAddress(GetEntityAddress(entity) + view_as<Address>(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;
|
|
}
|