From 5796ddd53624c775d7adf75b4df221477a3f7786 Mon Sep 17 00:00:00 2001 From: Jackz Date: Sat, 6 Jan 2024 22:01:22 -0600 Subject: [PATCH] Working prop spawner --- scripting/include/hats/hats.sp | 9 +- scripting/include/hats/props.sp | 663 +++++++++++++++++++++++++++++--- scripting/include/hats/walls.sp | 434 ++++++++++++++------- scripting/include/jutils.inc | 4 +- scripting/l4d2_hats.sp | 155 +++++--- 5 files changed, 1021 insertions(+), 244 deletions(-) diff --git a/scripting/include/hats/hats.sp b/scripting/include/hats/hats.sp index 615fe98..f292707 100644 --- a/scripting/include/hats/hats.sp +++ b/scripting/include/hats/hats.sp @@ -328,7 +328,6 @@ Action Command_DoAHat(int client, int args) { // Use the new wall editor WallBuilder[client].Reset(); WallBuilder[client].entity = EntIndexToEntRef(entity); - WallBuilder[client].canScale = false; WallBuilder[client].SetMode(MOVE_ORIGIN); PrintToChat(client, "\x04[Hats] \x01Beta Prop Mover active for \x04%d", entity); } else { @@ -353,7 +352,7 @@ Action Command_DoAHat(int client, int args) { } else if(entity == EntRefToEntIndex(WallBuilder[client].entity)) { // Prevent making an entity you editing a hat return Plugin_Handled; - } else if(cvar_sm_hats_max_distance.FloatValue > 0.0 && entity >= MaxClients) { + } else if(!isForced && cvar_sm_hats_max_distance.FloatValue > 0.0 && entity >= MaxClients) { float posP[3], posE[3]; GetClientEyePosition(client, posP); GetEntPropVector(entity, Prop_Data, "m_vecOrigin", posE); @@ -377,8 +376,8 @@ Action Command_DoAHat(int client, int args) { PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent); entity = parent; } else if(entity <= MaxClients) { // Checks for hatting a player entity - if(IsFakeClient(entity)) { - PrintToChat(client, "[Hats] Cannot hat bots"); + if(IsFakeClient(entity) && L4D_GetIdlePlayerOfBot(entity) != -1) { + PrintToChat(client, "[Hats] Cannot hat idle bots"); return Plugin_Handled; } else if(GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous"); @@ -537,7 +536,7 @@ int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { } bool IsHatsEnabled(int client) { - return (cvar_sm_hats_enabled.IntValue == 1 && GetUserAdmin(client) != INVALID_ADMIN_ID) || cvar_sm_hats_enabled.IntValue == 2 + return (cvar_sm_hats_enabled.IntValue == 1 && GetUserAdmin(client) != INVALID_ADMIN_ID) || cvar_sm_hats_enabled.IntValue == 2 } void ClearHats() { diff --git a/scripting/include/hats/props.sp b/scripting/include/hats/props.sp index e126424..33a9e90 100644 --- a/scripting/include/hats/props.sp +++ b/scripting/include/hats/props.sp @@ -1,69 +1,644 @@ +TopMenuObject g_propSpawnerCategory; public void OnAdminMenuReady(Handle topMenuHandle) { TopMenu topMenu = TopMenu.FromHandle(topMenuHandle); if(topMenu != g_topMenu) { - TopMenuObject propSpawner = topMenu.AddCategory("Prop Spawner (Alpha)", Category_Handler); - if(propSpawner != INVALID_TOPMENUOBJECT) { - topMenu.AddItem("Spawn Prop", AdminMenu_Spawn, propSpawner, "sm_prop"); - topMenu.AddItem("Edit Props", AdminMenu_Edit, propSpawner, "sm_prop"); - topMenu.AddItem("Delete Props", AdminMenu_Delete, propSpawner, "sm_prop"); - topMenu.AddItem("Save / Load", AdminMenu_SaveLoad, propSpawner, "sm_prop"); - + g_propSpawnerCategory = topMenu.AddCategory("hats_editor", Category_Handler); + if(g_propSpawnerCategory != INVALID_TOPMENUOBJECT) { + topMenu.AddItem("editor_spawn", AdminMenu_Spawn, g_propSpawnerCategory, "sm_prop"); + topMenu.AddItem("editor_edit", AdminMenu_Edit, g_propSpawnerCategory, "sm_prop"); + topMenu.AddItem("editor_delete", AdminMenu_Delete, g_propSpawnerCategory, "sm_prop"); + topMenu.AddItem("editor_saveload", AdminMenu_SaveLoad, g_propSpawnerCategory, "sm_prop"); } } g_topMenu = topMenu; - +} + + +enum struct CategoryData { + char name[64]; + bool hasItems; + ArrayList items; +} +enum struct ItemData { + char model[128]; + char name[64]; +} +enum struct SaveData { + char model[128]; + buildType type; + float origin[3]; + float angles[3]; + int color[4]; + + void FromEntity(int entity) { + // Use this.model as a buffer: + GetEntityClassname(entity, this.model, sizeof(this.model)); + if(StrEqual(this.model, "prop_physics")) this.type = Build_Physics; + else if(StrEqual(this.model, "prop_dynamic")) { + if(GetEntProp(entity, Prop_Send, "m_nSolidType") == 0) { + this.type = Build_NonSolid; + } else { + this.type = Build_Solid; + } + } + + GetEntPropString(entity, Prop_Data, "m_ModelName", this.model, sizeof(this.model)); + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); + GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); + GetEntityRenderColor(entity, this.color[0],this.color[1],this.color[2],this.color[3]); + } + + void Serialize(char[] output, int maxlen) { + Format( + output, maxlen, "%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d", + this.model, this.type, this.origin[0], this.origin[1], this.origin[2], + this.angles[0], this.angles[1], this.angles[2], + this.color[0], this.color[1], this.color[2], this.color[3] + ); + } + + void Deserialize(const char[] output) { + char buffer[32]; + int index = SplitString(output, ",", this.model, sizeof(this.model)); + index = SplitString(output[index], ",", buffer, sizeof(buffer)); + this.type = view_as(StringToInt(buffer)); + for(int i = 0; i < 3; i++) { + index = SplitString(output[index], ",", buffer, sizeof(buffer)); + this.origin[i] = StringToFloat(buffer); + } + for(int i = 0; i < 3; i++) { + index = SplitString(output[index], ",", buffer, sizeof(buffer)); + this.angles[i] = StringToFloat(buffer); + } + for(int i = 0; i < 4; i++) { + index = SplitString(output[index], ",", buffer, sizeof(buffer)); + this.color[i] = StringToInt(buffer); + } + } + +} +ArrayList g_categories; +ArrayList g_spawnedItems; +ArrayList g_savedItems; + +bool LoadSaves(ArrayList saves) { + saves = new ArrayList(ByteCountToCells(64)); + char path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s", g_currentMap); + FileType fileType; + DirectoryListing listing = OpenDirectory(path); + if(listing == null) return false; + char buffer[64]; + while(listing.GetNext(buffer, sizeof(buffer), fileType)) { + saves.PushString(buffer); + } + delete listing; + return true; +} + +public bool LoadSave(const char[] save) { + char path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s/%s", g_currentMap, save); + // ArrayList savedItems = new ArrayList(sizeof(SaveData)); + File file = OpenFile(path, "r"); + if(file == null) return false; + char buffer[256]; + SaveData data; + while(file.ReadLine(buffer, sizeof(buffer))) { + data.Deserialize(buffer); + int entity = -1; + if(data.type == Build_Physics) + entity = CreateEntityByName("prop_physics"); + else + entity = CreateEntityByName("prop_dynamic"); + if(entity == -1) continue; + PrecacheModel(data.model); + DispatchKeyValue(entity, "model", data.model); + DispatchKeyValue(entity, "targetname", "saved_prop"); + DispatchKeyValue(entity, "solid", data.type == Build_NonSolid ? "0" : "6"); + TeleportEntity(entity, data.origin, data.angles, NULL_VECTOR); + if(!DispatchSpawn(entity)) continue; + // TODO: Setrendertype? + SetEntityRenderColor(entity, data.color[0], data.color[1], data.color[2], data.color[3]); + // TODO: previews? + // g_savedItems.PushArray(data); + } + delete file; + // delete savedItems; + return true; +} + +bool CreateSave(const char[] name) { + char path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s/%s.txt", g_currentMap, name); + File file = OpenFile(name, "w"); + if(file == null) return false; + char buffer[132]; + SaveData data; + for(int i = 0; i < g_spawnedItems.Length; i++) { + int ref = g_spawnedItems.Get(i); + data.FromEntity(ref); + data.Serialize(buffer, sizeof(buffer)); + file.WriteLine("%s", buffer); + } + file.Flush(); + delete file; + return true; +} + +void UnloadSave() { + if(g_savedItems != null) { + delete g_savedItems; + } +} + +public void LoadCategories() { + if(g_categories != null) return; + g_categories = new ArrayList(sizeof(CategoryData)); + char path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/models"); + LoadFolder(g_categories, path); +} +public void UnloadCategories() { + if(g_categories == null) return; + _UnloadCategories(g_categories); + delete g_categories; +} +void _UnloadCategories(ArrayList list) { + CategoryData cat; + for(int i = 0; i < list.Length; i++) { + list.GetArray(i, cat); + _UnloadCategory(cat); + } +} +void _UnloadCategory(CategoryData cat) { + // Is a sub-category: + if(!cat.hasItems) { + _UnloadCategories(cat.items); + } + delete cat.items; +} + +void LoadFolder(ArrayList parent, const char[] rootPath) { + char buffer[PLATFORM_MAX_PATH]; + FileType fileType; + DirectoryListing listing = OpenDirectory(rootPath); + if(listing == null) { + LogError("Cannot open \"%s\"", rootPath); + } + while(listing.GetNext(buffer, sizeof(buffer), fileType)) { + if(fileType == FileType_Directory) { + // TODO: support subcategory + if(buffer[0] == '.') continue; + CategoryData data; + Format(data.name, sizeof(data.name), "%s>>", buffer); + data.items = new ArrayList(); + + Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer); + LoadFolder(data.items, buffer); + parent.PushArray(data); + } else if(fileType == FileType_File) { + Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer); + LoadProps(parent, buffer); + } + } + delete listing; +} + +void LoadProps(ArrayList parent, const char[] filePath) { + File file = OpenFile(filePath, "r"); + if(file == null) { + PrintToServer("[Props] Cannot open file \"%s\"", filePath); + return; + } + CategoryData category; + category.items = new ArrayList(sizeof(ItemData)); + category.hasItems = true; + char buffer[128]; + if(!file.ReadLine(buffer, sizeof(buffer))) { + delete file; + return; + } + ReplaceString(buffer, sizeof(buffer), "\n", ""); + ReplaceString(buffer, sizeof(buffer), "\r", ""); + Format(category.name, sizeof(category.name), "%s>", buffer); + while(file.ReadLine(buffer, sizeof(buffer))) { + ItemData item; + int index = SplitString(buffer, " ", item.model, sizeof(item.model)); + if(index == -1) { + strcopy(item.name, sizeof(item.name), buffer); + } else { + strcopy(item.name, sizeof(item.name), buffer[index]); + } + category.items.PushArray(item); + } + parent.PushArray(category); + delete file; +} +public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) { + if(!g_isSearchActive[client]) { + return Plugin_Continue; + } + ArrayList results = SearchItems(sArgs); + if(results.Length == 0) { + CPrintToChat(client, "\x04[Editor]\x01 No results found. :("); + } else { + ShowItemMenuAny(client, results); + } + delete results; + return Plugin_Handled; +} +#define MAX_SEARCH_RESULTS 30 +ArrayList SearchItems(const char[] query) { + // TODO: search + ArrayList results = new ArrayList(sizeof(ItemData)); + _searchCategory(results, g_categories, query); + return results; +} + +void _searchCategory(ArrayList results, ArrayList categories, const char[] query) { + CategoryData cat; + for(int i = 0; i < categories.Length; i++) { + categories.GetArray(i, cat); + if(cat.hasItems) { + _searchItems(results, cat.items, query); + } else { + _searchCategory(results, cat.items, query); + } + if(results.Length > MAX_SEARCH_RESULTS) return; + } +} +void _searchItems(ArrayList results, ArrayList items, const char[] query) { + ItemData item; + for(int i = 0; i < items.Length; i++) { + items.GetArray(i, item); + if(StrContains(item.name, query, false)) { + results.PushArray(item); + } + if(results.Length > MAX_SEARCH_RESULTS) return; + } } void Category_Handler(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength) { if(action == TopMenuAction_DisplayTitle) { Format(buffer, maxlength, "Select a task:"); } else if(action == TopMenuAction_DisplayOption) { - Format(buffer, maxlength, "Spawn Props"); + Format(buffer, maxlength, "Spawn Props (Beta)"); } } + void AdminMenu_Spawn(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - if(action == TopMenuAction_SelectOption) { - if(!FindConVar("sv_cheats").BoolValue) { - ReplyToCommand(param, "[Props] Enable cheats to use the prop spawner"); + if(action == TopMenuAction_DisplayOption) { + Format(buffer, maxlength, "Spawn Props"); + } else if(action == TopMenuAction_SelectOption) { + if(!FindConVar("sm_cheats").BoolValue) { + CReplyToCommand(param, "\x04[Editor] \x01Set \x05sm_cheats\x01 to \x051\x01 to use the prop spawner"); return; } - // TODO: - /* - Flow: - 1. /admin -> Prop Spawner -> Spawn -> [category] -> [prop] - 2. ghost spawner active (press '?somekey?' to switch spawn mode) - 3. continue on place. press button to press? - */ - // Menu menu = new Menu(Handler_Spawn); - // menu.SetTitle("Spawn Method:"); - - // menu.AddItem("p", "Physics"); - // menu.AddItem("s", "Solid"); - // menu.AddItem("n", "Non Solid"); - - // menu.ExitBackButton = true; - // menu.ExitButton = true; - // menu.Display(param, MENU_TIME_FOREVER); + Menu menu = new Menu(Spawn_RootHandler); + menu.SetTitle("Choose list:"); + menu.AddItem("f", "Favorites"); + menu.AddItem("r", "Recents"); + menu.AddItem("s", "Search"); + menu.AddItem("n", "Prop List"); + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.Display(param, MENU_TIME_FOREVER); } } -void AdminMenu_Edit(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - -} -void AdminMenu_Delete(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - -} -void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - -} -int Handler_Spawn(Menu menu, MenuAction action, int client, int param2) { +int Spawn_RootHandler(Menu menu, MenuAction action, int client, int param2) { if (action == MenuAction_Select) { - static char info[2]; - if(info[0] == 'p') { - - } - + char info[2]; + menu.GetItem(param2, info, sizeof(info)); + switch(info[0]) { + case 'f': Spawn_ShowFavorites(client); + case 'r': Spawn_ShowRecents(client); + case 's': Spawn_ShowSearch(client); + case 'n': ShowCategoryList(client); + } + // TODO: handle back (to top menu) } else if (action == MenuAction_End) delete menu; return 0; +} +Spawn_ShowFavorites(int client) { + PrintToChat(client, "In development"); + return; + // Menu menu = new Menu(SpawnItemHandler); + // char model[128]; + // for(int i = 0; i <= g_spawnedItems.Length; i++) { + // int ref = g_spawnedItems.Get(i); + // if(IsValidEntity(ref)) { + // GetEntPropString(ref, Prop_Data, "m_ModelName", model, sizeof(model)); + // menu.AddItem(model, model); + // } + // } + // menu.ExitBackButton = true; + // menu.ExitButton = true; + // menu.Display(client, MENU_TIME_FOREVER); +} +Spawn_ShowRecents(int client) { + Menu menu = new Menu(SpawnItemHandler); + menu.SetTitle("Recent Props:"); + char model[128]; + for(int i = 0; i <= g_spawnedItems.Length; i++) { + int ref = g_spawnedItems.Get(i); + if(IsValidEntity(ref)) { + GetEntPropString(ref, Prop_Data, "m_ModelName", model, sizeof(model)); + menu.AddItem(model, model); + } + } + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.Display(client, MENU_TIME_FOREVER); +} +Spawn_ShowSearch(int client) { + g_isSearchActive[client] = true; + CReplyToCommand(client, "\x04[Editor] \x01Please enter search query in chat:"); +} +void AdminMenu_Edit(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { + if(action == TopMenuAction_DisplayOption) { + Format(buffer, maxlength, "Edit Props"); + } else if(action == TopMenuAction_SelectOption) { + ShowEditList(param); + } +} +void ShowDeleteList(int client, int index) { + Menu menu = new Menu(DeleteHandler); + menu.SetTitle("Delete Props"); + + menu.AddItem("-1", "Delete All"); + menu.AddItem("-2", "Delete All (Mine Only)"); + char info[8]; + char buffer[128]; + for(int i = 0; i < g_spawnedItems.Length; i++) { + int ref = g_spawnedItems.Get(i); + IntToString(i, info, sizeof(info)); + GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); + index = FindCharInString(buffer, '/', true); + if(index != -1) + menu.AddItem(info, buffer[index + 1]); + } + + menu.ExitBackButton = true; + menu.ExitButton = true; + // Add +2 to the index for the two "Delete ..." buttons + menu.DisplayAt(client, index + 2, MENU_TIME_FOREVER); +} +void ShowEditList(int client, int index = 0) { + Menu menu = new Menu(EditHandler); + menu.SetTitle("Edit Prop"); + + char info[8]; + char buffer[32]; + for(int i = 0; i < g_spawnedItems.Length; i++) { + int ref = g_spawnedItems.Get(i); + IntToString(i, info, sizeof(info)); + GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); + index = FindCharInString(buffer, '/', true); + if(index != -1) + menu.AddItem(info, buffer[index + 1]); + } + + menu.ExitBackButton = true; + menu.ExitButton = true; + // Add +2 to the index for the two "Delete ..." buttons + menu.DisplayAt(client, index, MENU_TIME_FOREVER); +} + +void AdminMenu_Delete(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { + if(action == TopMenuAction_DisplayOption) { + Format(buffer, maxlength, "Delete Props"); + } else if(action == TopMenuAction_SelectOption) { + ShowDeleteList(param, -2); + } +} + +void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { + if(action == TopMenuAction_DisplayOption) { + Format(buffer, maxlength, "Save / Load"); + } else if(action == TopMenuAction_SelectOption) { + + ArrayList saves; + LoadSaves(saves); + Menu menu = new Menu(SaveLoadHandler); + menu.SetTitle("Save / Load"); + char name[64]; + menu.AddItem("", "[New Save]"); + for(int i = 0; i < saves.Length; i++) { + saves.GetString(i, name, sizeof(name)); + menu.AddItem(name, name); + } + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.Display(param, MENU_TIME_FOREVER); + delete saves; + } +} + +void ShowCategoryList(int client) { + LoadCategories(); + Menu menu = new Menu(SpawnCategoryHandler); + menu.SetTitle("Choose a category"); + CategoryData cat; + char info[4]; + for(int i = 0; i < g_categories.Length; i++) { + g_categories.GetArray(i, cat); + Format(info, sizeof(info), "%d", i); + // TODO: add support for nested + if(cat.hasItems) + menu.AddItem(info, cat.name); + } + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.DisplayAt(client, g_lastCategoryIndex[client], MENU_TIME_FOREVER); +} + +int SpawnCategoryHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char info[8]; + menu.GetItem(param2, info, sizeof(info)); + int index = StringToInt(info); + if(index > 0) { + ShowItemMenu(client, index); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +int SaveLoadHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char saveName[64]; + menu.GetItem(param2, saveName, sizeof(saveName)); + if(saveName[0] == '\0') { + // Save new + FormatTime(saveName, sizeof(saveName), "%Y-%m-%d %X"); + if(CreateSave(saveName)) { + PrintToChat(client, "\x04[Editor]\x01 Created save \x05%s.txt", saveName); + } else { + PrintToChat(client, "\x04[Editor]\x01 Error creating save file"); + } + } else if(LoadSave(saveName)) { + PrintToChat(client, "\x04[Editor]\x01 Loaded save \x05%s", saveName); + } else { + PrintToChat(client, "\x04[Editor]\x01 Error loading save file"); + } + } else if (action == MenuAction_Cancel) { + if(param2 == MenuCancel_ExitBack) { + DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +int DeleteHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char info[8]; + menu.GetItem(param2, info, sizeof(info)); + int index = StringToInt(info); + if(index == -1) { + // Delete all (everyone) + int count = DeleteAll(); + PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count); + ShowDeleteList(client, index); + } else if(index == -2) { + // Delete all (mine only) + int count = DeleteAll(client); + PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count); + ShowDeleteList(client, index); + } else { + int ref = g_spawnedItems.Get(index); + // TODO: add delete confirm + if(IsValidEntity(ref)) { + RemoveEntity(ref); + } + g_spawnedItems.Erase(index); + if(index > 0) { + index--; + } + ShowDeleteList(client, index); + } + } else if (action == MenuAction_Cancel) { + if(param2 == MenuCancel_ExitBack) { + DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +void + +int DeleteAll(int onlyPlayer = 0) { + int userid = onlyPlayer > 0 ? GetClientUserId(onlyPlayer) : 0; + int count; + for(int i = 0; i < g_spawnedItems.Length; i++) { + int ref = g_spawnedItems.Get(i); + int spawnedBy = g_spawnedItems.Get(i, 1); + // Skip if wishing to only delete certain items: + if(onlyPlayer != 0 && spawnedBy != userid) continue; + if(IsValidEntity(ref)) { + RemoveEntity(ref); + } + g_spawnedItems.Erase(i); + count++; + } + return count; +} + +int EditHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char info[8]; + menu.GetItem(param2, info, sizeof(info)); + int index = StringToInt(info); + int ref = g_spawnedItems.Get(index); + int entity = EntRefToEntIndex(ref); + if(entity > 0) { + WallBuilder[client].Import(entity, false); + PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d", entity); + } else { + PrintToChat(client, "\x04[Editor]\x01 Entity disappeared."); + g_spawnedItems.Erase(index); + index--; + } + ShowEditList(client, index); + } else if (action == MenuAction_Cancel) { + if(param2 == MenuCancel_ExitBack) { + DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + + +void ShowItemMenuAny(int client, ArrayList items, const char[] title = "") { + Menu itemMenu = new Menu(SpawnItemHandler); + if(title[0] != '\0') + itemMenu.SetTitle(title); + ItemData item; + char info[132]; + for(int i = 0; i < items.Length; i++) { + items.GetArray(i, item); + Format(info, sizeof(info), "%d|%s", i, item.model); + itemMenu.AddItem(info, item.name); + } + itemMenu.ExitBackButton = true; + itemMenu.ExitButton = true; + itemMenu.DisplayAt(client, g_lastItemIndex[client], MENU_TIME_FOREVER); +} + +void ShowItemMenu(int client, int index) { + if(g_lastCategoryIndex[client] != index) { + g_lastCategoryIndex[client] = index; + g_lastItemIndex[client] = 0; //Reset + } + CategoryData category; + g_categories.GetArray(index, category); + ShowItemMenuAny(client, category.items, category.name); +} + +int SpawnItemHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char info[132]; + menu.GetItem(param2, info, sizeof(info)); + char index[4]; + int modelIndex = SplitString(info, "|", index, sizeof(index)); + g_lastItemIndex[client] = StringToInt(index); + + if(WallBuilder[client].PreviewModel(info[modelIndex])) { + PrintToChat(client, "\x04[Editor]\x01 Spawning: \x04%s\x01", info[modelIndex+7]); + ShowHint(client); + } else { + PrintToChat(client, "\x04[Editor]\x01 Error spawning model \x01(%s)"); + } + + ShowItemMenu(client, g_lastCategoryIndex[client]); + } else if(action == MenuAction_Cancel) { + if(param2 == MenuCancel_ExitBack) { + ShowCategoryList(client); + } + } else if (action == MenuAction_End) { + delete menu; + } + return 0; +} + +#define SHOW_HINT_MIN_DURATION 600 // 600 s (10min) +void ShowHint(int client) { + int time = GetTime(); + if(time - g_lastShowedHint[client] < SHOW_HINT_MIN_DURATION) return; + PrintToChat(client, "\x05R: \x01Change Mode"); + PrintToChat(client, "\x05Middle Click: \x01Cancel Placement \x05Shift + Middle Click: \x01Place \x05Ctrl + Middle Click: \x01Change Type"); + PrintToChat(client, "\x05E: \x01Rotate (hold, use mouse) \x05Left Click: \x01Rotate Axis \x05Right Click: \x01Snap Angle"); + + g_lastShowedHint[client] = time; +} + +Action Command_Props(int client, int args) { + PrintToChat(client, "\x05Not implemented"); + return Plugin_Handled; } \ No newline at end of file diff --git a/scripting/include/hats/walls.sp b/scripting/include/hats/walls.sp index 742cff5..11a9458 100644 --- a/scripting/include/hats/walls.sp +++ b/scripting/include/hats/walls.sp @@ -5,12 +5,43 @@ int GLOW_WHITE[4] = { 255, 255, 255, 255 }; int GLOW_GREEN[4] = { 3, 252, 53 }; float ORIGIN_SIZE[3] = { 2.0, 2.0, 2.0 }; -enum wallMode { +enum editMode { INACTIVE = 0, MOVE_ORIGIN, SCALE, - FREELOOK + COLOR, + FREELOOK, } +char MODE_NAME[5][] = { + "Error", + "Move & Rotate", + "Scale", + "Color", + "Freelook" +} + +enum editFlag { + Edit_None, + Edit_Copy = 1, + Edit_Preview = 2, + Edit_WallCreator = 4 +} + +enum buildType { + Build_Solid, + Build_Physics, + Build_NonSolid, +} + +enum CompleteType { + Complete_WallSuccess, + Complete_WallError, + Complete_PropSpawned, + Complete_PropError, + Complete_EditSuccess +} + +char COLOR_INDEX[4] = "RGBA"; ArrayList createdWalls; @@ -19,25 +50,35 @@ enum struct WallBuilderData { float mins[3]; float angles[3]; float size[3]; - wallMode mode; + int color[4]; + int colorIndex; int axis; int snapAngle; int moveSpeed; float moveDistance; int entity; - bool canScale; bool hasCollision; - bool isCopy; + + editMode mode; + buildType buildType; + editFlag flags; void Reset(bool initial = false) { + // Clear previews + if(this.entity != INVALID_ENT_REFERENCE && this.flags & Edit_Preview) { + if(IsValidEntity(this.entity)) + RemoveEntity(this.entity); + } this.entity = INVALID_ENT_REFERENCE; - this.isCopy = false; this.size[0] = this.size[1] = this.size[2] = 5.0; this.angles[0] = this.angles[1] = this.angles[2] = 0.0; + this.color[0] = this.color[1] = this.color[2] = this.color[3] = 255; + this.colorIndex = 0; this.axis = 1; - this.canScale = true; this.moveDistance = 200.0; this.hasCollision = true; + this.flags = Edit_None; + this.buildType = Build_Solid; this.CalculateMins(); this.SetMode(INACTIVE); if(initial) { @@ -53,18 +94,27 @@ enum struct WallBuilderData { } void Draw(int color[4], float lifetime, float amplitude = 0.1) { - if(!this.canScale && this.entity != INVALID_ENT_REFERENCE) { - TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR); - } else { + if(this.flags & Edit_WallCreator || this.entity == INVALID_ENT_REFERENCE) { Effect_DrawBeamBoxRotatableToAll(this.origin, this.mins, this.size, this.angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, amplitude, color, 0); + } else { + TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR); } Effect_DrawAxisOfRotationToAll(this.origin, this.angles, ORIGIN_SIZE, g_iLaserIndex, 0, 0, 30, 0.2, 0.1, 0.1, 0, 0.0, 0); } + void UpdateEntity() { + int alpha = this.color[3]; + // Keep previews transparent + if(this.flags & Edit_Preview) { + alpha = 200; + } + SetEntityRenderColor(this.entity, this.color[0], this.color[1], this.color[2], alpha); + } + bool CheckEntity(int client) { if(this.entity != INVALID_ENT_REFERENCE) { if(!IsValidEntity(this.entity)) { - PrintToChat(client, "\x04[Hats]\x01 Entity has vanished, editing cancelled."); + PrintToChat(client, "\x04[Editor]\x01 Entity has vanished, editing cancelled."); this.Reset(); return false; } @@ -76,7 +126,7 @@ enum struct WallBuilderData { return this.mode != INACTIVE; } - void SetMode(wallMode mode) { + void SetMode(editMode mode) { this.mode = mode; } @@ -91,34 +141,36 @@ enum struct WallBuilderData { // - SCALE // - FREECAM case MOVE_ORIGIN: { - if(this.canScale) { + if(this.flags & Edit_WallCreator) { this.mode = SCALE; - PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Scale\x01 (Press \x04RELOAD\x01 to change mode)"); + } else if(this.flags & Edit_Preview) { + this.mode = COLOR; } else { this.mode = FREELOOK; - PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Freelook\x01 (Press \x04RELOAD\x01 to change mode)"); } } case SCALE: { this.mode = FREELOOK; - PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Freelook\x01 (Press \x04RELOAD\x01 to change mode)"); + } + case COLOR: { + this.mode = FREELOOK; } case FREELOOK: { this.mode = MOVE_ORIGIN; - PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Move & Rotate\x01 (Press \x04RELOAD\x01 to change mode)"); // PrintToChat(client, "Hold \x04USE (E)\x01 to rotate, \x04WALK (SHIFT)\x01 to change speed"); } } + PrintToChat(client, "\x04[Editor]\x01 Mode: \x05%s\x01 (Press \x04RELOAD\x01 to change mode)", MODE_NAME[this.mode]); cmdThrottle[client] = tick; } void ToggleCollision(int client, float tick) { - if(tick - cmdThrottle[client] <= 0.15) return; + if(tick - cmdThrottle[client] <= 0.25) return; this.hasCollision = !this.hasCollision if(this.hasCollision) - PrintToChat(client, "\x04[Hats]\x01 Collision: \x05ON\x01"); + PrintToChat(client, "\x04[Editor]\x01 Collision: \x05ON\x01"); else - PrintToChat(client, "\x04[Hats]\x01 Collision: \x04OFF\x01"); + PrintToChat(client, "\x04[Editor]\x01 Collision: \x04OFF\x01"); cmdThrottle[client] = tick; } @@ -126,13 +178,13 @@ enum struct WallBuilderData { if(tick - cmdThrottle[client] <= 0.15) return; if(this.axis == 0) { this.axis = 1; - PrintToChat(client, "\x04[Hats]\x01 Rotate Axis: \x05HEADING (Y)\x01"); + PrintToChat(client, "\x04[Editor]\x01 Rotate Axis: \x05HEADING (Y)\x01"); } else if(this.axis == 1) { this.axis = 2; - PrintToChat(client, "\x04[Hats]\x01 Rotate Axis: \x05PITCH (X)\x01"); + PrintToChat(client, "\x04[Editor]\x01 Rotate Axis: \x05PITCH (X)\x01"); } else { this.axis = 0; - PrintToChat(client, "\x04[Hats]\x01 Rotate Axis: \x05ROLL (Z)\x01"); + PrintToChat(client, "\x04[Editor]\x01 Rotate Axis: \x05ROLL (Z)\x01"); } cmdThrottle[client] = tick; } @@ -152,9 +204,9 @@ enum struct WallBuilderData { this.angles[2] = SnapTo(this.angles[2], float(this.snapAngle)); if(this.snapAngle == 1) - PrintToChat(client, "\x04[Hats]\x01 Rotate Snap Degrees: \x04(OFF)\x01", this.snapAngle); + PrintToChat(client, "\x04[Editor]\x01 Rotate Snap Degrees: \x04(OFF)\x01", this.snapAngle); else - PrintToChat(client, "\x04[Hats]\x01 Rotate Snap Degrees: \x05%d\x01", this.snapAngle); + PrintToChat(client, "\x04[Editor]\x01 Rotate Snap Degrees: \x05%d\x01", this.snapAngle); cmdThrottle[client] = tick; } @@ -162,21 +214,50 @@ enum struct WallBuilderData { if(tick - cmdThrottle[client] <= 0.25) return; this.moveSpeed++; if(this.moveSpeed > 10) this.moveSpeed = 1; - PrintToChat(client, "\x04[Hats]\x01 Scale Speed: \x05%d\x01", this.moveSpeed); - // if(this.movetype == 0) { - // this.movetype = 1; - // PrintToChat(client, "\x04[SM]\x01 Move Type: \x05HEADING (Y)\x01"); - // } else { - // this.movetype = 0; - // PrintToChat(client, "\x04[SM]\x01 Rotate Axis: \x05PITCH (X)\x01"); - // } + PrintToChat(client, "\x04[Editor]\x01 Scale Speed: \x05%d\x01", this.moveSpeed); cmdThrottle[client] = tick; } - int Build() { - if(!this.canScale) { + void CycleBuildType(int client) { + // No tick needed, is handled externally + if(this.buildType == Build_Physics) { + this.buildType = Build_Solid; + PrintToChat(client, "\x04[Editor]\x01 Spawn as: \x05Solid\x01"); + } else if(this.buildType == Build_Solid) { + this.buildType = Build_Physics; + PrintToChat(client, "\x04[Editor]\x01 Spawn as: \x05Physics\x01"); + } else { + this.buildType = Build_NonSolid; + PrintToChat(client, "\x04[Editor]\x01 Spawn as: \x05Non Solid\x01"); + } + } + + void CycleColorComponent(int client, float tick) { + if(tick - cmdThrottle[client] <= 0.25) return; + this.colorIndex++; + if(this.colorIndex > 3) this.colorIndex = 0; + PrintToChat(client, "\x04[Editor]\x01 Color Component: \x05%c\x01", COLOR_INDEX[this.colorIndex]); + cmdThrottle[client] = tick; + } + + + CompleteType Create(int& entity) { + if(this.flags & Edit_WallCreator) { + return this._FinishWall(entity) ? Complete_WallSuccess : Complete_WallError; + } else if(this.flags & Edit_Preview) { + return this._FinishPreview(entity) ? Complete_PropSpawned : Complete_PropError; + } else { + // Is edit, do nothing, just reset this.Reset(); - return -3; + entity = 0; + return Complete_EditSuccess; + } + } + + bool _FinishWall(int& id) { + if(~this.flags & Edit_WallCreator) { + this.Reset(); + return false; } // Don't need to build a new one if we editing: int blocker = this.entity; @@ -186,7 +267,7 @@ enum struct WallBuilderData { isEdit = true; } blocker = CreateEntityByName("func_brush"); - if(blocker == -1) return -1; + if(blocker == -1) return false; DispatchKeyValueVector(blocker, "mins", this.mins); DispatchKeyValueVector(blocker, "maxs", this.size); DispatchKeyValueVector(blocker, "boxmins", this.mins); @@ -203,7 +284,7 @@ enum struct WallBuilderData { DispatchKeyValue(blocker, "targetname", name); // DispatchKeyValue(blocker, "excludednpc", "player"); TeleportEntity(blocker, this.origin, this.angles, NULL_VECTOR); - if(!DispatchSpawn(blocker)) return -1; + if(!DispatchSpawn(blocker)) return false; SetEntPropVector(blocker, Prop_Send, "m_vecMins", this.mins); SetEntPropVector(blocker, Prop_Send, "m_vecMaxs", this.size); SetEntProp(blocker, Prop_Send, "m_nSolidType", 2); @@ -215,7 +296,42 @@ enum struct WallBuilderData { this.Draw(GLOW_GREEN, 5.0, 1.0); this.Reset(); - return isEdit ? -2 : createdWalls.Push(EntIndexToEntRef(blocker)); + if(!isEdit) { + id = createdWalls.Push(EntIndexToEntRef(blocker)); + } + return true; + } + + bool _FinishPreview(int& entity) { + entity = -1; + if(this.buildType == Build_Physics) + entity = CreateEntityByName("prop_physics"); + else + entity = CreateEntityByName("prop_dynamic"); + if(entity == -1) return false; + + char model[128]; + GetEntPropString(this.entity, Prop_Data, "m_ModelName", model, sizeof(model)); + DispatchKeyValue(entity, "model", model); + DispatchKeyValue(entity, "targetname", "prop_preview"); + if(this.buildType == Build_NonSolid) + DispatchKeyValue(entity, "solid", "0"); + else + DispatchKeyValue(entity, "solid", "6"); + TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); + if(!DispatchSpawn(entity)) { + return false; + } + SetEntityRenderColor(entity, this.color[0], this.color[1], this.color[2], this.color[3]); + SetEntityRenderColor(this.entity, 255, 128, 255, 200); // reset ghost + GlowEntity(entity, 1.4) + + if(this.mode == FREELOOK) + this.SetMode(MOVE_ORIGIN); + + + // Don't kill preview until cancel + return true; } int Copy() { @@ -239,11 +355,38 @@ enum struct WallBuilderData { // SetEntProp(entity, Prop_Send, "m_nSolidType", 6); TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); this.entity = entity; - this.isCopy = true; + this.flags |= Edit_Copy; return entity; } - void Import(int entity, bool makeCopy = false, wallMode mode = SCALE) { + void StartWall() { + this.Reset(); + this.flags |= Edit_WallCreator; + } + + bool PreviewModel(const char[] model) { + // If last entity was preview, kill it + PrecacheModel(model); + this.Reset(); + int entity = CreateEntityByName("prop_dynamic"); + if(entity == -1) return false; + DispatchKeyValue(entity, "model", model); + DispatchKeyValue(entity, "targetname", "prop_preview"); + DispatchKeyValue(entity, "solid", "0"); + DispatchKeyValue(entity, "rendercolor", "255 128 255"); + DispatchKeyValue(entity, "renderamt", "200"); + DispatchKeyValue(entity, "rendermode", "1"); + if(!DispatchSpawn(entity)) { + return false; + } + this.entity = entity; + this.flags |= (Edit_Copy | Edit_Preview); + this.SetMode(MOVE_ORIGIN); + // Seems some entities fail here: + return IsValidEntity(entity); + } + + void Import(int entity, bool makeCopy = false, editMode mode = SCALE) { this.Reset(); GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); @@ -257,13 +400,65 @@ enum struct WallBuilderData { void Cancel() { // Delete any copies: - if(this.isCopy) { + if(this.flags & Edit_Copy || this.flags & Edit_Preview) { RemoveEntity(this.entity); } this.SetMode(INACTIVE); } + + // void OpenSettings(int client) { + // Menu menu = new Menu(SettingsHandler); + // menu.AddItem("color", "Change Color"); + // menu.AddItem("type", "Change Spawn Type"); + // // if(this.flags & Edit_Scale) + // menu.ExitButton = true; + // // Only show back if we are for a preview + // menu.ExitBackButton = (this.flags & Edit_Preview) != 0; + // menu.Display(client, MENU_TIME_FOREVER); + // } } +WallBuilderData WallBuilder[MAXPLAYERS+1]; + +// int SettingsHandler(Menu menu, MenuAction action, int client, int param2) { +// if (action == MenuAction_Select) { +// char info[8]; +// menu.GetItem(param2, info, sizeof(info)); +// if(StrEqual(info, "color")) { +// Menu newMenu = new Menu(ColorHandler); +// newMenu.AddItem() +// } else if(StrEqual(info, "type")) { + +// } else { +// CPrintToChat(client, "\x04[Editor]\x01 Error: Unknown setting \x05%s", info); +// } + +// // Re-open category list when done editing setting +// if(WallBuilder[client].flags & Edit_Preview) { +// ShowItemMenu(client, g_lastCategoryIndex[client]); +// } +// } else if(action == MenuAction_Cancel) { +// if(WallBuilder[client].flags & Edit_Preview) { +// ShowItemMenu(client, g_lastCategoryIndex[client]); +// } +// } else if (action == MenuAction_End) +// delete menu; +// return 0; +// } + +// int ColorHandler(Menu menu, MenuAction action, int client, int param2) { +// if (action == MenuAction_Select) { +// char info[8]; + +// } else if(action == MenuAction_Cancel) { +// if(WallBuilder[client].flags & Edit_Preview) { +// ShowItemMenu(client, g_lastCategoryIndex[client]); +// } +// } else if (action == MenuAction_End) +// delete menu; +// return 0; +// } + Action OnWallClicked(int entity, int activator, int caller, UseType type, float value) { int wallId = FindWallId(entity); if(wallId > 0) { @@ -275,15 +470,14 @@ Action OnWallClicked(int entity, int activator, int caller, UseType type, float return Plugin_Continue; } -WallBuilderData WallBuilder[MAXPLAYERS+1]; // TODO: Stacker, copy tool, new command? public Action Command_MakeWall(int client, int args) { if(WallBuilder[client].IsActive()) { - ReplyToCommand(client, "\x04[Hats]\x01 You are currently editing an entity. Finish editing your current entity with with \x05/edit done\x01 or cancel with \x04/edit cancel\x01"); + ReplyToCommand(client, "\x04[Editor]\x01 You are currently editing an entity. Finish editing your current entity with with \x05/edit done\x01 or cancel with \x04/edit cancel\x01"); } else { - WallBuilder[client].Reset(); + WallBuilder[client].StartWall(); if(args > 0) { // Get values for X, Y and Z axis (defaulting to 1.0): char arg2[8]; @@ -311,15 +505,15 @@ public Action Command_MakeWall(int client, int args) { WallBuilder[client].SetMode(SCALE); GetCursorLimited(client, 100.0, WallBuilder[client].origin, Filter_IgnorePlayer); - PrintToChat(client, "\x04[Hats]\x01 New Wall Started. End with \x05/wall build\x01 or \x04/wall cancel\x01"); - PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Scale\x01"); + PrintToChat(client, "\x04[Editor]\x01 New Wall Started. End with \x05/wall build\x01 or \x04/wall cancel\x01"); + PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); } return Plugin_Handled; } public Action Command_ManageWalls(int client, int args) { if(args == 0) { - PrintToChat(client, "\x04[Hats]\x01 Created Walls: \x05%d\x01", createdWalls.Length); + PrintToChat(client, "\x04[Editor]\x01 Created Walls: \x05%d\x01", createdWalls.Length); for(int i = 1; i <= createdWalls.Length; i++) { GlowWall(i, GLOW_WHITE, 20.0); } @@ -332,22 +526,36 @@ public Action Command_ManageWalls(int client, int args) { // Remove frozen flag from user, as some modes use this int flags = GetEntityFlags(client) & ~FL_FROZEN; SetEntityFlags(client, flags); - - int id = WallBuilder[client].Build(); - if(id == -1) { - PrintToChat(client, "\x04[Hats]\x01 Wall Creation: \x04Error\x01"); - } else if(id == -2) { - PrintToChat(client, "\x04[Hats]\x01 Wall Edit: \x04Complete\x01"); - } else if(id == -3) { - PrintToChat(client, "\x04[Hats]\x01 Entity Edit: \x04Complete\x01"); - } else { - PrintToChat(client, "\x04[Hats]\x01 Wall Creation: \x05Wall #%d Created\x01", id + 1); + + int entity; + CompleteType result = WallBuilder[client].Create(entity); + switch(result) { + case Complete_WallSuccess: { + if(entity > 0) + PrintToChat(client, "\x04[Editor]\x01 Wall Creation: \x05Wall #%d Created\x01", entity + 1); + else + PrintToChat(client, "\x04[Editor]\x01 Wall Edit: \x04Complete\x01"); + } + case Complete_PropSpawned: + PrintToChat(client, "\x04[Editor]\x01 Prop Spawned: \x04%d\x01", entity); + + case Complete_EditSuccess: + PrintToChat(client, "\x04[Editor]\x01 Entity Edited: \x04%d\x01", entity); + + default: + PrintToChat(client, "\x04[Editor]\x01 Unknown result"); } } else if(StrEqual(arg1, "cancel")) { int flags = GetEntityFlags(client) & ~FL_FROZEN; SetEntityFlags(client, flags); WallBuilder[client].Cancel(); - PrintToChat(client, "\x04[Hats]\x01 Wall Creation: \x04Cancelled\x01"); + if(WallBuilder[client].flags & Edit_Preview) + PrintToChat(client, "\x04[Editor]\x01 Prop Spawer: \x04Done\x01"); + else if(WallBuilder[client].flags & Edit_WallCreator) { + PrintToChat(client, "\x04[Editor]\x01 Wall Creation: \x04Cancelled\x01"); + } else { + PrintToChat(client, "\x04[Editor]\x01 Entity Edit: \x04Cancelled\x01"); + } } else if(StrEqual(arg1, "export")) { // TODO: support exp #id float origin[3], angles[3], size[3]; @@ -372,29 +580,29 @@ public Action Command_ManageWalls(int client, int args) { if(WallBuilder[client].IsActive() && args == 1) { int entity = WallBuilder[client].entity; if(IsValidEntity(entity)) { - PrintToChat(client, "\x04[Hats]\x01 You are not editing any existing entity, use \x05/wall cancel\x01 to stop or \x05/wall delete "); + PrintToChat(client, "\x04[Editor]\x01 You are not editing any existing entity, use \x05/wall cancel\x01 to stop or \x05/wall delete "); } else if(entity > MaxClients) { RemoveEntity(entity); WallBuilder[client].Reset(); - PrintToChat(client, "\x04[Hats]\x01 Deleted current entity"); + PrintToChat(client, "\x04[Editor]\x01 Deleted current entity"); } else { - PrintToChat(client, "\x04[Hats]\x01 Cannot delete player entities."); + PrintToChat(client, "\x04[Editor]\x01 Cannot delete player entities."); } } else if(StrEqual(arg2, "all")) { int walls = createdWalls.Length; for(int i = 1; i <= createdWalls.Length; i++) { DeleteWall(i); } - PrintToChat(client, "\x04[Hats]\x01 Deleted \x05%d\x01 Walls", walls); + PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 Walls", walls); } else { int id = GetWallId(client, arg2); if(id > -1) { DeleteWall(id); - PrintToChat(client, "\x04[Hats]\x01 Deleted Wall: \x05#%d\x01", id); + PrintToChat(client, "\x04[Editor]\x01 Deleted Wall: \x05#%d\x01", id); } } } else if(StrEqual(arg1, "create")) { - ReplyToCommand(client, "\x04[Hats]\x01 Syntax: /mkwall [size x] [size y] [size z]"); + ReplyToCommand(client, "\x04[Editor]\x01 Syntax: /mkwall [size x] [size y] [size z]"); } else if(StrEqual(arg1, "toggle")) { if(StrEqual(arg2, "all")) { int walls = createdWalls.Length; @@ -403,20 +611,20 @@ public Action Command_ManageWalls(int client, int args) { AcceptEntityInput(entity, "Toggle"); GlowWall(i, GLOW_BLUE); } - PrintToChat(client, "\x04[Hats]\x01 Toggled \x05%d\x01 walls", walls); + PrintToChat(client, "\x04[Editor]\x01 Toggled \x05%d\x01 walls", walls); } else { int id = GetWallId(client, arg2); if(id > -1) { int entity = GetWallEntity(id); AcceptEntityInput(entity, "Toggle"); GlowWall(id, GLOW_BLUE); - PrintToChat(client, "\x04[Hats]\x01 Toggled Wall: \x05#%d\x01", id); + PrintToChat(client, "\x04[Editor]\x01 Toggled Wall: \x05#%d\x01", id); } } } else if(StrEqual(arg1, "filter")) { if(args < 3) { - ReplyToCommand(client, "\x04[Hats]\x01 Syntax: \x05/walls filter \x04"); - ReplyToCommand(client, "\x04[Hats]\x01 Valid filters: \x05player"); + ReplyToCommand(client, "\x04[Editor]\x01 Syntax: \x05/walls filter \x04"); + ReplyToCommand(client, "\x04[Editor]\x01 Valid filters: \x05player"); return Plugin_Handled; } @@ -430,13 +638,13 @@ public Action Command_ManageWalls(int client, int args) { int entity = GetWallEntity(i); AcceptEntityInput(entity, "SetExcluded"); } - PrintToChat(client, "\x04[Hats]\x01 Set %d walls' filter to \x05%s\x01", walls, arg3); + PrintToChat(client, "\x04[Editor]\x01 Set %d walls' filter to \x05%s\x01", walls, arg3); } else { int id = GetWallId(client, arg2); if(id > -1) { int entity = GetWallEntity(id); AcceptEntityInput(entity, "SetExcluded"); - PrintToChat(client, "\x04[Hats]\x01 Set wall #%d filter to \x05%s\x01", id, arg3); + PrintToChat(client, "\x04[Editor]\x01 Set wall #%d filter to \x05%s\x01", id, arg3); } } } else if(StrEqual(arg1, "edit")) { @@ -444,31 +652,30 @@ public Action Command_ManageWalls(int client, int args) { if(id > -1) { int entity = GetWallEntity(id); WallBuilder[client].Import(entity); - PrintToChat(client, "\x04[Hats]\x01 Editing wall \x05%d\x01. End with \x05/wall done\x01 or \x04/wall cancel\x01", id); - PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Scale\x01"); + PrintToChat(client, "\x04[Editor]\x01 Editing wall \x05%d\x01. End with \x05/wall done\x01 or \x04/wall cancel\x01", id); + PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); } } else if(StrEqual(arg1, "edite") || (arg1[0] == 'c' && arg1[1] == 'u')) { int index = GetLookingEntity(client, Filter_ValidHats); //GetClientAimTarget(client, false); if(index > 0) { WallBuilder[client].Import(index, false, MOVE_ORIGIN); - WallBuilder[client].canScale = false; char classname[32]; char targetname[32]; GetEntityClassname(index, classname, sizeof(classname)); GetEntPropString(index, Prop_Data, "m_target", targetname, sizeof(targetname)); - PrintToChat(client, "\x04[Hats]\x01 Editing entity \x05%d (%s) [%s]\x01. End with \x05/wall done\x01", index, classname, targetname); - PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Move & Rotate\x01"); + PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d (%s) [%s]\x01. End with \x05/wall done\x01", index, classname, targetname); + PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Move & Rotate\x01"); } else { - ReplyToCommand(client, "\x04[Hats]\x01 Invalid or non existent entity"); + ReplyToCommand(client, "\x04[Editor]\x01 Invalid or non existent entity"); } } else if(StrEqual(arg1, "copy")) { if(WallBuilder[client].IsActive()) { int oldEntity = WallBuilder[client].entity; if(oldEntity == INVALID_ENT_REFERENCE) { - PrintToChat(client, "\x04[Hats]\x01 Finish editing your wall first: \x05/wall done\x01 or \x04/wall cancel\x01"); + PrintToChat(client, "\x04[Editor]\x01 Finish editing your wall first: \x05/wall done\x01 or \x04/wall cancel\x01"); } else { int entity = WallBuilder[client].Copy(); - PrintToChat(client, "\x04[Hats]\x01 Editing copy \x05%d\x01 of entity \x05%d\x01. End with \x05/edit done\x01 or \x04/edit cancel\x01", entity, oldEntity); + PrintToChat(client, "\x04[Editor]\x01 Editing copy \x05%d\x01 of entity \x05%d\x01. End with \x05/edit done\x01 or \x04/edit cancel\x01", entity, oldEntity); } } else { int id = GetWallId(client, arg2); @@ -476,8 +683,8 @@ public Action Command_ManageWalls(int client, int args) { int entity = GetWallEntity(id); WallBuilder[client].Import(entity, true); GetCursorLimited(client, 100.0, WallBuilder[client].origin, Filter_IgnorePlayer); - PrintToChat(client, "\x04[Hats]\x01 Editing copy of wall \x05%d\x01. End with \x05/wall build\x01 or \x04/wall cancel\x01", id); - PrintToChat(client, "\x04[Hats]\x01 Mode: \x05Scale\x01"); + PrintToChat(client, "\x04[Editor]\x01 Editing copy of wall \x05%d\x01. End with \x05/wall build\x01 or \x04/wall cancel\x01", id); + PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); } } } else if(StrEqual(arg1, "list")) { @@ -486,7 +693,7 @@ public Action Command_ManageWalls(int client, int args) { ReplyToCommand(client, "Wall #%d - EntIndex: %d", i, EntRefToEntIndex(entity)); } } else { - ReplyToCommand(client, "\x04[Hats]\x01 See console for list of commands"); + ReplyToCommand(client, "\x04[Editor]\x01 See console for list of commands"); GetCmdArg(0, arg1, sizeof(arg1)); PrintToConsole(client, "%s done / build - Finishes editing, creates wall if making wall", arg1); PrintToConsole(client, "%s cancel - Cancels editing (for entity edits is same as done)", arg1); @@ -506,13 +713,13 @@ int GetWallId(int client, const char[] arg) { if(StringToIntEx(arg, id) > 0 && id > 0 && id <= createdWalls.Length) { int entity = GetWallEntity(id); if(!IsValidEntity(entity)) { - ReplyToCommand(client, "\x04[Hats]\x01 The wall with specified id no longer exists."); + ReplyToCommand(client, "\x04[Editor]\x01 The wall with specified id no longer exists."); createdWalls.Erase(id - 1); return -2; } return id; } else { - ReplyToCommand(client, "\x04[Hats]\x01 Invalid wall id, must be between 0 - %d", createdWalls.Length - 1 ); + ReplyToCommand(client, "\x04[Editor]\x01 Invalid wall id, must be between 0 - %d", createdWalls.Length - 1 ); return -1; } } @@ -567,7 +774,7 @@ void DeleteWall(int id) { BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports/%s.cfg", currentMap); File file = OpenFile(sPath, "w"); if(file == null) { - PrintToServer("[Hats] Export: Cannot open \"%s\", cant write", sPath); + PrintToServer("[Editor] Export: Cannot open \"%s\", cant write", sPath); } PrintWriteLine(client, file, "{"); @@ -600,59 +807,4 @@ void PrintWriteLine(int client, File file, const char[] format, any ...) { if(file != null) file.WriteLine(line); PrintToChat(client, line); -} - -enum struct WallModelSizeEntry { - char name[32]; - char model[64]; -} -enum struct WallModelEntry { - char name[32]; - - WallModelSizeEntry size1; - WallModelSizeEntry size2; - WallModelSizeEntry size3; -} -ArrayList wallModels; - -void LoadModels() { - if(wallModels != null) delete wallModels; - wallModels = new ArrayList(sizeof(WallModelEntry)); - KeyValues kv = new KeyValues("WallData"); - - char sPath[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, sPath, sizeof(sPath), "data/walls_data.cfg"); - - if(!FileExists(sPath) || !kv.ImportFromFile(sPath)) { - delete kv; - PrintToServer("[FTT] Could not load phrase list from data/walls_data.cfg"); - return; - } - // TODO: implement models to spawn - // char name[32]; - // Go through all the words: - // kv.GotoFirstSubKey(); - // int i = 0; - // char buffer[4]; - // do { - // kv.GetSectionName(name, sizeof(name)); - // for(;;) { - // IntToString(++i, buffer, sizeof(buffer)); - // kv.GetString(buffer, phrase, MAX_PHRASE_LENGTH, "_null"); - // if(strcmp(phrase, "_null") == 0) break; - // phrases.PushString(phrase); - // } - // i = 0; - // if(StrEqual(word, "_full message phrases")) { - // fullMessagePhraseList = phrases.Clone(); - // continue; - // } - // #if defined DEBUG_PHRASE_LOAD - // PrintToServer("Loaded %d phrases for word \"%s\"", phrases.Length, word); - // #endif - // REPLACEMENT_PHRASES.SetValue(word, phrases.Clone(), true); - // } while (kv.GotoNextKey(false)); - - delete kv; -} - \ No newline at end of file +} \ No newline at end of file diff --git a/scripting/include/jutils.inc b/scripting/include/jutils.inc index 2c9460a..d8bd945 100644 --- a/scripting/include/jutils.inc +++ b/scripting/include/jutils.inc @@ -913,8 +913,8 @@ stock void GlowPoint(const float pos[3], float lifetime = 5.0) { CreateTimer(lifetime, Timer_KillEntity, entity); } -stock void GlowEntity(int entity, float lifetime = 10.0) { - L4D2_SetEntityGlow(entity, L4D2Glow_Constant, 10000, 0, _glowColor, false); +stock void GlowEntity(int entity, float lifetime = 10.0, int glowColor[3] = _glowColor) { + L4D2_SetEntityGlow(entity, L4D2Glow_Constant, 10000, 0, glowColor, false); CreateTimer(lifetime, Timer_ClearGlow, EntIndexToEntRef(entity)); } diff --git a/scripting/l4d2_hats.sp b/scripting/l4d2_hats.sp index 8f7d82e..e798a18 100644 --- a/scripting/l4d2_hats.sp +++ b/scripting/l4d2_hats.sp @@ -38,6 +38,13 @@ ConVar cvar_sm_hats_max_distance; TopMenu g_topMenu; +int g_lastCategoryIndex[MAXPLAYERS+1]; +int g_lastItemIndex[MAXPLAYERS+1]; +int g_lastShowedHint[MAXPLAYERS+1]; +bool g_isSearchActive[MAXPLAYERS+1]; + +char g_currentMap[64]; + #include #include #include @@ -62,6 +69,7 @@ public void OnPluginStart() { } createdWalls = new ArrayList(); + g_spawnedItems = new ArrayList(2); LoadTranslations("common.phrases"); HookEvent("player_entered_checkpoint", OnEnterSaferoom); @@ -75,6 +83,7 @@ public void OnPluginStart() { RegAdminCmd("sm_mkwall", Command_MakeWall, ADMFLAG_CHEATS); RegAdminCmd("sm_walls", Command_ManageWalls, ADMFLAG_CHEATS); RegAdminCmd("sm_wall", Command_ManageWalls, ADMFLAG_CHEATS); + RegAdminCmd("sm_prop", Command_Props, ADMFLAG_CHEATS); RegConsoleCmd("sm_hatp", Command_DoAHatPreset); cvar_sm_hats_blacklist_enabled = CreateConVar("sm_hats_blacklist_enabled", "1", "Is the prop blacklist enabled", FCVAR_NONE, true, 0.0, true, 1.0); @@ -106,6 +115,11 @@ public void OnPluginStart() { } LoadPresets(); + + TopMenu topMenu; + if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) { + OnAdminMenuReady(topMenu); + } } public void OnLibraryRemoved(const char[] name) { @@ -116,23 +130,6 @@ public void OnLibraryRemoved(const char[] name) { /////////////////////////////////////////////////////////////////////////////////////////////// -public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); - // Check if an item picked up a user's hat and do nothing... - // for(int slot = 0; slot <= 5; slot++) { - // int wpn = GetPlayerWeaponSlot(client, slot); - // for(int i = 1; i <= MaxClients; i++) { - // if(i != client && IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { - // int hat = GetHat(i); - // if(hat == wpn) { - // break; - // } - // } - // } - // } -} - - public void OnEnterSaferoom(Event event, const char[] name, bool dontBroadcast) { int userid = event.GetInt("userid"); int client = GetClientOfUserId(userid); @@ -172,12 +169,6 @@ Action Timer_PlaceHat(Handle h, int userid) { return Plugin_Handled; } -Action Timer_Kill(Handle h, int entity) { - if(IsValidEntity(entity)) - RemoveEntity(entity); - return Plugin_Handled; -} - // Tries to find a valid location at user's cursor, avoiding placing into solid walls (such as invisible walls) or objects stock bool GetSmartCursorLocation(int client, float outPos[3]) { float start[3], angle[3], ceilPos[3], wallPos[3], normal[3]; @@ -199,7 +190,7 @@ stock bool GetSmartCursorLocation(int client, float outPos[3]) { bool ceilCollided = TR_DidHit(); bool ceilOK = !TR_AllSolid(); TR_GetEndPosition(ceilPos); - float distCeil = GetVectorDistance(outPos, ceilPos, true); + // float distCeil = GetVectorDistance(outPos, ceilPos, true); // Find a suitable position backwards angle[0] = 70.0; @@ -334,7 +325,7 @@ void CheckKill(int ref, int client) { Action Timer_PropYeetEnd(Handle h, DataPack pack) { pack.Reset(); int realEnt = EntRefToEntIndex(pack.ReadCell()); - int visibleEnt = EntRefToEntIndex(pack.ReadCell()); + // int visibleEnt = EntRefToEntIndex(pack.ReadCell()); // if(IsValidEntity(visibleEnt)) { // float pos[3], ang[3]; // GetEntPropVector(visibleEnt, Prop_Send, "m_vecOrigin", pos); @@ -488,7 +479,7 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 ClearParent(entity); // If hat is not a player, we teleport them to the void (0, 0, 0) - // Otherwise, we just simply dismount the player while hatter is on ladder + // Ostherwise, we just simply dismount the player while hatter is on ladder if(entity >= MaxClients) TeleportEntity(entity, EMPTY_ANG, NULL_VECTOR, NULL_VECTOR); if(visibleEntity > 0) { @@ -584,30 +575,35 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 bool allowMove = true; switch(WallBuilder[client].mode) { case MOVE_ORIGIN: { + SetWeaponDelay(client, 0.5); bool isRotate; int flags = GetEntityFlags(client); - if(buttons & IN_USE) { - PrintCenterText(client, "%.1f %.1f %.1f", WallBuilder[client].angles[0], WallBuilder[client].angles[1], WallBuilder[client].angles[2]); - isRotate = true; - SetEntityFlags(client, flags |= FL_FROZEN); - if(buttons & IN_ATTACK) WallBuilder[client].CycleAxis(client, tick); - else if(buttons & IN_ATTACK2) WallBuilder[client].CycleSnapAngle(client, tick); - - // Rotation control: - if(tick - cmdThrottle[client] > 0.20) { - if(WallBuilder[client].axis == 0) { - if(mouse[1] > 10) WallBuilder[client].angles[0] += WallBuilder[client].snapAngle; - else if(mouse[1] < -10) WallBuilder[client].angles[0] -= WallBuilder[client].snapAngle; - } else if(WallBuilder[client].axis == 1) { - if(mouse[0] > 10) WallBuilder[client].angles[1] += WallBuilder[client].snapAngle; - else if(mouse[0] < -10) WallBuilder[client].angles[1] -= WallBuilder[client].snapAngle; - } else { - if(mouse[1] > 10) WallBuilder[client].angles[2] += WallBuilder[client].snapAngle; - else if(mouse[1] < -10) WallBuilder[client].angles[2] -= WallBuilder[client].snapAngle; + if(buttons & IN_USE && ~buttons & IN_ZOOM) { + if(buttons & IN_SPEED) { + WallBuilder[client].ToggleCollision(client, tick); + } else { + PrintCenterText(client, "%.1f %.1f %.1f", WallBuilder[client].angles[0], WallBuilder[client].angles[1], WallBuilder[client].angles[2]); + isRotate = true; + SetEntityFlags(client, flags |= FL_FROZEN); + if(buttons & IN_ATTACK) WallBuilder[client].CycleAxis(client, tick); + else if(buttons & IN_ATTACK2) WallBuilder[client].CycleSnapAngle(client, tick); + + // Rotation control: + if(tick - cmdThrottle[client] > 0.20) { + if(WallBuilder[client].axis == 0) { + if(mouse[1] > 10) WallBuilder[client].angles[0] += WallBuilder[client].snapAngle; + else if(mouse[1] < -10) WallBuilder[client].angles[0] -= WallBuilder[client].snapAngle; + } else if(WallBuilder[client].axis == 1) { + if(mouse[0] > 10) WallBuilder[client].angles[1] += WallBuilder[client].snapAngle; + else if(mouse[0] < -10) WallBuilder[client].angles[1] -= WallBuilder[client].snapAngle; + } else { + if(mouse[1] > 10) WallBuilder[client].angles[2] += WallBuilder[client].snapAngle; + else if(mouse[1] < -10) WallBuilder[client].angles[2] -= WallBuilder[client].snapAngle; + } + cmdThrottle[client] = tick; } - cmdThrottle[client] = tick; } } else { // Move position @@ -620,9 +616,6 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 flags = flags & ~FL_FROZEN; SetEntityFlags(client, flags); } - if(buttons & IN_SPEED) { - WallBuilder[client].ToggleCollision(client, tick); - } GetCursorLimited2(client, WallBuilder[client].moveDistance, WallBuilder[client].origin, Filter_IgnorePlayerAndWall, WallBuilder[client].hasCollision); } @@ -659,10 +652,44 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 WallBuilder[client].CalculateMins(); } } + case COLOR: { + SetWeaponDelay(client, 0.5); + PrintCenterText(client, "%d %d %d %d", WallBuilder[client].color[0], WallBuilder[client].color[1], WallBuilder[client].color[2], WallBuilder[client].color[3]); + if(buttons & IN_USE) { + WallBuilder[client].CycleColorComponent(client, tick); + } else if(buttons & IN_ATTACK) { + WallBuilder[client].color[WallBuilder[client].colorIndex]--; + if(WallBuilder[client].color[WallBuilder[client].colorIndex] < 0) { + WallBuilder[client].color[WallBuilder[client].colorIndex] = 0; + } + WallBuilder[client].UpdateEntity(); + allowMove = false; + } else if(buttons & IN_ATTACK2) { + WallBuilder[client].color[WallBuilder[client].colorIndex]++; + if(WallBuilder[client].color[WallBuilder[client].colorIndex] > 255) { + WallBuilder[client].color[WallBuilder[client].colorIndex] = 255; + } + WallBuilder[client].UpdateEntity(); + allowMove = false; + } + } } - switch(buttons) { - case IN_RELOAD: WallBuilder[client].CycleMode(client, tick); // R: Cycle forward + if(buttons & IN_RELOAD) + WallBuilder[client].CycleMode(client, tick); // R: Cycle forward + else if(WallBuilder[client].flags & Edit_Preview && tick - cmdThrottle[client] >= 0.25 && buttons & IN_ZOOM) { + if(buttons & IN_SPEED) { + int entity; + WallBuilder[client].Create(entity); + int index = g_spawnedItems.Push(EntIndexToEntRef(entity)); + g_spawnedItems.Set(index, GetClientUserId(client), 1); + } else if(buttons & IN_DUCK) { + WallBuilder[client].CycleBuildType(client); + } else { + WallBuilder[client].Cancel(); + CPrintToChat(client, "\x04[Editor]\x01 Cancelled."); + } + cmdThrottle[client] = tick; } WallBuilder[client].Draw(BUILDER_COLOR, 0.1, 0.1); @@ -723,6 +750,10 @@ public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast public void OnClientDisconnect(int client) { tempGod[client] = false; WallBuilder[client].Reset(); + g_isSearchActive[client] = false; + g_lastCategoryIndex[client] = 0; + g_lastItemIndex[client] = 0; + g_lastShowedHint[client] = 0; hatData[client].yeetGroundTimer = null; ClearHat(client, true); } @@ -747,12 +778,14 @@ public void OnMapStart() { cmdThrottle[i] = 0.0; tempGod[i] = false; } + GetCurrentMap(g_currentMap, sizeof(g_currentMap)); NavAreas = GetSpawnLocations(); } public void OnMapEnd() { delete NavAreas; + g_spawnedItems.Clear(); for(int i = 1; i <= createdWalls.Length; i++) { if(hatData[i].yeetGroundTimer != null) { delete hatData[i].yeetGroundTimer; @@ -762,6 +795,8 @@ public void OnMapEnd() { } createdWalls.Clear(); ClearHats(); + UnloadCategories(); + UnloadSave(); } public void OnPluginEnd() { ClearHats(); @@ -771,6 +806,14 @@ public void OnPluginEnd() { SetEntityFlags(i, flags); } } + if(g_spawnedItems != null) { + for(int i = 0; i < g_spawnedItems.Length; i++) { + int ref = g_spawnedItems.Get(i); + RemoveEntity(ref); + } + delete g_spawnedItems; + } + TriggerInput("prop_preview", "Kill"); } public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) { @@ -780,7 +823,6 @@ public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) { return entity != data; } - int GetLookingEntity(int client, TraceEntityFilter filter) { static float pos[3], ang[3]; GetClientEyePosition(client, pos); @@ -844,7 +886,16 @@ bool CheckBlacklist(int entity) { //////////////////////////////// - +stock void TriggerInput(const char[] targetName, const char[] input) { + int entity = -1; + char _targetName[32]; + while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", _targetName, sizeof(_targetName)); + if(StrEqual(_targetName, targetName)) { + AcceptEntityInput(entity, input); + } + } +} stock bool FindGround(const float start[3], float end[3]) {