using System; using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; using EHR.Modules; using UnityEngine; using static EHR.Translator; namespace EHR.Gamemodes; internal static class FreeForAll { private static Dictionary FFAShieldedList = []; private static Dictionary FFAIncreasedSpeedList = []; private static Dictionary FFADecreasedSpeedList = []; public static Dictionary FFALowerVisionList = []; public static Dictionary KillCount = []; public static int RoundTime; public static Dictionary PlayerTeams = []; public static readonly Dictionary TeamColors = new() { { 0, "#00ffff" }, { 1, "#ffff00" }, { 2, "#ff00ff" }, { 3, "#ff0000" }, { 4, "#00ff00" }, { 5, "#0000ff" }, { 6, "#ffffff" }, { 7, "#000000" } }; public static OptionItem FFAGameTime; public static OptionItem FFAKcd; public static OptionItem FFALowerVision; public static OptionItem FFAIncreasedSpeed; public static OptionItem FFADecreasedSpeed; public static OptionItem FFAShieldDuration; public static OptionItem FFAModifiedVisionDuration; public static OptionItem FFAModifiedSpeedDuration; public static OptionItem FFADisableVentingWhenTwoPlayersAlive; public static OptionItem FFADisableVentingWhenKcdIsUp; public static OptionItem FFAEnableRandomAbilities; public static OptionItem FFAEnableRandomTwists; public static OptionItem FFAShieldIsOneTimeUse; public static OptionItem FFAChatDuringGame; public static OptionItem FFATeamMode; public static OptionItem FFATeamNumber; public static void SetupCustomOption() { FFAGameTime = new IntegerOptionItem(67_223_001, "FFA_GameTime", new(30, 600, 10), 300, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetHeader(true); FFAKcd = new FloatOptionItem(67_223_002, "FFA_KCD", new(1f, 60f, 1f), 10f, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); FFADisableVentingWhenTwoPlayersAlive = new BooleanOptionItem(67_223_003, "FFA_DisableVentingWhenTwoPlayersAlive", true, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)); FFADisableVentingWhenKcdIsUp = new BooleanOptionItem(67_223_004, "FFA_DisableVentingWhenKCDIsUp", true, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)); FFAEnableRandomAbilities = new BooleanOptionItem(67_223_005, "FFA_EnableRandomAbilities", true, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)); FFAShieldDuration = new FloatOptionItem(67_223_006, "FFA_ShieldDuration", new(1f, 70f, 1f), 7f, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); FFAIncreasedSpeed = new FloatOptionItem(67_223_007, "FFA_IncreasedSpeed", new(0.1f, 5f, 0.1f), 1.5f, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier); FFADecreasedSpeed = new FloatOptionItem(67_223_008, "FFA_DecreasedSpeed", new(0.1f, 5f, 0.1f), 1f, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier); FFAModifiedSpeedDuration = new FloatOptionItem(67_223_009, "FFA_ModifiedSpeedDuration", new(1f, 60f, 1f), 10f, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); FFALowerVision = new FloatOptionItem(67_223_010, "FFA_LowerVision", new(0f, 1f, 0.05f), 0.5f, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier); FFAModifiedVisionDuration = new FloatOptionItem(67_223_011, "FFA_ModifiedVisionDuration", new(1f, 70f, 1f), 5f, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); FFAEnableRandomTwists = new BooleanOptionItem(67_223_012, "FFA_EnableRandomTwists", true, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)); FFAShieldIsOneTimeUse = new BooleanOptionItem(67_223_013, "FFA_ShieldIsOneTimeUse", true, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)); FFAChatDuringGame = new BooleanOptionItem(67_223_014, "FFA_ChatDuringGame", false, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)); FFATeamMode = new BooleanOptionItem(67_223_015, "FFA_TeamMode", false, TabGroup.GameSettings) .SetGameMode(CustomGameMode.FFA) .SetHeader(true) .SetColor(new Color32(0, 255, 165, byte.MaxValue)); FFATeamNumber = new IntegerOptionItem(67_223_016, "FFA_TeamNumber", new(2, 8, 1), 2, TabGroup.GameSettings) .SetParent(FFATeamMode) .SetGameMode(CustomGameMode.FFA) .SetColor(new Color32(0, 255, 165, byte.MaxValue)); } public static void Init() { if (Options.CurrentGameMode != CustomGameMode.FFA) return; FFADecreasedSpeedList = []; FFAIncreasedSpeedList = []; FFALowerVisionList = []; FFAShieldedList = []; KillCount = []; RoundTime = FFAGameTime.GetInt() - 10; Utils.SendRPC(CustomRPC.FFASync, 1, RoundTime); PlayerTeams = []; var allPlayers = Main.EnumerateAlivePlayerControls(); if (Main.GM.Value && AmongUsClient.Instance.AmHost) allPlayers = allPlayers.Without(PlayerControl.LocalPlayer); allPlayers = allPlayers.ExceptBy(ChatCommands.Spectators, x => x.PlayerId).ToArray(); foreach (PlayerControl pc in allPlayers) { KillCount[pc.PlayerId] = 0; Utils.SendRPC(CustomRPC.FFASync, 2, pc.PlayerId, 0); } if (FFATeamMode.GetBool()) { int i = 0; foreach (IEnumerable ids in allPlayers.Select(x => x.PlayerId).Shuffle().Partition(FFATeamNumber.GetInt())) foreach (byte id in ids) PlayerTeams[id] = i++; } } public static int GetRankFromScore(byte playerId) { try { int ms = KillCount[playerId]; int rank = 1 + KillCount.Values.Count(x => x > ms); rank += KillCount.Where(x => x.Value == ms).Select(x => x.Key).ToList().IndexOf(playerId); // In the old version, the struct 'KeyValuePair' was checked for equality using the inefficient runtime-provided implementation return rank; } catch { return PlayerControl.AllPlayerControls.Count; } } public static string GetHudText() { if (RoundTime == 60) { SoundManager.Instance.PlaySound(HudManager.Instance.LobbyTimerExtensionUI.lobbyTimerPopUpSound, false); Utils.FlashColor(new(1f, 1f, 0f, 0.4f), 1.4f); } return $"{RoundTime / 60:00}:{RoundTime % 60:00}"; } public static void OnPlayerAttack(PlayerControl killer, PlayerControl target) { try { if (target.inVent) { Logger.Info("Target is in a vent, kill blocked", "FFA"); return; } if (FFATeamMode.GetBool() && PlayerTeams[killer.PlayerId] == PlayerTeams[target.PlayerId]) { Logger.Info("Killer and Target are in the same team, attack blocked", "FFA"); return; } var aapc = Main.CachedAlivePlayerControls(); if (FFAShieldedList.TryGetValue(target.PlayerId, out long dur)) { killer.Notify(GetString("FFA_TargetIsShielded")); Logger.Info($"{killer.GetRealName().RemoveHtmlTags()} attacked shielded player {target.GetRealName().RemoveHtmlTags()}, their shield expires in {FFAShieldDuration.GetInt() - (Utils.TimeStamp - dur)}s", "FFA"); if (FFAShieldIsOneTimeUse.GetBool()) { FFAShieldedList.Remove(target.PlayerId); target.Notify(GetString("FFA_ShieldBroken")); Logger.Info($"{target.GetRealName().RemoveHtmlTags()}'s shield was removed because {killer.GetRealName().RemoveHtmlTags()} tried to kill them and the shield is one-time-use according to settings", "FFA"); } return; } OnPlayerKill(killer); if (aapc.Count == 3) { PlayerControl otherPC = null; foreach (PlayerControl pc in aapc) { if (pc.PlayerId != killer.PlayerId && pc.PlayerId != target.PlayerId && pc.IsAlive()) { TargetArrow.Add(killer.PlayerId, pc.PlayerId); TargetArrow.Add(pc.PlayerId, killer.PlayerId); otherPC = pc; break; } } Logger.Info($"The last 2 players ({killer.GetRealName().RemoveHtmlTags()} & {otherPC?.GetRealName().RemoveHtmlTags()}) now have an arrow toward each other", "FFA"); if (FFADisableVentingWhenTwoPlayersAlive.GetBool()) { if (killer.inVent) killer.MyPhysics?.RpcExitVent(killer.GetClosestVent().Id); if (otherPC && otherPC.inVent) otherPC.MyPhysics?.RpcExitVent(otherPC.GetClosestVent().Id); } } if (FFAEnableRandomAbilities.GetBool()) { var sync = false; var mark = false; float nowKCD = Main.AllPlayerKillCooldown[killer.PlayerId]; byte EffectType; if (Main.NormalOptions.MapId != 4) EffectType = (byte)IRandom.Instance.Next(0, 10); else EffectType = (byte)IRandom.Instance.Next(4, 10); switch (EffectType) { // Buff case <= 7: { var EffectID = (byte)IRandom.Instance.Next(0, 3); if (Main.NormalOptions.MapId == 4) EffectID = 2; switch (EffectID) { case 0: FFAShieldedList.TryAdd(killer.PlayerId, Utils.TimeStamp); killer.Notify(GetString("FFA-Event-GetShield"), FFAShieldDuration.GetFloat()); Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); break; case 1: FFAIncreasedSpeedList[killer.PlayerId] = Utils.TimeStamp; Main.AllPlayerSpeed[killer.PlayerId] = FFAIncreasedSpeed.GetFloat(); Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); killer.Notify(GetString("FFA-Event-GetIncreasedSpeed"), FFAModifiedSpeedDuration.GetFloat()); mark = true; break; case 2: Main.AllPlayerKillCooldown[killer.PlayerId] = Math.Clamp(FFAKcd.GetFloat() - 3f, 1f, 60f); killer.Notify(GetString("FFA-Event-GetLowKCD")); sync = true; break; default: Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); break; } break; } // De-Buff case 8: { var effectID = (byte)IRandom.Instance.Next(0, 3); if (Main.NormalOptions.MapId == 4) effectID = 1; switch (effectID) { case 0: FFADecreasedSpeedList[killer.PlayerId] = Utils.TimeStamp; Main.AllPlayerSpeed[killer.PlayerId] = FFADecreasedSpeed.GetFloat(); killer.Notify(GetString("FFA-Event-GetDecreasedSpeed"), FFAModifiedSpeedDuration.GetFloat()); Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); mark = true; break; case 1: Main.AllPlayerKillCooldown[killer.PlayerId] = Math.Clamp(FFAKcd.GetFloat() + 3f, 1f, 60f); killer.Notify(GetString("FFA-Event-GetHighKCD")); sync = true; break; case 2: FFALowerVisionList.TryAdd(killer.PlayerId, Utils.TimeStamp); Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); killer.Notify(GetString("FFA-Event-GetLowVision")); mark = true; break; default: Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); break; } break; } // Mixed default: LateTask.New(() => killer.TPToRandomVent(), 0.5f, "FFA-Event-TP"); killer.Notify(GetString("FFA-Event-GetTP")); Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); break; } if (killer.AmOwner) Main.AllPlayerKillCooldown[killer.PlayerId] += Utils.CalculatePingDelay(); if (sync || Math.Abs(nowKCD - Main.AllPlayerKillCooldown[killer.PlayerId]) > 0.1f) { mark = false; killer.SyncSettings(); } if (mark) killer.MarkDirtySettings(); } } catch (Exception e) { Utils.ThrowException(e); } killer.Kill(target); } private static void OnPlayerKill(PlayerControl killer) { if (Main.GM.Value && AmongUsClient.Instance.AmHost) PlayerControl.LocalPlayer.KillFlash(); ChatCommands.Spectators.ToValidPlayers().Do(x => x.KillFlash()); KillCount[killer.PlayerId]++; Utils.SendRPC(CustomRPC.FFASync, 2, killer.PlayerId, KillCount[killer.PlayerId]); } public static string GetPlayerArrow(PlayerControl seer, PlayerControl target = null) { if (GameStates.IsMeeting || target && seer.PlayerId != target.PlayerId) return string.Empty; var aapc = Main.CachedAlivePlayerControls(); if (aapc.Count != 2) return string.Empty; PlayerControl otherPlayer = aapc.FirstOrDefault(pc => pc.IsAlive() && pc.PlayerId != seer.PlayerId); if (!otherPlayer) return string.Empty; string arrow = TargetArrow.GetArrows(seer, otherPlayer.PlayerId); return CustomRoles.Killer.ColoredTextByRole(arrow); } //[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] public static class FixedUpdatePatch { private static long LastFixedUpdate; public static void Postfix() { if (!Main.IntroDestroyed || !GameStates.IsInTask || ExileController.Instance || Options.CurrentGameMode != CustomGameMode.FFA || !AmongUsClient.Instance.AmHost) return; long now = Utils.TimeStamp; if (IntroCutsceneDestroyPatch.IntroDestroyTS + 10 > now || LastFixedUpdate == now) return; LastFixedUpdate = now; RoundTime--; Utils.SendRPC(CustomRPC.FFASync, 1, RoundTime); var rd = IRandom.Instance; var ffaDoTPdecider = (byte)rd.Next(0, 100); bool ffaDoTP = ffaDoTPdecider == 0; if (FFAEnableRandomTwists.GetBool() && ffaDoTP) { Logger.Info("Swap everyone with someone", "FFA"); var aapc = Main.CachedAlivePlayerControls(); List changePositionPlayers = []; foreach (PlayerControl pc in aapc) { if (changePositionPlayers.Contains(pc.PlayerId) || !pc.IsAlive() || pc.onLadder || pc.inVent || pc.inMovingPlat) continue; PlayerControl[] filtered = aapc.Where(a => pc.IsAlive() && !pc.inVent && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); if (filtered.Length == 0) break; PlayerControl target = filtered.RandomElement(); if (pc.inVent || target.inVent) continue; changePositionPlayers.Add(target.PlayerId); changePositionPlayers.Add(pc.PlayerId); pc.RPCPlayCustomSound("Teleport"); Vector2 originPs = target.Pos(); target.TP(pc.Pos()); pc.TP(originPs); target.Notify(Utils.ColorString(new(0, 255, 165, byte.MaxValue), string.Format(GetString("FFA-Event-RandomTP"), pc.GetRealName()))); pc.Notify(Utils.ColorString(new(0, 255, 165, byte.MaxValue), string.Format(GetString("FFA-Event-RandomTP"), target.GetRealName()))); } changePositionPlayers.Clear(); } if (Main.NormalOptions.MapId == 4) return; foreach (PlayerControl pc in Main.CachedAlivePlayerControls()) { var sync = false; if (FFADecreasedSpeedList.TryGetValue(pc.PlayerId, out long dstime) && dstime + FFAModifiedSpeedDuration.GetInt() < now) { Logger.Info(pc.GetRealName() + "'s decreased speed expired", "FFA"); FFADecreasedSpeedList.Remove(pc.PlayerId); Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); sync = true; } if (FFAIncreasedSpeedList.TryGetValue(pc.PlayerId, out long istime) && istime + FFAModifiedSpeedDuration.GetInt() < now) { Logger.Info(pc.GetRealName() + "'s increased speed expired", "FFA"); FFAIncreasedSpeedList.Remove(pc.PlayerId); Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); sync = true; } if (FFALowerVisionList.TryGetValue(pc.PlayerId, out long lvtime) && lvtime + FFAModifiedVisionDuration.GetInt() < now) { Logger.Info(pc.GetRealName() + "'s lower vision effect expired", "FFA"); FFALowerVisionList.Remove(pc.PlayerId); sync = true; } if (FFAShieldedList.TryGetValue(pc.PlayerId, out long stime) && stime + FFAShieldDuration.GetInt() < now) { Logger.Info(pc.GetRealName() + "'s shield expired", "FFA"); FFAShieldedList.Remove(pc.PlayerId); } if (sync) pc.MarkDirtySettings(); } } } }