From a9be3ff6529bea3f22b6f486534df7fb17cfaa88 Mon Sep 17 00:00:00 2001 From: ZypherusKhan Date: Thu, 2 Apr 2026 00:16:18 +0530 Subject: [PATCH] CTF spawn protection + KOTZ summary fix for player points (#537) * Add spawn protection setting * Fix summary to show individual player points * Fix --- Gamemodes/CaptureTheFlag.cs | 30 ++++++++++++++++++++++- Gamemodes/KingOfTheZones.cs | 48 +++++++++++++++++++++---------------- Resources/Lang/en_US.jsonc | 2 ++ 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/Gamemodes/CaptureTheFlag.cs b/Gamemodes/CaptureTheFlag.cs index e3b196fc8..35ac05381 100644 --- a/Gamemodes/CaptureTheFlag.cs +++ b/Gamemodes/CaptureTheFlag.cs @@ -22,6 +22,7 @@ public static class CaptureTheFlag private static OptionItem WhenFlagCarrierGetsTagged; private static OptionItem SpeedReductionForFlagCarrier; private static OptionItem TagCooldown; + private static OptionItem SpawnProtectionTime; private static OptionItem GameEndCriteria; private static OptionItem RoundsToPlay; private static OptionItem PointsToWin; @@ -55,6 +56,7 @@ public static class CaptureTheFlag private static Dictionary PlayerData = []; private static Dictionary DefaultOutfits = []; private static Dictionary TemporarilyOutPlayers = []; + private static Dictionary SpawnProtectionTimes = []; private static bool ValidTag; private static long GameStartTS; private static int RoundsPlayed => TeamData.Values.Sum(x => x.RoundsWon); @@ -172,6 +174,11 @@ public static class CaptureTheFlag .SetGameMode(CustomGameMode.CaptureTheFlag) .SetValueFormat(OptionFormat.Multiplier) .SetColor(color); + + SpawnProtectionTime = new IntegerOptionItem(id + 13, "CTF_SpawnProtectionTime", new(0, 30, 1), 5, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(color); } public static bool KnowTargetRoleColor(PlayerControl target, ref string color) @@ -240,6 +247,11 @@ public static class CaptureTheFlag sb.Append(string.Format(Translator.GetString("CTF_BackIn"), backTS - Utils.TimeStamp)); sb.Append('\n'); } + else if (SpawnProtectionTimes.TryGetValue(id, out long protectionEndTS)) + { + sb.Append(string.Format(Translator.GetString("CTF_Suffix.SpawnProtectionTime"), protectionEndTS - Utils.TimeStamp)); + sb.Append('\n'); + } sb.Append(Utils.ColorString(Color.blue, TeamData[CTFTeam.Blue].RoundsWon.ToString())); sb.Append("<#ffffff> | "); @@ -326,6 +338,7 @@ public static class CaptureTheFlag PlayerData = Main.PlayerStates.Keys.ToDictionary(x => x, _ => new CTFPlayerData()); DefaultOutfits = Main.EnumeratePlayerControls().ToDictionary(x => x.PlayerId, x => x.Data.DefaultOutfit); TemporarilyOutPlayers = []; + SpawnProtectionTimes = []; ValidTag = false; SendRPC(); } @@ -472,7 +485,7 @@ public static class CaptureTheFlag public static void OnCheckMurder(PlayerControl killer, PlayerControl target) { - if (!ValidTag || TemporarilyOutPlayers.ContainsKey(killer.PlayerId) || !PlayerTeams.TryGetValue(target.PlayerId, out CTFTeam targetTeam) || !PlayerTeams.TryGetValue(killer.PlayerId, out CTFTeam killerTeam) || killerTeam == targetTeam || TeamData.Values.Any(x => x.FlagCarrier == killer.PlayerId)) return; + if (!ValidTag || TemporarilyOutPlayers.ContainsKey(killer.PlayerId) || SpawnProtectionTimes.ContainsKey(target.PlayerId) || !PlayerTeams.TryGetValue(target.PlayerId, out CTFTeam targetTeam) || !PlayerTeams.TryGetValue(killer.PlayerId, out CTFTeam killerTeam) || killerTeam == targetTeam || TeamData.Values.Any(x => x.FlagCarrier == killer.PlayerId)) return; new[] { killer, target }.Do(x => x.SetKillCooldownNonSync(KCD)); @@ -488,6 +501,7 @@ public static class CaptureTheFlag target.TP(targetTeam.GetFlagBase().Position); Main.AllPlayerSpeed[target.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); target.MarkDirtySettings(); + SpawnProtectionTimes[target.PlayerId] = Utils.TimeStamp + SpawnProtectionTime.GetInt(); break; case 1: target.Suicide(PlayerState.DeathReason.Kill); @@ -811,10 +825,24 @@ public static class CaptureTheFlag __instance.ReviveFromTemporaryExile(); __instance.TP(team.GetFlagBase().Position); RPC.PlaySoundRPC(__instance.PlayerId, Sounds.SpawnSound); + SpawnProtectionTimes[__instance.PlayerId] = Utils.TimeStamp + SpawnProtectionTime.GetInt(); } if (!notified) Utils.NotifyRoles(SpecifySeer: __instance, SpecifyTarget: __instance, SendOption: SendOption.None); } + + if (SpawnProtectionTimes.TryGetValue(__instance.PlayerId, out long protectionEndTS)) + { + if (now >= protectionEndTS) + { + SpawnProtectionTimes.Remove(__instance.PlayerId); + Utils.NotifyRoles(SpecifySeer: __instance, SpecifyTarget: __instance, SendOption: SendOption.None); + } + else if (!notified) + { + Utils.NotifyRoles(SpecifySeer: __instance, SpecifyTarget: __instance, SendOption: SendOption.None); + } + } } } } diff --git a/Gamemodes/KingOfTheZones.cs b/Gamemodes/KingOfTheZones.cs index 960addab5..f0b44544e 100644 --- a/Gamemodes/KingOfTheZones.cs +++ b/Gamemodes/KingOfTheZones.cs @@ -82,6 +82,7 @@ public static class KingOfTheZones private static Dictionary PlayerTeams = []; public static Dictionary RespawnTimes = []; private static Dictionary SpawnProtectionTimes = []; + private static Dictionary PlayerPoints = []; private static Dictionary Points = []; private static Dictionary ZoneDomination = []; private static Dictionary ZoneMoveSchedules = []; @@ -249,6 +250,7 @@ public static class KingOfTheZones KOTZTeam[] teams = Enum.GetValues(); Points = teams.ToDictionary(x => x, _ => 0); + PlayerPoints = Main.PlayerStates.Keys.ToDictionary(x => x, _ => 0); ZoneDomination = Zones.ToDictionary(x => x, _ => KOTZTeam.None); ZoneMoveSchedules = []; ZoneDowntimeExpire = []; @@ -591,13 +593,13 @@ public static class KingOfTheZones public static string GetStatistics(byte id) { - return string.Format(GetString("KOTZ.EndScreen.Statistics"), GetZoneTime(id)); + return string.Format(GetString("KOTZ.EndScreen.Statistics"), PlayerPoints.TryGetValue(id, out int points) ? points : 0); } public static int GetZoneTime(byte id) { - try { return Points[PlayerTeams[id]]; } - catch { return 0; } + if (!PlayerPoints.TryGetValue(id, out int points)) return 0; + return points; } public static bool CheckForGameEnd(out GameOverReason reason) @@ -839,23 +841,22 @@ public static class KingOfTheZones try { - Dictionary zoneTeamCounts = new(Zones.Count); - - foreach (SystemTypes zone in Zones) - zoneTeamCounts[zone] = new int[TeamsEnumSize]; + Dictionary zoneTeamCounts = Zones.ToDictionary(x => x, _ => new int[TeamsEnumSize]); + Dictionary> playersInZone = Zones.ToDictionary(x => x, _ => new List()); foreach (PlayerControl player in Main.EnumerateAlivePlayerControls()) { - if (RespawnTimes.ContainsKey(player.PlayerId)) - continue; + if (RespawnTimes.ContainsKey(player.PlayerId)) continue; + + PlainShipRoom room = player.GetPlainShipRoom(); + if (!room) continue; + + SystemTypes playerZone = room.RoomId; + if (!playersInZone.TryGetValue(playerZone, out List occupants)) continue; int team = (int)PlayerTeams[player.PlayerId]; - - foreach (SystemTypes zone in Zones) - { - if (player.IsInRoom(zone)) - zoneTeamCounts[zone][team]++; - } + zoneTeamCounts[playerZone][team]++; + occupants.Add(player.PlayerId); } foreach (SystemTypes zone in Zones) @@ -894,13 +895,20 @@ public static class KingOfTheZones : (KOTZTeam)maxTeam; } - foreach (KOTZTeam team in ZoneDomination.Values) + foreach ((SystemTypes zone, KOTZTeam winningTeam) in ZoneDomination) { - if (team != KOTZTeam.None) - Points[team]++; - } + if (winningTeam == KOTZTeam.None) continue; - Logger.Info($"Zone domination: {string.Join(", ", ZoneDomination.Select(x => $"{x.Key} = {x.Value}"))}", "KOTZ"); + Points[winningTeam]++; + + foreach (byte playerId in playersInZone[zone]) + { + if (PlayerTeams[playerId] != winningTeam) continue; + if (!Main.PlayerStates.TryGetValue(playerId, out PlayerState state) || state.IsDead) continue; + + PlayerPoints[playerId]++; + } + } } catch (Exception e) { Utils.ThrowException(e); } diff --git a/Resources/Lang/en_US.jsonc b/Resources/Lang/en_US.jsonc index dcfbfc222..b86a50400 100644 --- a/Resources/Lang/en_US.jsonc +++ b/Resources/Lang/en_US.jsonc @@ -4260,7 +4260,9 @@ "CTF_PlayerStats_Tags": "<#00a5ff>Tags: <#ffffff>{0}", "CTF_PointsToWin": "Points to Win", "CTF_RoundsToPlay": "Rounds to Play", + "CTF_SpawnProtectionTime": "Spawn Protection Time", "CTF_SpeedReductionForFlagCarrier": "Speed Reduction for Flag Carriers", + "CTF_Suffix.SpawnProtectionTime": "<#00ffa5>Under Spawn Protection <#888888>- <#ffff44>{0}", "CTF_TagCooldown": "Tag Cooldown", "CTF_TaggedPlayersGet": "Tagged Players get", "CTF_TaggedPlayersGet.Killed": "Killed",