diff --git a/plugins/l4d2_ai_tweaks.smx b/plugins/l4d2_ai_tweaks.smx index aa17262..d858309 100644 Binary files a/plugins/l4d2_ai_tweaks.smx and b/plugins/l4d2_ai_tweaks.smx differ diff --git a/plugins/l4d2_detections.smx b/plugins/l4d2_detections.smx index d8f3d74..6aadb4b 100644 Binary files a/plugins/l4d2_detections.smx and b/plugins/l4d2_detections.smx differ diff --git a/plugins/l4d2_prophunt.smx b/plugins/l4d2_prophunt.smx new file mode 100644 index 0000000..afcdef1 Binary files /dev/null and b/plugins/l4d2_prophunt.smx differ diff --git a/plugins/l4d2_turret.smx b/plugins/l4d2_turret.smx index 765c214..bc4bc54 100644 Binary files a/plugins/l4d2_turret.smx and b/plugins/l4d2_turret.smx differ diff --git a/scripting/include/gamemodes/base.inc b/scripting/include/gamemodes/base.inc index 3b156c7..db652f7 100644 --- a/scripting/include/gamemodes/base.inc +++ b/scripting/include/gamemodes/base.inc @@ -246,6 +246,9 @@ methodmap PeekCamera { } } +bool Filter_IgnoreAll(int entity, int mask) { + return false; +} enum struct EntityConfig { float origin[3]; diff --git a/scripting/include/guesswho/gwgame.inc b/scripting/include/guesswho/gwgame.inc index 6326229..9911593 100644 --- a/scripting/include/guesswho/gwgame.inc +++ b/scripting/include/guesswho/gwgame.inc @@ -391,10 +391,6 @@ stock void LookAtPoint(int client, const float targetPos[3]) { TeleportEntity(client, NULL_VECTOR, fFinalPos, NULL_VECTOR); } -bool Filter_IgnoreAll(int entity, int mask) { - return false; -} - void SetPlayerBlind(int target, int amount) { int targets[1]; diff --git a/scripting/include/hideandseek/hsgame.inc b/scripting/include/hideandseek/hsgame.inc index 0043a95..d909987 100644 --- a/scripting/include/hideandseek/hsgame.inc +++ b/scripting/include/hideandseek/hsgame.inc @@ -231,9 +231,3 @@ stock void GetViewVector(float fVecAngle[3], float fOutPut[3]) fOutPut[1] = Sine(fVecAngle[1] / (180 / FLOAT_PI)); fOutPut[2] = -Sine(fVecAngle[0] / (180 / FLOAT_PI)); } - - -bool Filter_IgnoreAll(int entity, int mask) { - return false; -} - 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_ai_tweaks.sp b/scripting/l4d2_ai_tweaks.sp index 8e703c1..fe0f10f 100644 --- a/scripting/l4d2_ai_tweaks.sp +++ b/scripting/l4d2_ai_tweaks.sp @@ -41,14 +41,12 @@ public Action OnFriendAction( BehaviorAction action, int actor, BehaviorAction p if(IsFakeClient(target)) { // If target is a bot, not idle player, ignore if(GetEntProp(target, Prop_Send, "m_humanSpectatorUserID") == 0) { - PrintToServer("Preventing %N from healing bot %N", actor, target); result.type = DONE; return Plugin_Handled; } } // If they are not black and white, also stop if(!GetEntProp(target, Prop_Send, "m_bIsOnThirdStrike")) { //If real player and not black and white, stop - PrintToServer("Preventing %N from healing %N, not black & white", actor, target); result.type = DONE; return Plugin_Handled; } diff --git a/scripting/l4d2_detections.sp b/scripting/l4d2_detections.sp index 4fa2a53..9421665 100644 --- a/scripting/l4d2_detections.sp +++ b/scripting/l4d2_detections.sp @@ -48,7 +48,7 @@ Bile Detections: */ stock bool IsPlayerBoomed(int client) { - return GetEntPropFloat(%0, Prop_Send, "m_vomitStart") + 20.1 > GetGameTime(); + return GetEntPropFloat(client, Prop_Send, "m_vomitStart") + 20.1 > GetGameTime(); } stock bool IsAnyPlayerBoomed() { for(int i = 1; i <= MaxClients; i++) { @@ -63,7 +63,7 @@ stock bool AnyRecentBileInPlay(int ignore) { return false; } -stock int GetEntityCountNear(const float[3] srcPos, float radius = 50000.0) { +stock int GetEntityCountNear(const float srcPos[3], float radius = 50000.0) { float pos[3]; int count; int entity = -1; @@ -87,7 +87,7 @@ stock int L4D_SpawnCommonInfected2(const float vPos[3], const float vAng[3] = { return entity; } -PlayerDetections[MAXPLAYERS+1] detections; +PlayerDetections detections[MAXPLAYERS+1]; GlobalForward fwd_PlayerDoubleKit, fwd_NoHordeBileWaste, fwd_DoorFaceCloser, fwd_CheckpointDoorFaceCloser; @@ -114,6 +114,7 @@ public void OnPluginStart() { HookEvent("item_pickup", Event_ItemPickup); HookEvent("door_close", Event_DoorClose); HookEvent("player_disconnect", Event_PlayerDisconnect); + HookEvent("heal_success", Event_HealSuccess); } public void OnClientPutInServer(int client) { @@ -139,7 +140,7 @@ public void OnMapStart() { } // TODO: Check when player enters saferoom, and has no kit and heals and pickup another -public Action Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { +public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if(client && L4D_IsInLastCheckpoint(client)) { static char itmName[32]; @@ -186,7 +187,17 @@ Action Timer_ClearDoubleKitDetection(Handle h, int userid) { return Plugin_Continue; } -public Action Event_DoorClose(Event event, const char[] name, bool dontBroadcast) { +public void Event_HealSuccess(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client) { + int target = GetClientOfUserId(event.GetInt("subject")); + int amount = event.GetInt("health_restored"); + int orgHealth = GetClientHealth(target) - amount; + PrintToConsoleAll("[Debug] %N healed %N (+%d health, was %d)", client, target, amount, orgHealth); + } +} + +public void Event_DoorClose(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if(fwd_DoorFaceCloser.FunctionCount > 0 && client) { bool isCheckpoint = event.GetBool("checkpoint"); 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; +} diff --git a/scripting/l4d2_turret.sp b/scripting/l4d2_turret.sp index 24755b6..5d34c1a 100644 --- a/scripting/l4d2_turret.sp +++ b/scripting/l4d2_turret.sp @@ -231,6 +231,7 @@ public Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float } public void OnMapEnd() { + manualTarget = -1; ClearTurrets(); } @@ -261,7 +262,7 @@ public void OnEntityDestroyed(int entity) { public Action Command_SpawnTurret(int client, int args) { float pos[3]; GetClientEyePosition(client, pos); - pos[2] += 20.0; + pos[2] += 40.0; int base = CreateParticleNamed(ENT_PORTAL_NAME, PARTICLE_ELMOS, pos, NULL_VECTOR); SetupTurret(base, TURRET_ACTIVATION_TIME); ReplyToCommand(client, "New turret (%d) will activate in %.0f seconds", base, TURRET_ACTIVATION_TIME); @@ -642,7 +643,7 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 // Run a ray trace to find a suitable position // TODO: Possibly run per-turret for more accurate preview... but it's already lag fest TR_TraceRayFilter(orgPos, angles, MASK_SHOT, RayType_Infinite, Filter_ManualTarget); - if(!IsValidEntity(manualTarget)) manualTarget = CreateTarget(aimPos, MANUAL_TARGETNAME); + if(manualTarget <= 0 || !IsValidEntity(manualTarget)) manualTarget = CreateTarget(aimPos, MANUAL_TARGETNAME); // Disable aim snapping if player is holding WALK (which is apparently IN_SPEED) bool aimSnapping = ~buttons & IN_SPEED > 0;