From 613802344b8fad1f5fd79ad58ab79f81ac6112a3 Mon Sep 17 00:00:00 2001 From: Jackz Date: Wed, 24 Aug 2022 11:00:07 -0500 Subject: [PATCH] Work on prophunt --- plugins/l4d2_prophunt.smx | Bin 0 -> 8376 bytes scripting/include/prophunt/phcmds.inc | 333 ++++++++++++++++++++ scripting/include/prophunt/phcore.inc | 171 +++++++++++ scripting/include/prophunt/phents.inc | 128 ++++++++ scripting/include/prophunt/phgame.inc | 390 ++++++++++++++++++++++++ scripting/include/prophunt/phtimers.inc | 70 +++++ scripting/l4d2_prophunt.sp | 151 +++++++++ 7 files changed, 1243 insertions(+) create mode 100644 plugins/l4d2_prophunt.smx create mode 100644 scripting/include/prophunt/phcmds.inc create mode 100644 scripting/include/prophunt/phcore.inc create mode 100644 scripting/include/prophunt/phents.inc create mode 100644 scripting/include/prophunt/phgame.inc create mode 100644 scripting/include/prophunt/phtimers.inc create mode 100644 scripting/l4d2_prophunt.sp diff --git a/plugins/l4d2_prophunt.smx b/plugins/l4d2_prophunt.smx new file mode 100644 index 0000000000000000000000000000000000000000..afcdef1a06aa43af88f0348da62657e9e14d9a60 GIT binary patch literal 8376 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 literal 0 HcmV?d00001 diff --git a/scripting/include/prophunt/phcmds.inc b/scripting/include/prophunt/phcmds.inc new file mode 100644 index 0000000..6399ca5 --- /dev/null +++ b/scripting/include/prophunt/phcmds.inc @@ -0,0 +1,333 @@ +#define GAMEMODE_PROP_NAME "phprop" +#define GAMEMODE_BLOCKER_NAME "phblocker" + + + +public Action Command_PropHunt(int client, int args) { + if(!isEnabled) ReplyToCommand(client, "Warn: %s is not active", GAMEMODE_NAME); + if(args > 0) { + char subcmd[32]; + GetCmdArg(1, subcmd, sizeof(subcmd)); + if(StrEqual(subcmd, "r") || StrEqual(subcmd, "reload", false)) { + GetCurrentMap(g_currentMap, sizeof(g_currentMap)); + char arg[4]; + GetCmdArg(2, arg, sizeof(arg)); + if(ReloadMapDB()) { + if(!LoadConfigForMap(g_currentMap)) { + ReplyToCommand(client, "Warn: Map has no config file"); + } + Game.Cleanup(true); + if(arg[0] == 'f') { + InitGamemode(); + } + SetupEntities(Game.Blockers, Game.Props, Game.Portals); + ReplyToCommand(client, "Reloaded map from config"); + } else { + ReplyToCommand(client, "Error occurred while reloading map file"); + } + } else if(StrEqual(subcmd, "set", false)) { + char set[16]; + if(args == 1) { + ReplyToCommand(client, "Current Map Set: \"%s\" (Specify with /gw set )", g_currentSet); + if(validSets.Length == 0) ReplyToCommand(client, "Available Sets: (no map config found)"); + else { + ReplyToCommand(client, "Available Sets: "); + for(int i = 0; i < validSets.Length; i++) { + validSets.GetString(i, set, sizeof(set)); + ReplyToCommand(client, "%d. %s", i + 1, set); + } + } + } else { + GetCmdArg(2, g_currentSet, sizeof(g_currentSet)); + for(int i = 0; i < validSets.Length; i++) { + validSets.GetString(i, set, sizeof(set)); + if(StrEqual(set, g_currentSet)) { + if(!LoadConfigForMap(g_currentMap)) { + ReplyToCommand(client, "Warn: No config entry for %s", g_currentMap); + } + Game.Cleanup(); + SetupEntities(Game.Blockers, Game.Props, Game.Portals); + PrintToChatAll("[PropHunt] Map set has been changed to \"%s\"", g_currentSet); + return Plugin_Handled; + } + } + ReplyToCommand(client, "Warning: Set was not found, use /gw r to force load."); + } + } else if(StrEqual(subcmd, "toggle")) { + char type[32]; + GetCmdArg(2, type, sizeof(type)); + bool doAll = StrEqual(type, "all"); + bool isUnknown = true; + + if(doAll || StrEqual(type, "blockers", false)) { + if(Game.Blockers) { + EntFire(GAMEMODE_BLOCKER_NAME, "Disable"); + ReplyToCommand(client, "Disabled all custom gamemode blockers"); + } else { + EntFire(GAMEMODE_BLOCKER_NAME, "Enable"); + ReplyToCommand(client, "Enabled all custom gamemode blockers"); + } + Game.Blockers = !Game.Blockers; + isUnknown = false; + } + if(doAll || StrEqual(type, "props", false)) { + if(Game.Props) { + EntFire(GAMEMODE_PROP_NAME, "Disable"); + EntFire(GAMEMODE_PROP_NAME, "DisableCollision"); + ReplyToCommand(client, "Disabled all custom gamemode props"); + } else { + EntFire(GAMEMODE_PROP_NAME, "Enable"); + EntFire(GAMEMODE_PROP_NAME, "EnableCollision"); + ReplyToCommand(client, "Enabled all custom gamemode props"); + } + Game.Props = !Game.Props; + isUnknown = false; + } + if(isUnknown) ReplyToCommand(client, "Specify the type to affect: 'blockers', 'props', or 'all'"); + } else if(StrEqual(subcmd, "clear", false)) { + static char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + if(StrEqual(arg, "all")) { + Game.Cleanup(); + ReplyToCommand(client, "Cleaned up everything."); + } else if(StrEqual(arg, "props")) { + EntFire(GAMEMODE_PROP_NAME, "kill"); + ReplyToCommand(client, "Removed all custom gamemode props"); + } else if(StrEqual(arg, "blockers")) { + EntFire(GAMEMODE_BLOCKER_NAME, "kill"); + ReplyToCommand(client, "Removed all custom gamemode blockers"); + } else ReplyToCommand(client, "Specify the type to affect: 'blockers', 'props', or 'all'"); + } else if(StrEqual(subcmd, "settime")) { + int prev = Game.MapTime; + static char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + int time = StringToInt(arg); + mapConfig.mapTime = time; + Game.MapTime = time; + ReplyToCommand(client, "Map's time is temporarily set to %d seconds (was %d)", time, prev); + } else if(StrEqual(subcmd, "settick")) { + static char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + int tick = -StringToInt(arg); + Game.Tick = tick; + ReplyToCommand(client, "Set tick time to %d", tick); + } else if(StrContains(subcmd, "map") >= 0) { + static char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + if(StrEqual(arg, "list")) { + ReplyToCommand(client, "See the console for available maps"); + char map[64]; + for(int i = 0; i < validMaps.Length; i++) { + validMaps.GetString(i, map, sizeof(map)); + PrintToConsole(client, "%d. %s", i + 1, map); + } + } else if(StrEqual(arg, "random")) { + bool foundMap; + char map[64]; + do { + int mapIndex = GetURandomInt() % validMaps.Length; + validMaps.GetString(mapIndex, map, sizeof(map)); + if(!StrEqual(g_currentMap, map, false)) { + foundMap = true; + } + } while(!foundMap); + PrintToChatAll("%s Switching map to %s", GAMEMODE_PREFIX, map); + ChangeMap(map); + } else if(StrEqual(arg, "next", false)) { + if(args == 1) { + ReplyToCommand(client, "Specify the map to change on the next round: 'next '"); + } else { + char arg2[64]; + GetCmdArg(3, arg2, sizeof(arg2)); + if(IsMapValid(arg2)) { + strcopy(nextRoundMap, sizeof(nextRoundMap), arg2); + PrintToChatAll("%s Switching map next round to %s", GAMEMODE_PREFIX, arg2); + ForceChangeLevel(arg, "SetMapSelect"); + } else { + ReplyToCommand(client, "Map is not valid"); + } + } + } else if(StrEqual(arg, "force", false)) { + if(args == 1) { + ReplyToCommand(client, "Specify the map to change to: 'force '"); + } else { + char arg2[64]; + GetCmdArg(3, arg2, sizeof(arg2)); + if(IsMapValid(arg2)) { + PrintToChatAll("[H&S] Switching map to %s", arg2); + ChangeMap(arg2); + } else { + ReplyToCommand(client, "Map is not valid"); + } + } + } else { + ReplyToCommand(client, "Syntax: 'map /next >"); + } + return Plugin_Handled; + } else if(StrEqual(subcmd, "pos", false)) { + float pos[3]; + GetAbsOrigin(client, pos); + ReplyToCommand(client, "\"origin\" \"%f %f %f\"", pos[0], pos[1], pos[2]); + GetClientEyeAngles(client, pos); + ReplyToCommand(client, "\"rotation\" \"%f %f %f\"", pos[0], pos[1], pos[2]); + } else if(StrEqual(subcmd, "prop", false)) { + float pos[3]; + GetAbsOrigin(client, pos); + ReplyToCommand(client, "\"MYPROP\""); + ReplyToCommand(client, "{"); + ReplyToCommand(client, "\t\"origin\" \"%f %f %f\"", pos[0], pos[1], pos[2]); + GetClientAbsAngles(client, pos); + ReplyToCommand(client, "\t\"rotation\" \"%f %f %f\"", pos[0], pos[1], pos[2]); + ReplyToCommand(client, "\t\"type\" \"prop_dynamic\""); + ReplyToCommand(client, "\t\"model\" \"props_junk/dumpster_2.mdl\""); + ReplyToCommand(client, "}"); + } else if(StrEqual(subcmd, "setspawn", false)) { + GetClientAbsOrigin(client, mapConfig.spawnpoint); + ReplyToCommand(client, "Set map's temporarily spawnpoint to your location."); + } else if(StrEqual(subcmd, "stuck")) { + TeleportEntity(client, mapConfig.spawnpoint, NULL_VECTOR, NULL_VECTOR); + } else if(StrEqual(subcmd, "peekfix")) { + if(!PeekCam.Exists()) { + PeekCam.Target = client; + } + + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) { + PeekCam.SetViewing(client, true); + PeekCam.SetViewing(client, false); + } + } + PeekCam.Destroy(); + ReplyToCommand(client, "Killing active camera"); + } else if(StrEqual(subcmd, "seeker")) { + if(args == 2) { + char arg1[32]; + GetCmdArg(2, arg1, sizeof(arg1)); + char target_name[MAX_TARGET_LENGTH]; + int target_list[1], target_count; + bool tn_is_ml; + if ((target_count = ProcessTargetString( + arg1, + client, + target_list, + 1, + 0, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0 + || target_list[0] <= 0){ + /* This function replies to the admin with a failure message */ + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + Game.ForceSetSeeker(target_list[0]); + ReplyToCommand(client, "Set the current seeker to %N", target_list[0]); + } else { + ReplyToCommand(client, "The current seeker is: %N", Game.Seeker); + } + } else if(StrEqual(subcmd, "debug")) { + ReplyToCommand(client, "- Game Info -"); + ReplyToCommand(client, "State: %d | Tick: %d", view_as(Game.State), Game.Tick); + + ReplyToCommand(client, "- Map Info -"); + ReplyToCommand(client, "Map: %s (set %s)", g_currentMap, g_currentSet); + if(mapConfig.hasSpawnpoint) + ReplyToCommand(client, "Has Spawnpoint: yes (%f %f %f)", mapConfig.spawnpoint[0], mapConfig.spawnpoint[1], mapConfig.spawnpoint[2]); + else + ReplyToCommand(client, "Has Spawnpoint: no (possibly map spawn %f %f %f)", mapConfig.spawnpoint[0], mapConfig.spawnpoint[1], mapConfig.spawnpoint[2]); + ReplyToCommand(client, "Map Time: %d", mapConfig.mapTime); + ReplyToCommand(client, "Flow Bounds: (%f, %f)", movePoints.MinFlow, movePoints.MaxFlow); + } else if(StrEqual(subcmd, "test")) { + + } else { + ReplyToCommand(client, "Unknown option. Leave blank for help"); + } + return Plugin_Handled; + } + ReplyToCommand(client, " === [ %s Commands ] ===", GAMEMODE_NAME); + if(GetUserAdmin(client) != INVALID_ADMIN_ID) { + ReplyToCommand(client, "- Dev Commands -"); + ReplyToCommand(client, "r/reload [force]: Reloads map config from file"); + ReplyToCommand(client, "toggle : Toggles all specified entities"); + ReplyToCommand(client, "clear : Clear all specified"); + ReplyToCommand(client, "settime [seconds]: Sets the time override for the map"); + ReplyToCommand(client, "settick [tick]: Sets the current tick timer value"); + ReplyToCommand(client, "- Admin Commands -"); + ReplyToCommand(client, "set [new set]: Change the prop set or view current"); + ReplyToCommand(client, "setspawn: Sets the temporary spawnpoint for the map"); + ReplyToCommand(client, "peekfix - Clear peek camera from all players"); + ReplyToCommand(client, "seeker [new seeker]: Get the active seeker, or set a new one."); + ReplyToCommand(client, "- User Commands -"); + } + ReplyToCommand(client, "stuck: Teleports you to spawn to unstuck yourself"); + return Plugin_Handled; +} + +public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) { + if(isEnabled) { + if(!StrEqual(command, "say")) { //Is team message + if(currentSeeker <= 0 || currentSeeker == client) { + return Plugin_Continue; + } + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && i != currentSeeker) + PrintToChat(i, "[Hiders] %N: %s", client, sArgs); + } + return Plugin_Handled; + } + } + return Plugin_Continue; +} + + +public Action Command_Join(int client, int args) { + if(!isEnabled) return Plugin_Continue; + static float tpLoc[3]; + FindSpawnPosition(tpLoc); + if(args == 1) { + static char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + char target_name[MAX_TARGET_LENGTH]; + int target_list[MAXPLAYERS], target_count; + bool tn_is_ml; + if ((target_count = ProcessTargetString( + arg1, + client, + target_list, + MAXPLAYERS, + 0, + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + /* This function replies to the admin with a failure message */ + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + for (int i = 0; i < target_count; i++) { + int target = target_list[i]; + if(GetClientTeam(target) != 2) { + ChangeClientTeam(target, 2); + L4D_RespawnPlayer(target); + TeleportEntity(target, tpLoc, NULL_VECTOR, NULL_VECTOR); + isPendingPlay[client] = false; + CheatCommand(target, "give", "knife"); + } + } + ReplyToCommand(client, "Joined %s", target_name); + } else { + if(currentSeeker == client) { + ReplyToCommand(client, "You are already in-game as a seeker."); + return Plugin_Handled; + } + isPendingPlay[client] = false; + ChangeClientTeam(client, 2); + L4D_RespawnPlayer(client); + TeleportEntity(client, tpLoc, NULL_VECTOR, NULL_VECTOR); + Game.SetupPlayer(client); + if(!ArePlayersJoining()) { + InitGamemode(); + } + } + return Plugin_Handled; +} diff --git a/scripting/include/prophunt/phcore.inc b/scripting/include/prophunt/phcore.inc new file mode 100644 index 0000000..c63b043 --- /dev/null +++ b/scripting/include/prophunt/phcore.inc @@ -0,0 +1,171 @@ +#define FOLDER_PERMS ( FPERM_U_READ | FPERM_U_WRITE | FPERM_U_EXEC | FPERM_G_EXEC | FPERM_G_WRITE | FPERM_G_READ | FPERM_O_EXEC ) + +#include +#include +#include +#include + + +static KeyValues kv; +StringMap mapConfigs; + +bool ReloadMapDB() { + if(kv != null) { + delete kv; + } + kv = new KeyValues("prophunt"); + + char sPath[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, sPath, sizeof(sPath), "data/prophunt"); + CreateDirectory(sPath, FOLDER_PERMS); + Format(sPath, sizeof(sPath), "%s/config.cfg", sPath); + + if(!FileExists(sPath) || !kv.ImportFromFile(sPath)) { + delete kv; + return false; + } + + validMaps.Clear(); + + char map[64]; + kv.GotoFirstSubKey(true); + do { + kv.GetSectionName(map, sizeof(map)); + validMaps.PushString(map); + } while(kv.GotoNextKey(true)); + kv.GoBack(); + return true; +} + +static float DEFAULT_SCALE[3] = { 5.0, 5.0, 5.0 }; + +bool LoadConfigForMap(const char[] map) { + kv.Rewind(); + if (kv.JumpToKey(map)) { + MapConfig config; + config.entities = new ArrayList(sizeof(EntityConfig)); + config.inputs = new ArrayList(ByteCountToCells(64)); + validSets.Clear(); + + static char buffer[64]; + buffer[0] = '\0'; + if(StrEqual(g_currentSet, "default") && kv.GetString("defaultset", buffer, sizeof(buffer)) && buffer[0] != '\0') { + strcopy(g_currentSet, sizeof(g_currentSet), buffer); + } + PrintToServer("[PropHunt] Loading config data for set %s on %s", g_currentSet, map); + + if(kv.JumpToKey("ents")) { + kv.GotoFirstSubKey(); + do { + EntityConfig entCfg; + kv.GetVector("origin", entCfg.origin, NULL_VECTOR); + kv.GetVector("rotation", entCfg.rotation, NULL_VECTOR); + kv.GetString("type", entCfg.type, sizeof(entCfg.type), "env_physics_blocker"); + kv.GetString("model", entCfg.model, sizeof(entCfg.model), ""); + if(entCfg.model[0] != '\0') + Format(entCfg.model, sizeof(entCfg.model), "models/%s", entCfg.model); + kv.GetVector("scale", entCfg.scale, DEFAULT_SCALE); + kv.GetVector("offset", entCfg.offset, NULL_VECTOR); + kv.GetString("set", buffer, sizeof(buffer), "default"); + if(validSets.FindString(buffer) == -1) { + validSets.PushString(buffer); + } + if(StrEqual(buffer, "default") || StrEqual(g_currentSet, buffer, false)) { + + config.entities.PushArray(entCfg); + } else { + kv.GetSectionName(buffer, sizeof(buffer)); + PrintToServer("Skipping %s", buffer); + } + } while (kv.GotoNextKey()); + // JumpToKey and GotoFirstSubKey both traverse, i guess, go back + kv.GoBack(); + kv.GoBack(); + } + if(kv.JumpToKey("inputs")) { + kv.GotoFirstSubKey(false); + do { + kv.GetSectionName(buffer, sizeof(buffer)); + config.inputs.PushString(buffer); + + kv.GetString(NULL_STRING, buffer, sizeof(buffer)); + config.inputs.PushString(buffer); + } while (kv.GotoNextKey(false)); + kv.GoBack(); + kv.GoBack(); + } + int mapTime; + + config.hasSpawnpoint = false; + config.canClimb = true; + config.pressButtons = true; + if(!StrEqual(g_currentSet, "default") && kv.JumpToKey("sets")) { + char set[16]; + kv.GotoFirstSubKey(true); + do { + kv.GetSectionName(set, sizeof(set)); + if(validSets.FindString(set) == -1) { + validSets.PushString(set); + } + if(StrEqual(g_currentSet, set, false)) { + kv.GetVector("spawnpoint", config.spawnpoint); + if(config.spawnpoint[0] != 0.0 && config.spawnpoint[1] != 0.0 && config.spawnpoint[2] != 0.0) { + PrintToServer("[PropHunt] Using provided custom spawnpoint for set %s at %0.1f, %0.1f, %0.1f", g_currentSet, config.spawnpoint[0], config.spawnpoint[1], config.spawnpoint[2]); + config.hasSpawnpoint = true; + } + mapTime = kv.GetNum("maptime", 0); + if(kv.JumpToKey("inputs")) { + kv.GotoFirstSubKey(false); + do { + kv.GetSectionName(buffer, sizeof(buffer)); + config.inputs.PushString(buffer); + + kv.GetString(NULL_STRING, buffer, sizeof(buffer)); + config.inputs.PushString(buffer); + } while (kv.GotoNextKey(false)); + kv.GoBack(); + kv.GoBack(); + } + break; + } + + } while(kv.GotoNextKey(true)); + kv.GoBack(); + kv.GoBack(); + } + + if(!config.hasSpawnpoint) { + kv.GetVector("spawnpoint", config.spawnpoint); + if(config.spawnpoint[0] != 0.0 && config.spawnpoint[1] != 0.0 && config.spawnpoint[2] != 0.0) { + PrintToServer("[PropHunt] Using provided custom spawnpoint at %0.1f, %0.1f, %0.1f", config.spawnpoint[0], config.spawnpoint[1], config.spawnpoint[2]); + config.hasSpawnpoint = true; + } else if (FindSpawnPosition(config.spawnpoint, false)) { + PrintToServer("[PropHunt] Using map spawnpoint at %0.1f, %0.1f, %0.1f", config.spawnpoint[0], config.spawnpoint[1], config.spawnpoint[2]); + config.hasSpawnpoint = true; + } else { + PrintToServer("[PropHunt] Could not find any spawnpoints, using default spawn"); + config.hasSpawnpoint = false; + } + } + + // Use default maptime if exists + if(mapTime == 0) + mapTime = kv.GetNum("maptime", 0); + if(mapTime > 0) { + config.mapTime = mapTime; + PrintToServer("[PropHunt] Map time overwritten to %d seconds", mapTime); + } + + mapConfigs.SetArray(map, config, sizeof(MapConfig)); + // Discard entInputs if unused + if(config.inputs.Length == 0) { + delete config.inputs; + } + mapConfig = config; + return true; + } else { + mapConfig.hasSpawnpoint = false; + PrintToServer("[PropHunt] %s has no config entry", map); + return false; + } +} \ No newline at end of file diff --git a/scripting/include/prophunt/phents.inc b/scripting/include/prophunt/phents.inc new file mode 100644 index 0000000..670c13e --- /dev/null +++ b/scripting/include/prophunt/phents.inc @@ -0,0 +1,128 @@ +#define ENT_PROP_NAME "gwprop" +#define ENT_BLOCKER_NAME "gwblocker" +#define ENT_PORTAL_NAME "gwportal" +#define ENT_ENV_NAME "gwenv" +#include + +stock void CheatCommand(int client, const char[] command, const char[] argument1) { + int userFlags = GetUserFlagBits(client); + SetUserFlagBits(client, ADMFLAG_ROOT); + int flags = GetCommandFlags(command); + SetCommandFlags(command, flags & ~FCVAR_CHEAT); + FakeClientCommand(client, "%s %s", command, argument1); + SetCommandFlags(command, flags); + SetUserFlagBits(client, userFlags); +} + + +stock void EntFire(const char[] name, const char[] input) { + static char targetname[64]; + static char cmd[32]; + #if defined DEBUG_LOG_MAPSTART + PrintToServer("EntFire: %s \"%s\"", name, input); + #endif + int len = SplitString(input, " ", cmd, sizeof(cmd)); + if(len > -1) SetVariantString(input[len]); + + int hammerId = name[0] == '!' ? StringToInt(name[1]) : 0; + for(int i = MaxClients + 1; i <= 4096; i++) { + if(IsValidEntity(i) && (IsValidEdict(i) || EntIndexToEntRef(i) != -1)) { + if(hammerId > 0) { + if(hammerId == Entity_GetHammerId(i)) { + if(len > -1) AcceptEntityInput(i, cmd); + else AcceptEntityInput(i, input); + } + } else { + GetEntPropString(i, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrEqual(targetname, name, false)) { + if(len > -1) AcceptEntityInput(i, cmd); + else AcceptEntityInput(i, input); + + } else { + GetEntityClassname(i, targetname, sizeof(targetname)); + if(StrEqual(targetname, name, false)) { + if(len > -1) AcceptEntityInput(i, cmd); + else AcceptEntityInput(i, input); + } + } + } + } + } +} + + + +void SetupEntities(bool blockers = true, bool props = true, bool portals = true) { + #if defined DEBUG_BLOCKERS + if(mapConfig.hasSpawnpoint) { + PrecacheModel("survivors/survivor_teenangst.mdl", true); + int dummy = CreateDummy("models/survivors/survivor_teenangst.mdl", "idle", mapConfig.spawnpoint, NULL_VECTOR); + SetEntProp(dummy, Prop_Data, "m_nSolidType", 0); + SetEntProp(dummy, Prop_Send, "m_CollisionGroup", 0); + SetEntProp(dummy, Prop_Send, "movetype", MOVETYPE_NONE); + } + EntFire("info_changelevel", "Kill"); + #endif + if(mapConfig.entities != null) { + PrintToServer("[GuessWho] Deploying %d custom entities (Set: %s) (blockers:%b props:%b portals:%b)", mapConfig.entities.Length, g_currentSet, blockers, props, portals); + for(int i = 0; i < mapConfig.entities.Length; i++) { + EntityConfig config; + mapConfig.entities.GetArray(i, config); + + if(config.model[0] != '\0') PrecacheModel(config.model); + + if(StrEqual(config.type, "env_physics_blocker")) { + if(blockers && CreateEnvBlockerScaled(config.type, config.origin, config.scale, isNavBlockersEnabled) == -1) { + Game.Warn("Failed to spawn blocker [type=%s] at (%.1f,%.1f, %.1f)", config.type, config.origin[0], config.origin[1], config.origin[2]); + } + } else if(StrEqual(config.type, "_relportal")) { + if(portals && CreatePortal(Portal_Relative, config.model, config.origin, config.offset, config.scale) == -1) { + Game.Warn("Failed to spawn rel portal at (%.1f,%.1f, %.1f)", config.origin[0], config.origin[1], config.origin[2]); + } + } else if(StrEqual(config.type, "_portal")) { + if(portals && CreatePortal(Portal_Teleport, config.model, config.origin, config.offset, config.scale) == -1) { + Game.Warn("Failed to spawn portal at (%.1f,%.1f, %.1f)", config.origin[0], config.origin[1], config.origin[2]); + } + } else if(StrEqual(config.type, "_lantern")) { + int parent = CreateProp("prop_dynamic", config.model, config.origin, config.rotation); + if(parent == -1) { + Game.Warn("Failed to spawn prop [type=%s] [model=%s] at (%.1f,%.1f, %.1f)", config.type, config.model, config.origin[0], config.origin[1], config.origin[2]); + } else { + float pos[3]; + pos = config.origin; + pos[2] += 15.0; + int child = CreateDynamicLight(pos, config.rotation, GetColorInt(255, 255, 242), 80.0, 11); + if(child == -1) { + Game.Warn("Failed to spawn light source for _lantern"); + } else { + SetParent(child, parent); + TeleportEntity(parent, config.origin, NULL_VECTOR, NULL_VECTOR); + } + } + } else if(StrEqual(config.type, "_dummy")) { + if(CreateDummy(config.model, "hitby_tankpunch", config.origin, config.rotation) == -1) { + Game.Warn("Failed to spawn dummy [model=%s] at (%.1f,%.1f, %.1f)", config.model, config.origin[0], config.origin[1], config.origin[2]); + } + } else if(StrEqual(config.type, "env_fire")) { + if(props && CreateFire(config.origin, config.scale[0], config.scale[1], config.scale[2]) == -1) { + Game.Warn("Failed to spawn env_fire at (%.1f,%.1f, %.1f)", config.origin[0], config.origin[1], config.origin[2]); + } + } else if(props) { + if(CreateProp(config.type, config.model, config.origin, config.rotation) == -1) { + Game.Warn("Failed to spawn prop [type=%s] [model=%s] at (%.1f,%.1f, %.1f)", config.type, config.model, config.origin[0], config.origin[1], config.origin[2]); + } + } + } + + static char key[64]; + static char value[64]; + if(mapConfig.inputs != null) { + for(int i = 0; i < mapConfig.inputs.Length - 1; i += 2) { + mapConfig.inputs.GetString(i, key, sizeof(key)); + mapConfig.inputs.GetString(i + 1, value, sizeof(value)); + EntFire(key, value); + } + } + } +} + diff --git a/scripting/include/prophunt/phgame.inc b/scripting/include/prophunt/phgame.inc new file mode 100644 index 0000000..f5f6d6f --- /dev/null +++ b/scripting/include/prophunt/phgame.inc @@ -0,0 +1,390 @@ +static int mapChangeMsgTicks = 5; + +int GetColorInt(int r, int g, int b) { + int color = r; + color += 256 * g; + color += 65536 * b; + return color; +} + +Action Timer_ChangeMap(Handle h) { + PrintToChatAll("Changing map to %s in %d seconds", nextRoundMap, mapChangeMsgTicks); + if(mapChangeMsgTicks-- == 0) { + ForceChangeLevel(nextRoundMap, "GuessWhoMapSelect"); + nextRoundMap[0] = '\0'; + return Plugin_Stop; + } + return Plugin_Continue; +} + +void ChangeMap(const char map[64], int time = 5) { + strcopy(nextRoundMap, sizeof(nextRoundMap), map); + mapChangeMsgTicks = time; + CreateTimer(1.0, Timer_ChangeMap, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); +} + +bool FindSpawnPosition(float pos[3], bool includePlayers = true) { + if(includePlayers) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { + GetClientAbsOrigin(i, pos); + return true; + } + } + } + int entity = INVALID_ENT_REFERENCE; + while ((entity = FindEntityByClassname(entity, "info_player_start")) != INVALID_ENT_REFERENCE) { + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); + return true; + } + return false; +} + +static char buffer[128]; + +bool isSeeker[MAXPLAYERS+1]; + +methodmap PropHuntGame < BaseGame { + + property int Seekers { + public get() { + int count = 0; + for(int i = 1; i <= MaxClients; i++) { + if(isSeeker[i]) count++; + } + return count; + } + } + + public bool IsSeeker(int client) { + return isSeeker[client]; + } + + public void SetSeeker(int client, bool value) { + isSeeker[client] = value; + } + + public void ClearSeekers() { + for(int i = 1; i <= MaxClients; i++) { + isSeeker[i] = false; + } + } + + property int Tick { + public get() { + if(!isEnabled) return -1; + L4D2_GetVScriptOutput("g_ModeScript.MutationState.Tick", buffer, sizeof(buffer)); + int value = -1; + if(StringToIntEx(buffer, value) > 0) { + return value; + } else { + return -1; + } + } + public set(int tick) { + Format(buffer, sizeof(buffer), "g_ModeScript.MutationState.Tick = %d", tick); + L4D2_ExecVScriptCode(buffer); + } + } + + property GameState State { + public get() { + if(!isEnabled) return State_Unknown; + L4D2_GetVScriptOutput("g_ModeScript.MutationState.State", buffer, sizeof(buffer)); + int stage = 0; + if(StringToIntEx(buffer, stage) > 0) { + return view_as(stage); + } else { + return State_Unknown; + } + } + public set(GameState state) { + if(isEnabled) { + Format(buffer, sizeof(buffer), "g_ModeScript.MutationState.State = %d", view_as(state)); + L4D2_ExecVScriptCode(buffer); + } + } + } + + property int MapTime { + public get() { + L4D2_GetVScriptOutput("g_ModeScript.MutationState.MaxTime", buffer, sizeof(buffer)); + return StringToInt(buffer); + } + public set(int seconds) { + Format(buffer, sizeof(buffer), "g_ModeScript.MutationState.MaxTime = %d", seconds); + L4D2_ExecVScriptCode(buffer); + if(timesUpTimer != null) { + float remaining = float(seconds) - float(this.Tick); + delete timesUpTimer; + timesUpTimer = CreateTimer(remaining, Timer_TimesUp, _, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + + public void Start() { + + } + + public void End(GameState state) { + this.State = state; + CreateTimer(5.0, Timer_ResetAll); + } + + public void Cleanup(bool noClearInv = false) { + DeleteCustomEnts(); + } + + public int _FindSeeker() { + if(!isEnabled) return -1; + L4D2_GetVScriptOutput("g_ModeScript.MutationState.CurrentSeeker && \"GetPlayerUserId\" in g_ModeScript.MutationState.CurrentSeeker ? g_ModeScript.MutationState.CurrentSeeker.GetPlayerUserId() : -1", buffer, sizeof(buffer)); + int uid = StringToInt(buffer); + if(uid > 0) { + return GetClientOfUserId(uid); + } else { + Game.Debug("Mutation has no seeker, manually attempting to find seeker"); + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) { + int entity = GetPlayerWeaponSlot(i, 1); + if(entity > -1 && GetEntityClassname(entity, buffer, sizeof(buffer)) && StrEqual(buffer, "melee")) { + GetEntPropString(entity, Prop_Data, "m_strMapSetScriptName", buffer, sizeof(buffer)); + if(StrEqual(buffer, "smg")) { + return i; + } + } + } + } + } + Game.Debug("All attempts to find a seeker failed"); + return -1; + } + + + public void ForceSetSeeker(int client, bool ignoreBalance = false) { + ignoreSeekerBalance = true; + this.Seeker = client; + } + + public bool TeleportToSpawn(int client) { + if(mapConfig.hasSpawnpoint) { + TeleportEntity(client, mapConfig.spawnpoint, NULL_VECTOR, NULL_VECTOR); + return true; + } else { + float pos[3]; + if(FindSpawnPosition(pos)) { + return false; + } + TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); + } + return false; + } + + public void TeleportAllToStart() { + if(mapConfig.hasSpawnpoint) { + PrintToServer("[GuessWho] Teleporting all players to provided spawnpoint (%f %f %f)", mapConfig.spawnpoint[0], mapConfig.spawnpoint[1], mapConfig.spawnpoint[2]); + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) { + this.TeleportToSpawn(i); + } + } + } else { + PrintToServer("[GuessWho] Warn: No spawnpoint found (provided or map spawn)"); + } + } + + // Ignores seeker + property int AlivePlayers { + public get() { + int amount = 0; + for(int i = 1; i <= MaxClients; i++) { + if(!this.IsSeeker(i) && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i) && !IsFakeClient(i)) { + amount++; + } + } + return amount; + } + } + + public void SetupInventory(int client) { + ClearInventory(client); + if(this.IsSeeker(client)) { + CheatCommand(client, "give", "smg"); + } + } + + public void SetupPlayer(int client) { + this.SetupInventory(client); + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + } + + public void UnsetupPlayer(int client) { + SDKUnhook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + } +} + +stock bool ArePlayersJoining() { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && !IsClientInGame(i)) { + return true; + } + } + return false; +} + +stock void GetHorizontalPositionFromClient(int client, float units, float finalPosition[3]) { + float pos[3], ang[3]; + GetClientEyeAngles(client, ang); + GetClientAbsOrigin(client, pos); + + float theta = DegToRad(ang[1]); + pos[0] += units * Cosine(theta); + pos[1] += units * Sine(theta); + finalPosition = pos; +} + +stock void GetAnglesLookAt(int iClient, int iTarget, float fFinalPos[3]) { + static float fTargetPos[3]; + static float fTargetAngles[3]; + static float fClientPos[3]; + + GetEntPropVector(iClient, Prop_Send, "m_vecOrigin", fClientPos); + GetClientEyePosition(iTarget, fTargetPos); + GetClientEyeAngles(iTarget, fTargetAngles); + + float fVecFinal[3]; + AddInFrontOf(fTargetPos, fTargetAngles, 7.0, fVecFinal); + MakeVectorFromPoints(fClientPos, fVecFinal, fFinalPos); + + GetVectorAngles(fFinalPos, fFinalPos); + + // TeleportEntity(iClient, NULL_VECTOR, fFinalPos, NULL_VECTOR); +} +stock void AddInFrontOf(const float fVecOrigin[3], const float fVecAngle[3], float fUnits, float fOutPut[3]) +{ + float fVecView[3]; GetViewVector(fVecAngle, fVecView); + + fOutPut[0] = fVecView[0] * fUnits + fVecOrigin[0]; + fOutPut[1] = fVecView[1] * fUnits + fVecOrigin[1]; + fOutPut[2] = fVecView[2] * fUnits + fVecOrigin[2]; +} +stock void GetViewVector(const float fVecAngle[3], float fOutPut[3]) +{ + fOutPut[0] = Cosine(fVecAngle[1] / (180 / FLOAT_PI)); + fOutPut[1] = Sine(fVecAngle[1] / (180 / FLOAT_PI)); + fOutPut[2] = -Sine(fVecAngle[0] / (180 / FLOAT_PI)); +} + +stock void LookAtClient(int iClient, int iTarget) { + static float fTargetPos[3]; + static float fTargetAngles[3]; + static float fClientPos[3]; + static float fFinalPos[3]; + + GetClientEyePosition(iClient, fClientPos); + GetClientEyePosition(iTarget, fTargetPos); + GetClientEyeAngles(iTarget, fTargetAngles); + + float fVecFinal[3]; + AddInFrontOf(fTargetPos, fTargetAngles, 7.0, fVecFinal); + MakeVectorFromPoints(fClientPos, fVecFinal, fFinalPos); + + GetVectorAngles(fFinalPos, fFinalPos); + + TeleportEntity(iClient, NULL_VECTOR, fFinalPos, NULL_VECTOR); +} + +stock void LookAtPoint(int client, const float targetPos[3]) { + static float targetAngles[3]; + static float clientPos[3]; + static float fFinalPos[3]; + + GetClientEyePosition(client, clientPos); + GetClientEyeAngles(client, targetAngles); + + float fVecFinal[3]; + AddInFrontOf(targetPos, targetAngles, 7.0, fVecFinal); + MakeVectorFromPoints(clientPos, fVecFinal, fFinalPos); + + GetVectorAngles(fFinalPos, fFinalPos); + + TeleportEntity(client, NULL_VECTOR, fFinalPos, NULL_VECTOR); +} + + +void SetPlayerBlind(int target, int amount) { + int targets[1]; + targets[0] = target; + + int duration = 1536; + int holdtime = 1536; + int flags = (amount == 0) ? (0x0001 | 0x0010) : (0x0002 | 0x0008); + int color[4] = { 0, 0, 0, 0 }; + color[3] = amount; + + Handle message = StartMessageEx(g_FadeUserMsgId, targets, 1); + BfWrite bf = UserMessageToBfWrite(message); + bf.WriteShort(duration); + bf.WriteShort(holdtime); + bf.WriteShort(flags); + bf.WriteByte(color[0]); + bf.WriteByte(color[1]); + bf.WriteByte(color[2]); + bf.WriteByte(color[3]); + EndMessage(); +} + +#define HIDER_DISTANCE_MAX_SIZE 10 + + +#define MAX_AUTO_VOCALIZATIONS 9 +static char AUTO_VOCALIZATIONS[MAX_AUTO_VOCALIZATIONS][] = { + "PlayerLaugh", + "PlayerSpotPill", + "Playerlookout", + "EatPills", + "ReviveMeInterrupted", + "PlayerIncapacitated", + "PlayerNiceShot", + "ResponseSoftDispleasureSwear", + "PlayerAreaClear" +}; + +enum struct HiderDistQueue { + int index; + float list[HIDER_DISTANCE_MAX_SIZE]; + int lastVocalize; + + void AddPos(const float pos[3]) { + this.list[this.index] = GetVectorDistance(seekerPos, pos); + if(++this.index == HIDER_DISTANCE_MAX_SIZE) { + this.index = 0; + } + } + + void Clear() { + for(int i = 0; i < HIDER_DISTANCE_MAX_SIZE; i++) { + this.list[i] = 0.0; + } + } + + float GetAverage() { + float sum = 0.0; + for(int i = 0; i < HIDER_DISTANCE_MAX_SIZE; i++) { + sum += this.list[i]; + } + return sum / float(HIDER_DISTANCE_MAX_SIZE); + } + + void Check(int i) { + if(this.GetAverage() > HIDER_MIN_AVG_DISTANCE_AUTO_VOCALIZE) { + int time = GetTime(); + if(time - this.lastVocalize > HIDER_AUTO_VOCALIZE_GRACE_TIME) { + this.lastVocalize = time; + int index = GetRandomInt(0, MAX_AUTO_VOCALIZATIONS - 1); + PerformScene(i, AUTO_VOCALIZATIONS[index]); + } + } + } +} + +HiderDistQueue distQueue[MAXPLAYERS+1]; \ No newline at end of file diff --git a/scripting/include/prophunt/phtimers.inc b/scripting/include/prophunt/phtimers.inc new file mode 100644 index 0000000..1a37392 --- /dev/null +++ b/scripting/include/prophunt/phtimers.inc @@ -0,0 +1,70 @@ + +Action Timer_RecordPoints(Handle h, int i) { + if(GetEntityFlags(i) & FL_ONGROUND && IsPlayerAlive(i)) { + LocationMeta meta; + GetClientAbsOrigin(i, meta.pos); + GetClientEyeAngles(i, meta.ang); + if(meta.pos[0] != vecLastLocation[i][0] || meta.pos[1] != vecLastLocation[i][1] || meta.pos[2] != vecLastLocation[i][2]) { + if(movePoints.AddPoint(meta)) { + recordTimer = null; + return Plugin_Stop; + } + Effect_DrawBeamBoxRotatableToClient(i, meta.pos, DEBUG_POINT_VIEW_MIN, DEBUG_POINT_VIEW_MAX, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {0, 0, 255, 64}, 0); + vecLastLocation[i] = meta.pos; + } + } + PrintHintText(i, "Points: %d / %d", movePoints.Length, MAX_VALID_LOCATIONS); + return Plugin_Continue; +} + +bool firstCheckDone = false; +Action Timer_WaitForPlayers(Handle h) { + if(!isEnabled) return Plugin_Stop; + if(!ArePlayersJoining()) { + Game.Debug("No players pending, ready to go"); + if(!firstCheckDone) { + // Wait one more iteration + firstCheckDone = true; + } else { + firstCheckDone = false; + InitGamemode(); + return Plugin_Stop; + } + } + Game.Debug("Waiting for players"); + return Plugin_Continue; +} + + +Action Timer_CheckHiders(Handle h) { + static float pos[3]; + static char classname[16]; + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { + GetClientAbsOrigin(i, pos); + distQueue[i].AddPos(pos); + distQueue[i].Check(i); + + int activeWeapon = GetEntPropEnt(i, Prop_Send, "m_hActiveWeapon"); + if(IsValidEntity(activeWeapon)) { + GetEntityClassname(activeWeapon, classname, sizeof(classname)); + if(i == currentSeeker) { + if(StrEqual(classname, "weapon_melee")) continue; + Game.SetupInventory(i); + } else if(StrEqual(classname, "weapon_gnome")) continue; + } + Game.SetupInventory(i); + } + } + Game.CleanupGnomes(true); + return Plugin_Continue; +} + +Action Timer_ResetAll(Handle h) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) { + ForcePlayerSuicide(i); + } + } + return Plugin_Handled; +} \ No newline at end of file diff --git a/scripting/l4d2_prophunt.sp b/scripting/l4d2_prophunt.sp new file mode 100644 index 0000000..c8624ca --- /dev/null +++ b/scripting/l4d2_prophunt.sp @@ -0,0 +1,151 @@ +#pragma semicolon 1 +#pragma newdecls required + +//#define DEBUG + +#define PLUGIN_VERSION "1.0" + +#include +#include +#include +#include +#include + +enum GameState { + State_Unknown = 0, + State_Hiding, + State_Active, + State_PropsWin, + State_SeekerWin, +} + +#define MAX_VALID_MODELS 2 +static char VALID_MODELS[MAX_VALID_MODELS][] = { + "models/props_crates/static_crate_40.mdl", + "models/props_junk/gnome.mdl" +}; + +static float EMPTY_ANG[3]; +#define TRANSPARENT "255 255 255 0" +#define WHITE "255 255 255 255" + +enum struct PropData { + int prop; + bool rotationLock; +} + +PropData propData[MAXPLAYERS+1]; + +public Plugin myinfo = +{ + name = "Prophunt", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "https://github.com/Jackzmc/sourcemod-plugins" +}; + +public void OnPluginStart() { + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead2) { + SetFailState("This plugin is for L4D2 only."); + } + RegConsoleCmd("sm_game", Command_Test); +} + +public void OnMapStart() { + for(int i = 0; i < MAX_VALID_MODELS; i++) { + PrecacheModel(VALID_MODELS[i]); + } +} + +void ResetPlayerData(int client) { + if(propData[client].prop > 0) { + AcceptEntityInput(propData[client].prop, "Kill"); + propData[client].prop = 0; + } + propData[client].rotationLock = false; +} + +public void OnMapEnd() { + for(int i = 1; i <= MaxClients; i++) { + ResetPlayerData(i); + if(IsClientConnected(i) && IsClientInGame(i)) { + DispatchKeyValue(i, "rendercolor", WHITE); + } + } +} + +public void OnClientDisconnect(int client) { + ResetPlayerData(client); +} + +public Action Command_Test(int client, int args) { + int prop = CreatePropInternal(VALID_MODELS[0]); + if(prop <= 0) { + ReplyToCommand(client, "Failed to spawn prop"); + return Plugin_Handled; + } + float pos[3]; + propData[client].prop = prop; + DispatchKeyValue(client, "rendercolor", TRANSPARENT); + // SetParent(prop, client); + // SetParentAttachment(prop, "eyes", true); + // TeleportEntity(prop, pos, EMPTY_ANG, NULL_VECTOR); + // SetParentAttachment(prop, "eyes", true); + SDKHook(client, SDKHook_SetTransmit, OnPlayerTransmit); + ReplyToCommand(client, "Game!"); + return Plugin_Handled; +} + + +Action OnPlayerTransmit(int entity, int client) { + return entity == client ? Plugin_Continue : Plugin_Stop; +} + +int CreatePropInternal(const char[] model) { + int entity = CreateEntityByName("prop_dynamic"); + DispatchKeyValue(entity, "model", model); + DispatchKeyValue(entity, "disableshadows", "1"); + DispatchKeyValue(entity, "targetname", "phprop"); + DispatchSpawn(entity); + SetEntProp(entity, Prop_Send, "m_nSolidType", 6); + SetEntProp(entity, Prop_Send, "m_CollisionGroup", 1); + SetEntProp(entity, Prop_Send, "movetype", MOVETYPE_NONE); + return entity; +} + +public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) { + if(propData[client].prop > 0) { + static float pos[3], ang[3]; + GetClientAbsOrigin(client, pos); + TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); + if(propData[client].rotationLock) + TeleportEntity(propData[client].prop, NULL_VECTOR, angles, NULL_VECTOR); + else { + ang[0] = 0.0; + ang[1] = angles[1]; + ang[2] = 0.0; + TeleportEntity(propData[client].prop, pos, ang, NULL_VECTOR); + } + } + return Plugin_Continue; +} + +Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { + /*if(attacker == currentSeeker) { + damage = 100.0; + ClearInventory(victim); + if(attacker > 0 && attacker <= MaxClients && IsFakeClient(victim)) { + PrintToChat(attacker, "That was a bot! -%.0f health", cvar_seekerFailDamageAmount.FloatValue); + SDKHooks_TakeDamage(attacker, 0, 0, cvar_seekerFailDamageAmount.FloatValue, DMG_DIRECT); + } + return Plugin_Changed; + } else if(attacker > 0 && attacker <= MaxClients) { + damage = 0.0; + return Plugin_Changed; + } else { + return Plugin_Continue; + }*/ + return Plugin_Continue; +}