diff --git a/plugins/l4d2_hideandseek.smx b/plugins/l4d2_hideandseek.smx index a57dac6..2dfdba8 100644 Binary files a/plugins/l4d2_hideandseek.smx and b/plugins/l4d2_hideandseek.smx differ diff --git a/scripting/l4d2_hideandseek.sp b/scripting/l4d2_hideandseek.sp index 299d41a..2d77327 100644 --- a/scripting/l4d2_hideandseek.sp +++ b/scripting/l4d2_hideandseek.sp @@ -4,9 +4,9 @@ //#define DEBUG #define PLUGIN_VERSION "1.0" -// #define DEBUG_BLOCKERS 1 +#define DEBUG_BLOCKERS 1 // #define FORCE_ENABLED 1 -// #define DEBUG_LOG_MAPSTART 1 +#define DEBUG_LOG_MAPSTART 1 #include #include @@ -58,7 +58,7 @@ static char gamemode[32], currentMap[64]; static bool isEnabled, lateLoaded; static bool isPendingPlay[MAXPLAYERS+1]; -static bool isNavBlockersEnabled = true, isPropsEnabled = true; +static bool isNavBlockersEnabled = true, isPropsEnabled = true, isPortalsEnabled = true; static bool isNearbyPlaying[MAXPLAYERS+1]; static bool wasThirdPersonVomitted[MAXPLAYERS+1]; static bool gameOver; @@ -66,17 +66,19 @@ static int currentSeeker; static int currentPlayers = 0; static const float DEFAULT_SCALE[3] = { 5.0, 5.0, 5.0 }; -static float spawnpoint[3]; -static bool hasSpawnpoint; -static ArrayList entities; -static ArrayList inputs; +static ArrayList validMaps; +static ArrayList validSets; static KeyValues kv; static StringMap mapConfigs; -static StringMap mapInputs; +static char currentSet[16] = "default"; + static Handle suspenseTimer, thirdPersonTimer; +static char nextRoundMap[64]; +static int mapChangeMsgTicks = 5; + // TODO: Disable weapon drop enum struct EntityConfig { @@ -85,8 +87,19 @@ enum struct EntityConfig { char type[32]; char model[64]; float scale[3]; + float offset[3]; } +enum struct MapConfig { + ArrayList entities; + ArrayList inputs; + float spawnpoint[3]; + bool hasSpawnpoint; + int mapTime; +} + +static MapConfig mapConfig; + public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { lateLoaded = late; return APLRes_Success; @@ -98,19 +111,14 @@ public void OnPluginStart() { SetFailState("This plugin is for L4D2 only."); } - kv = new KeyValues("EntityConfig"); + validMaps = new ArrayList(ByteCountToCells(64)); + validSets = new ArrayList(ByteCountToCells(16)); + mapConfigs = new StringMap(); - char sPath[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, sPath, sizeof(sPath), "data/hideandseek.cfg"); - - if(!FileExists(sPath) || !kv.ImportFromFile(sPath)) { - delete kv; + if(!ReloadMapDB()) { SetFailState("Could not load entity config from data/hideandseek.cfg"); } - mapConfigs = new StringMap(); - mapInputs = new StringMap(); - ConVar hGamemode = FindConVar("mp_gamemode"); hGamemode.GetString(gamemode, sizeof(gamemode)); hGamemode.AddChangeHook(Event_GamemodeChange); @@ -131,12 +139,270 @@ public void OnPluginStart() { } RegConsoleCmd("sm_joingame", Command_Join, "Joins or joins someone else"); - RegAdminCmd("sm_hs_blockers", Command_ToggleBlockers, ADMFLAG_KICK, "Toggle nav blockers"); - RegAdminCmd("sm_hs_props", Command_ToggleProps, ADMFLAG_KICK, "Toggle props"); - RegAdminCmd("sm_hs_clear", Command_Clear, ADMFLAG_KICK, "Toggle props"); + RegAdminCmd("sm_hs", Command_HideAndSeek, ADMFLAG_CHEATS, "The main command. see /hs help"); } +bool ReloadMapDB() { + if(kv != null) { + delete kv; + } + kv = new KeyValues("hideandseek"); + + char sPath[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, sPath, sizeof(sPath), "data/hideandseek.cfg"); + + + 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; +} + +public Action Command_HideAndSeek(int client, int args) { + if(args > 0) { + char subcmd[16]; + GetCmdArg(1, subcmd, sizeof(subcmd)); + if(StrEqual(subcmd, "r") || StrEqual(subcmd, "reload", false)) { + GetCurrentMap(currentMap, sizeof(currentMap)); + char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + if(ReloadMapDB()) { + if(!LoadConfigForMap(currentMap)) { + ReplyToCommand(client, "Warn: Map has no config file"); + } + Cleanup(); + if(arg[0] == 'f') { + CreateTimer(0.1, Timer_RoundStart); + } + SetupEntities(isNavBlockersEnabled, isPropsEnabled, isPortalsEnabled); + ReplyToCommand(client, "Reloaded map from config"); + } else { + ReplyToCommand(client, "Error occurred while reloading map file"); + } + } else if(StrEqual(subcmd, "set", false)) { + if(args == 1) { + ReplyToCommand(client, "Current Map Set: \"%s\" (Specify with /hs set )", currentSet); + if(validSets.Length == 0) ReplyToCommand(client, "Available Sets: (no map config found)"); + else { + ReplyToCommand(client, "Available Sets: "); + char set[16]; + for(int i = 0; i < validSets.Length; i++) { + validSets.GetString(i, set, sizeof(set)); + ReplyToCommand(client, "%d. %s", i + 1, set); + } + } + } else { + GetCmdArg(2, currentSet, sizeof(currentSet)); + if(!LoadConfigForMap(currentMap)) { + ReplyToCommand(client, "Warn: Map has no config file"); + } + Cleanup(); + SetupEntities(isNavBlockersEnabled, isPropsEnabled, isPortalsEnabled); + ReplyToCommand(client, "Set the current set to \"%s\"", currentSet); + } + } 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(isNavBlockersEnabled) { + EntFire("hsblocker", "Disable"); + ReplyToCommand(client, "Disabled all custom gamemode blockers"); + } else { + EntFire("hsblocker", "Enable"); + ReplyToCommand(client, "Enabled all custom gamemode blockers"); + } + isNavBlockersEnabled = !isNavBlockersEnabled; + isUnknown = false; + } + if(doAll || StrEqual(type, "props", false)) { + if(isPropsEnabled) { + EntFire("hsprop", "Disable"); + EntFire("hsprop", "DisableCollision"); + ReplyToCommand(client, "Disabled all custom gamemode props"); + } else { + EntFire("hsprop", "Enable"); + EntFire("hsprop", "EnableCollision"); + ReplyToCommand(client, "Enabled all custom gamemode props"); + } + isPropsEnabled = !isPropsEnabled; + isUnknown = false; + } + if(doAll || StrEqual(type, "portals", false)) { + if(isPortalsEnabled) { + EntFire("hsportal", "Disable"); + ReplyToCommand(client, "Disabled all custom gamemode portals"); + } else { + EntFire("hsportal", "Enable"); + ReplyToCommand(client, "Enabled all custom gamemode portals"); + } + isPortalsEnabled = !isPortalsEnabled; + isUnknown = false; + } + if(isUnknown) ReplyToCommand(client, "Specify the type to affect: 'blockers', 'props', 'portals', or 'all'"); + } else if(StrEqual(subcmd, "clear", false)) { + static char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + bool doAll = StrEqual(arg, "all"); + bool isUnknown = true; + + if(doAll || StrEqual(arg, "props")) { + EntFire("hsprop", "kill"); + ReplyToCommand(client, "Removed all custom gamemode props"); + isUnknown = false; + } + if(doAll || StrEqual(arg, "blockers")) { + EntFire("hsblocker", "kill"); + ReplyToCommand(client, "Removed all custom gamemode blockers"); + isUnknown = false; + } + if(doAll || StrEqual(arg, "portals")) { + EntFire("hsportal", "kill"); + ReplyToCommand(client, "Removed all custom gamemode portals"); + isUnknown = false; + } + if(isUnknown) ReplyToCommand(client, "Specify the type to affect: 'blockers', 'props', 'portals', or 'all'"); + } else if(StrEqual(subcmd, "settime")) { + int prev = GetMapTime(); + static char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + int time = StringToInt(arg); + mapConfig.mapTime = time; + SetMapTime(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); + SetTick(tick); + ReplyToCommand(client, "Set tick time to %d", tick); + } else if(StrEqual(subcmd, "map")) { + 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(currentMap, map, false)) { + foundMap = true; + } + } while(!foundMap); + PrintToChatAll("[H&S] Switching map to %s", 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(2, arg2, sizeof(arg2)); + if(IsMapValid(arg2)) { + strcopy(nextRoundMap, sizeof(nextRoundMap), arg2); + PrintToChatAll("[H&S] Switching map next round to %s", 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(2, 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, "bots")) { + if(args == 2) { + char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + if(StrEqual(arg, "toggle")) { + bool newValue = !IsBotsEnabled(); + SetBotsEnabled(newValue); + if(newValue) ReplyToCommand(client, "Bots are now enabled"); + else ReplyToCommand(client, "Bots are now disabled"); + return Plugin_Handled; + } else if(StrEqual(arg, "on") || StrEqual(arg, "true")) { + SetBotsEnabled(true); + ReplyToCommand(client, "Bots are now enabled"); + return Plugin_Handled; + } else if(StrEqual(arg, "off") || StrEqual(arg, "false")) { + SetBotsEnabled(false); + ReplyToCommand(client, "Bots are now disabled"); + return Plugin_Handled; + } + } + if(IsBotsEnabled()) ReplyToCommand(client, "Bots are enabled"); + else ReplyToCommand(client, "Bots are disabled"); + } + return Plugin_Handled; + } + ReplyToCommand(client, " - Hide & Seek Commands -"); + ReplyToCommand(client, "toggle : Toggles all specified"); + ReplyToCommand(client, "set [new set]: Change the prop set or view current"); + 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, "setspawn: Sets the temporary spawnpoint for the map"); + ReplyToCommand(client, "stuck: Teleports you to spawn to unstuck yourself"); + ReplyToCommand(client, "bots [toggle, [value]]: View if bots are enabled, or turn them on"); + return Plugin_Handled; +} + + public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) { if(isEnabled) { if(!StrEqual(command, "say")) { //Is team message @@ -153,25 +419,35 @@ public Action OnClientSayCommand(int client, const char[] command, const char[] return Plugin_Continue; } -ArrayList LoadConfigForMap(const char[] map) { +bool LoadConfigForMap(const char[] map) { + kv.Rewind(); if (kv.JumpToKey(map)) { - ArrayList configs = new ArrayList(sizeof(EntityConfig)); - ArrayList entInputs = new ArrayList(ByteCountToCells(64)); + MapConfig config; + config.entities = new ArrayList(sizeof(EntityConfig)); + config.inputs = new ArrayList(ByteCountToCells(64)); + validSets.Clear(); if(kv.JumpToKey("ents")) { kv.GotoFirstSubKey(); + char entSet[16]; do { - EntityConfig config; - kv.GetVector("origin", config.origin, NULL_VECTOR); - kv.GetVector("rotation", config.rotation, NULL_VECTOR); - kv.GetString("type", config.type, sizeof(config.type), "env_physics_blocker"); - kv.GetString("model", config.model, sizeof(config.model), ""); - if(config.model[0] != '\0') - Format(config.model, sizeof(config.model), "models/%s", config.model); - kv.GetVector("scale", config.scale, DEFAULT_SCALE); - - configs.PushArray(config); + 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", entSet, sizeof(entSet), "default"); + if(validSets.FindString(entSet) == -1) { + validSets.PushString(entSet); + } + if(StrEqual(currentSet, entSet, false)) { + config.entities.PushArray(entCfg); + } } while (kv.GotoNextKey()); - // Both JumpToKey and GotoFirstSubKey both traverse, i guess, go back + // JumpToKey and GotoFirstSubKey both traverse, i guess, go back kv.GoBack(); kv.GoBack(); } @@ -182,81 +458,75 @@ ArrayList LoadConfigForMap(const char[] map) { static char buffer[64]; do { kv.GetSectionName(buffer, sizeof(buffer)); - entInputs.PushString(buffer); + config.inputs.PushString(buffer); kv.GetString(NULL_STRING, buffer, sizeof(buffer)); - entInputs.PushString(buffer); + config.inputs.PushString(buffer); } while (kv.GotoNextKey(false)); kv.GoBack(); } + int mapTime; + + config.hasSpawnpoint = false; + if(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(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("[H&S] Using provided custom spawnpoint for set %s at %0.1f, %0.1f, %0.1f", currentSet, config.spawnpoint[0], config.spawnpoint[1], config.spawnpoint[2]); + config.hasSpawnpoint = true; + } + mapTime = kv.GetNum("maptime", 0); + } + } while(kv.GotoNextKey(true)); + kv.GoBack(); + } - if(kv.GetVector("spawnpoint", spawnpoint)) { - hasSpawnpoint = true; - } else { - hasSpawnpoint = false; + 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("[H&S] 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 (GetSpawnPosition(config.spawnpoint, false)) { + PrintToServer("[H&S] Using map spawnpoint at %0.1f, %0.1f, %0.1f", config.spawnpoint[0], config.spawnpoint[1], config.spawnpoint[2]); + config.hasSpawnpoint = true; + } else { + PrintToServer("[H&S] Could not find any spawnpoints, using default spawn"); + config.hasSpawnpoint = false; + } } - // Store ArrayList handle - mapConfigs.SetValue(map, configs); + + // Use default maptime if exists + if(mapTime == 0) + mapTime = kv.GetNum("maptime", 0); + if(mapTime > 0) { + config.mapTime = mapTime; + PrintToServer("[H&S] Map time overwritten to %d seconds", mapTime); + } + mapConfigs.SetArray(map, config, sizeof(MapConfig)); // Discard entInputs if unused - if(entInputs.Length > 0) - mapInputs.SetValue(map, entInputs); - else - delete entInputs; - return configs; - } else { - return null; - } -} - -public Action Command_Clear(int client, int args) { - if(args > 0) { - static char arg[16]; - GetCmdArg(1, arg, sizeof(arg)); - if(StrEqual(arg, "props")) { - EntFire("hsprop", "kill"); - ReplyToCommand(client, "Removed all custom gamemode props"); - return Plugin_Continue; - } else if(StrEqual(arg, "blockers")) { - EntFire("hsblockers", "kill"); - ReplyToCommand(client, "Removed all custom gamemode blockers"); - return Plugin_Continue; - + if(config.inputs.Length == 0) { + delete config.inputs; } - } - ReplyToCommand(client, "Specify 'props' or 'blockers'"); - return Plugin_Continue; -} - -public Action Command_ToggleBlockers(int client, int args) { - if(isNavBlockersEnabled) { - EntFire("hsblocker", "Disable"); - ReplyToCommand(client, "Disabled all custom gamemode blockers"); + mapConfig = config; + return true; } else { - EntFire("hsblocker", "Enable"); - ReplyToCommand(client, "Enabled all custom gamemode blockers"); + mapConfig.hasSpawnpoint = false; + PrintToServer("[H&S] No map config exists for %s", map); + return false; } - isNavBlockersEnabled = !isNavBlockersEnabled; - return Plugin_Handled; -} - -public Action Command_ToggleProps(int client, int args) { - if(isPropsEnabled) { - EntFire("hsprop", "Disable"); - EntFire("hsprop", "DisableCollision"); - ReplyToCommand(client, "Disabled all custom gamemode props"); - } else { - EntFire("hsprop", "Enable"); - EntFire("hsprop", "EnableCollision"); - ReplyToCommand(client, "Enabled all custom gamemode props"); - } - isPropsEnabled = !isPropsEnabled; - return Plugin_Handled; } public Action Command_Join(int client, int args) { static float tpLoc[3]; + GetSpawnPosition(tpLoc); if(args == 1) { - GetClientAbsOrigin(client, tpLoc); static char arg1[32]; GetCmdArg(1, arg1, sizeof(arg1)); char target_name[MAX_TARGET_LENGTH]; @@ -292,12 +562,6 @@ public Action Command_Join(int client, int args) { ReplyToCommand(client, "You are already in-game as a seeker."); return Plugin_Handled; } - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { - GetClientAbsOrigin(i, tpLoc); - break; - } - } isPendingPlay[client] = false; ChangeClientTeam(client, 2); L4D_RespawnPlayer(client); @@ -320,7 +584,7 @@ public void OnClientConnected(int client) { } public Action Timer_KeepWaiting(Handle h) { - L4D2_ExecVScriptCode("g_ModeScript.MutationState.StateTick = -40"); + SetTick(-40); SetState(State_Startup); PrintHintTextToAll("Waiting for players to join..."); return IsGameSoloOrPlayersLoading() ? Plugin_Continue : Plugin_Stop; @@ -331,24 +595,21 @@ public void OnClientDisconnect(int client) { currentPlayers--; } - public void OnMapStart() { if(!isEnabled) return; + currentSeeker = 0; + char map[64]; GetCurrentMap(map, sizeof(map)); if(!StrEqual(currentMap, map)) { + PrintToServer("[H&S] Map changed, loading fresh config"); strcopy(currentMap, sizeof(currentMap), map); - static char mapTime[16]; - L4D2_GetVScriptOutput("g_ModeScript.MutationState.MapTime", mapTime, sizeof(mapTime)); - PrintToServer("[H&S] Map %s has a time of %s seconds", map, mapTime); - - - if(!mapConfigs.GetValue(map, entities)) { - entities = LoadConfigForMap(map); + if(!mapConfigs.GetArray(map, mapConfig, sizeof(MapConfig))) { + LoadConfigForMap(map); } - mapInputs.GetValue(map, inputs); + strcopy(currentSet, sizeof(currentSet), "default"); } #if defined DEBUG_BLOCKERS @@ -384,22 +645,29 @@ public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[ #endif if(isEnabled) { HookEvent("round_end", Event_RoundEnd); - HookEvent("round_start", Event_RoundStart); + HookEvent("round_start_post_nav", Event_RoundStart); HookEvent("item_pickup", Event_ItemPickup); HookEvent("player_death", Event_PlayerDeath); SetupEntities(); - CreateTimer(15.0, Timer_RoundStart); + CreateTimer(12.0, Timer_RoundStart); if(suspenseTimer != null) delete suspenseTimer; suspenseTimer = CreateTimer(20.0, Timer_Music, _, TIMER_REPEAT); if(thirdPersonTimer != null) delete thirdPersonTimer; thirdPersonTimer = CreateTimer(1.0, Timer_CheckPlayers, _, TIMER_REPEAT); + if(!lateLoaded) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) + ForcePlayerSuicide(i); + } + } } else if(!lateLoaded && suspenseTimer != null) { UnhookEvent("round_end", Event_RoundEnd); UnhookEvent("round_start", Event_RoundStart); UnhookEvent("item_pickup", Event_ItemPickup); UnhookEvent("player_death", Event_PlayerDeath); + Cleanup(); delete suspenseTimer; delete thirdPersonTimer; } @@ -443,6 +711,9 @@ public void OnClientPutInServer(int client) { isPendingPlay[client] = true; isNearbyPlaying[client] = false; PrintToChatAll("%N will play next round", client); + float pos[3]; + GetSpawnPosition(pos); + TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); } } @@ -471,19 +742,26 @@ public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) } public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { - if(hasSpawnpoint) { + if(mapConfig.hasSpawnpoint) { + PrintToServer("[H&S] Using provided spawnpoint: %.1f %.1f %.1f", mapConfig.spawnpoint[0], mapConfig.spawnpoint[1], mapConfig.spawnpoint[2]); for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i)) { - TeleportEntity(i, spawnpoint, NULL_VECTOR, NULL_VECTOR); + TeleportEntity(i, mapConfig.spawnpoint, NULL_VECTOR, NULL_VECTOR); } } } + EntFire("relay_intro_start", "Kill"); SetupEntities(); CreateTimer(15.0, Timer_RoundStart); } public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { + if(nextRoundMap[0] != '\0') { + ForceChangeLevel(nextRoundMap, "SetMapSelect"); + nextRoundMap[0] = '\0'; + return; + } currentSeeker = 0; static float tpLoc[3]; for(int i = 1; i <= MaxClients; i++) { @@ -578,9 +856,27 @@ public Action Timer_RoundStart(Handle h) { } entity = INVALID_ENT_REFERENCE; while ((entity = FindEntityByClassname(entity, "func_brush")) != INVALID_ENT_REFERENCE) { - AcceptEntityInput(entity, "Kll"); + AcceptEntityInput(entity, "Kill"); } + entity = INVALID_ENT_REFERENCE; + while ((entity = FindEntityByClassname(entity, "infected")) != INVALID_ENT_REFERENCE) { + AcceptEntityInput(entity, "Kill"); + } + entity = INVALID_ENT_REFERENCE; + while ((entity = FindEntityByClassname(entity, "prop_door_rotating")) != INVALID_ENT_REFERENCE) { + AcceptEntityInput(entity, "Unlock"); + if(GetURandomFloat() > 0.5) + AcceptEntityInput(entity, "Toggle"); + } + while ((entity = FindEntityByClassname(entity, "func_simpleladder")) != INVALID_ENT_REFERENCE) { + SetEntProp(entity, Prop_Send, "m_iTeamNum", 0); + } PrintToServer("[H&S] Pressing buttons"); + if(mapConfig.mapTime > 0) { + SetMapTime(mapConfig.mapTime); + } + + PrintToServer("[H&S] Map time is %d seconds", GetMapTime()); return Plugin_Continue; } public Action Timer_CheckWeapons(Handle h) { @@ -599,14 +895,17 @@ public Action Timer_CheckWeapons(Handle h) { stock int CreateEnvBlockerBox(const float pos[3], bool enabled = true) { int entity = CreateEntityByName("env_physics_blocker"); + if(entity == -1) return -1; DispatchKeyValue(entity, "targetname", "hsblocker"); DispatchKeyValue(entity, "initialstate", "1"); DispatchKeyValue(entity, "BlockType", "0"); TeleportEntity(entity, pos, NULL_VECTOR, NULL_VECTOR); - DispatchSpawn(entity); - if(enabled) - AcceptEntityInput(entity, "Enable"); - return entity; + if(DispatchSpawn(entity)) { + if(enabled) + AcceptEntityInput(entity, "Enable"); + return entity; + } + return -1; } stock int CreateEnvBlockerScaled(const char[] entClass, const float pos[3], const float scale[3] = { 5.0, 5.0, 5.0 }, bool enabled = true) { @@ -623,18 +922,85 @@ stock int CreateEnvBlockerScaled(const char[] entClass, const float pos[3], cons DispatchKeyValueVector(entity, "maxs", scale); TeleportEntity(entity, pos, NULL_VECTOR, NULL_VECTOR); - DispatchSpawn(entity); - SetEntPropVector(entity, Prop_Send, "m_vecMaxs", scale); - SetEntPropVector(entity, Prop_Send, "m_vecMins", mins); - if(enabled) + if(DispatchSpawn(entity)) { + #if defined DEBUG_LOG_MAPSTART + PrintToServer("spawn blocker scaled %.1f %.1f %.1f scale [%.0f %.0f %.0f]", pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); + #endif + SetEntPropVector(entity, Prop_Send, "m_vecMaxs", scale); + SetEntPropVector(entity, Prop_Send, "m_vecMins", mins); + if(enabled) + AcceptEntityInput(entity, "Enable"); + #if defined DEBUG_BLOCKERS + Effect_DrawBeamBoxRotatableToAll(pos, mins, scale, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {255, 0, 0, 255}, 0); + #endif + return entity; + } + return -1; +} +enum PortalType { + Portal_Relative, + Portal_Teleport +} +PortalType entityPortalType[2048]; +float entityPortalOffsets[2048][3]; +stock int CreatePortal(PortalType type, const char model[64], const float pos[3], const float offset[3] = { 40.0, 40.0, 0.0 }, const float scale[3] = { 5.0, 5.0, 5.0 }) { + int entity = CreateEntityByName("trigger_multiple"); + if(entity == -1) return -1; + DispatchKeyValue(entity, "spawnflags", "513"); + DispatchKeyValue(entity, "solid", "6"); + DispatchKeyValue(entity, "targetname", "hsportal"); + DispatchKeyValue(entity, "wait", "0"); + if(DispatchSpawn(entity)) { + TeleportEntity(entity, pos, NULL_VECTOR, NULL_VECTOR); + static float mins[3]; + mins = scale; + NegateVector(mins); + SetEntPropVector(entity, Prop_Send, "m_vecMaxs", scale); + SetEntPropVector(entity, Prop_Send, "m_vecMins", mins); + SetEntProp(entity, Prop_Send, "m_nSolidType", 2); + + HookSingleEntityOutput(entity, "OnStartTouch", OnPortalTouch, false); + #if defined DEBUG_BLOCKERS + Effect_DrawBeamBoxRotatableToAll(pos, mins, scale, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {255, 0, 255, 255}, 0); + #endif + #if defined DEBUG_LOG_MAPSTART + PrintToServer("spawn portal %d - pos %.1f %.1f %.1f - scale %.1f %.1f %.1f", entity, pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); + #endif AcceptEntityInput(entity, "Enable"); - #if defined DEBUG_BLOCKERS - Effect_DrawBeamBoxRotatableToAll(pos, mins, scale, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 150.0, 0.1, 0.1, 0, 0.0, {0, 255, 0, 255}, 0); - #endif - #if defined DEBUG_LOG_MAPSTART - PrintToServer("spawn blocker scaled %.1f %.1f %.1f scale [%.0f %.0f %.0f]", pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); - #endif - return entity; + + entityPortalOffsets[entity] = NULL_VECTOR; + + // Convert relative offset to one based off full scale: + entityPortalType[entity] = type; + if(type == Portal_Relative) { + if(offset[0] != 0.0) entityPortalOffsets[entity][0] = (scale[0] * 2) + offset[0]; + if(offset[1] != 0.0) entityPortalOffsets[entity][1] = (scale[1] * 2) + offset[1]; + if(offset[2] != 0.0) entityPortalOffsets[entity][2] = (scale[2] * 2) + offset[2]; + } else { + entityPortalOffsets[entity] = offset; + } + + return entity; + } + return -1; +} + +void OnPortalTouch(const char[] output, int caller, int activator, float delay) { + if(entityPortalType[caller] == Portal_Relative) { + float pos[3]; + GetClientAbsOrigin(activator, pos); + float ang[3]; + GetClientAbsAngles(activator, ang); + if(ang[0] < 0) pos[0] -= entityPortalOffsets[caller][0]; + else pos[0] += entityPortalOffsets[caller][0]; + if(ang[1] < 0) pos[1] -= entityPortalOffsets[caller][1]; + else pos[1] += entityPortalOffsets[caller][1]; + if(ang[2] < 0) pos[2] -= entityPortalOffsets[caller][2]; + else pos[2] += entityPortalOffsets[caller][2]; + TeleportEntity(activator, pos, NULL_VECTOR, NULL_VECTOR); + } else { + TeleportEntity(activator, entityPortalOffsets[caller], NULL_VECTOR, NULL_VECTOR); + } } stock int CreatePropDynamic(const char[] model, const float pos[3], const float ang[3]) { @@ -647,18 +1013,46 @@ stock int CreatePropPhysics(const char[] model, const float pos[3], const float stock int CreateProp(const char[] entClass, const char[] model, const float pos[3], const float ang[3]) { int entity = CreateEntityByName(entClass); + if(entity == -1) return -1; DispatchKeyValue(entity, "model", model); DispatchKeyValue(entity, "solid", "6"); DispatchKeyValue(entity, "targetname", "hsprop"); DispatchKeyValue(entity, "disableshadows", "1"); TeleportEntity(entity, pos, ang, NULL_VECTOR); - DispatchSpawn(entity); - #if defined DEBUG_LOG_MAPSTART - PrintToServer("spawn prop %.1f %.1f %.1f model %s", pos[0], pos[1], pos[2], model[7]); - #endif - return entity; + if(DispatchSpawn(entity)) { + #if defined DEBUG_LOG_MAPSTART + PrintToServer("spawn prop %.1f %.1f %.1f model %s", pos[0], pos[1], pos[2], model[7]); + #endif + return entity; + } + return -1; } +// Taken from silver's https://forums.alliedmods.net/showthread.php?p=1658873 +stock int CreateDynamicLight(float vOrigin[3], float vAngles[3], int color, float brightness, int style = 0) { + int entity = CreateEntityByName("light_dynamic"); + if( entity == -1) + return -1; + + DispatchKeyValue(entity, "_light", "0 0 0 255"); + DispatchKeyValue(entity, "brightness", "1"); + DispatchKeyValueFloat(entity, "spotlight_radius", 32.0); + DispatchKeyValueFloat(entity, "distance", brightness); + DispatchKeyValue(entity, "targetname", "hslamp"); + DispatchKeyValueFloat(entity, "style", float(style)); + SetEntProp(entity, Prop_Send, "m_clrRender", color); + if(DispatchSpawn(entity)) { + TeleportEntity(entity, vOrigin, vAngles, NULL_VECTOR); + AcceptEntityInput(entity, "TurnOn"); + #if defined DEBUG_LOG_MAPSTART + PrintToServer("spawn dynamic light %.1f %.1f %.1f", vOrigin[0], vOrigin[1], vOrigin[2]); + #endif + return entity; + } + return -1; +} + + stock void CheatCommand(int client, const char[] command, const char[] argument1) { int userFlags = GetUserFlagBits(client); SetUserFlagBits(client, ADMFLAG_ROOT); @@ -671,50 +1065,98 @@ stock void CheatCommand(int client, const char[] command, const char[] argument1 stock void EntFire(const char[] name, const char[] input) { static char targetname[64]; - for(int i = MAXPLAYERS + 1; i <= GetMaxEntities(); i++) { - if(IsValidEntity(i) && IsValidEdict(i)) { + static char cmd[32]; + #if defined DEBUG_LOG_MAPSTART + PrintToServer("[H&S] EntFire: %s \"%s\"", name, input); + #endif + int len = SplitString(input, " ", cmd, sizeof(cmd)); + if(len > -1) SetVariantString(input[len]); + for(int i = MAXPLAYERS + 1; i <= 4096; i++) { + if(IsValidEntity(i) && (IsValidEdict(i) || EntIndexToEntRef(i) != -1)) { GetEntPropString(i, Prop_Data, "m_iName", targetname, sizeof(targetname)); - if(StrEqual(targetname, name)) { - AcceptEntityInput(i, input); - } + if(StrEqual(targetname, name, false)) { + if(len > -1) AcceptEntityInput(i, cmd); + else AcceptEntityInput(i, input); + } /*else { + GetEntityClassname(targetname, sizeof(targetname)); + if(StrEqual(targetname, name, false)) { + if(len > -1) AcceptEntityInput(i, cmd); + else AcceptEntityInput(i, input); + } + }*/ } } } -void SetupEntities() { - if(entities != null) { - PrintToServer("[H&S] Found map entity config, deploying %d entities", entities.Length); - for(int i = 0; i < entities.Length; i++) { +void SetupEntities(bool blockers = true, bool props = true, bool portals = true) { + if(mapConfig.entities != null) { + PrintToServer("[H&S] Deploying %d custom entities (Set: %s) (blockers:%b props:%b portals:%b)", mapConfig.entities.Length, currentSet, blockers, props, portals); + for(int i = 0; i < mapConfig.entities.Length; i++) { EntityConfig config; - entities.GetArray(i, config); + mapConfig.entities.GetArray(i, config); if(config.model[0] != '\0') PrecacheModel(config.model); if(StrEqual(config.type, "env_physics_blocker")) { - CreateEnvBlockerScaled(config.type, config.origin, config.scale, isNavBlockersEnabled); - } else { - CreateProp(config.type, config.model, config.origin, config.rotation); + if(blockers && CreateEnvBlockerScaled(config.type, config.origin, config.scale, isNavBlockersEnabled) == -1) { + PrintToServer("[H&S: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) { + PrintToServer("[H&S: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) { + PrintToServer("[H&S: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) { + PrintToServer("[H&S: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) { + PrintToServer("[H&S] Failed to spawn light source for _lantern"); + } else { + SetParent(child, parent); + TeleportEntity(parent, config.origin, NULL_VECTOR, NULL_VECTOR); + } + } + } else if(props) { + if(CreateProp(config.type, config.model, config.origin, config.rotation) == -1) { + PrintToServer("[H&S: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(inputs != null) { - for(int i = 0; i < inputs.Length - 1; i += 2) { - inputs.GetString(i, key, sizeof(key)); - inputs.GetString(i + 1, value, sizeof(value)); + 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); - #if defined DEBUG_LOG_MAPSTART - PrintToServer("[H&S] EntFire: %s %s", key, value); - #endif } } - - - } } +int GetColorInt(int r, int g, int b) { + int color = r; + color += 256 * g; + color += 65536 * b; + return color; +} + +void Cleanup() { + EntFire("hsprop", "kill"); + EntFire("hsblocker", "kill"); + EntFire("hsportal", "kill"); +} + GameState GetState() { if(!isEnabled) return State_Unknown; static char buffer[4]; @@ -730,7 +1172,7 @@ GameState GetState() { int GetSlasher() { if(!isEnabled) return -1; static char buffer[8]; - L4D2_GetVScriptOutput("g_ModeScript.MutationState.CurrentSlasher ? g_ModeScript.MutationState.CurrentSlasher.GetPlayerUserId() : -1", buffer, sizeof(buffer)); + L4D2_GetVScriptOutput("g_ModeScript.MutationState.CurrentSlasher && \"GetPlayerUserId\" in g_ModeScript.MutationState.CurrentSlasher ? g_ModeScript.MutationState.CurrentSlasher.GetPlayerUserId() : -1", buffer, sizeof(buffer)); int uid = StringToInt(buffer); if(uid > 0) { return GetClientOfUserId(uid); @@ -747,10 +1189,62 @@ int GetTick() { if(StringToIntEx(buffer, value) > 0) { return value; } else { - return value; + return -1; } } +void SetTick(int tick) { + static char buf[64]; + Format(buf, sizeof(buf), "g_ModeScript.MutationState.StateTick = %d", tick); + L4D2_ExecVScriptCode(buf); +} + + +int GetMapTime() { + static char mapTime[16]; + L4D2_GetVScriptOutput("g_ModeScript.MutationState.MapTime", mapTime, sizeof(mapTime)); + return StringToInt(mapTime); +} + +void SetMapTime(int seconds) { + static char buf[64]; + Format(buf, sizeof(buf), "g_ModeScript.MutationState.MapTime = %d", seconds); + L4D2_ExecVScriptCode(buf); +} + +Action Timer_ChangeMap(Handle h) { + PrintToChatAll("Changing map to %s in %d seconds", nextRoundMap, mapChangeMsgTicks); + if(mapChangeMsgTicks-- == 0) { + ForceChangeLevel(nextRoundMap, "H&SMapSelect"); + 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 GetSpawnPosition(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; +} + bool SetState(GameState state) { if(!isEnabled) return false; static char buffer[64]; @@ -769,4 +1263,36 @@ bool IsGameSoloOrPlayersLoading() { } } return connecting > 0 || ingame == 1; +} + +//cm_NoSurvivorBots +bool SetBotsEnabled(bool value) { + static char buffer[64]; + if(value) + Format(buffer, sizeof(buffer), "g_ModeScript.MutationOptions.cm_NoSurvivorBots = true"); + else + Format(buffer, sizeof(buffer), "g_ModeScript.MutationOptions.cm_NoSurvivorBots = false"); + return L4D2_ExecVScriptCode(buffer); +} + +bool IsBotsEnabled() { + static char result[8]; + L4D2_GetVScriptOutput("g_ModeScript.MutationState.cm_NoSurvivorBots", result, sizeof(result)); + return StrEqual(result, "true", 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; +} + +void SetParent(int child, int parent) { + SetVariantString("!activator"); + AcceptEntityInput(child, "SetParent", parent); } \ No newline at end of file