diff --git a/CTA/CustomTeamAssigner/CustomRoles.cs b/CTA/CustomTeamAssigner/CustomRoles.cs index 8c57ef695..12e92d2e5 100644 --- a/CTA/CustomTeamAssigner/CustomRoles.cs +++ b/CTA/CustomTeamAssigner/CustomRoles.cs @@ -478,6 +478,9 @@ public enum CustomRoles // Mingle MinglePlayer, + + // Snowdown + SnowdownPlayer, // GM GM, diff --git a/CustomRoles.cs b/CustomRoles.cs index 8c57ef695..12e92d2e5 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -478,6 +478,9 @@ public enum CustomRoles // Mingle MinglePlayer, + + // Snowdown + SnowdownPlayer, // GM GM, diff --git a/GameMode/Snowdown.cs b/GameMode/Snowdown.cs new file mode 100644 index 000000000..725201b71 --- /dev/null +++ b/GameMode/Snowdown.cs @@ -0,0 +1,481 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AmongUs.GameOptions; +using UnityEngine; + +namespace EHR; + +public static class Snowdown +{ + // Kill: Give the target a snowball + // Vanish: Throw snowball in movement direction / Cycle to next powerup while in shop + // Sabotage: Enter/Exit shop + // Pet: Buy the selected powerup in the shop + + public static float SnowballThrowSpeed = 4f; + private static int SnowballGainFrequency = 5; + private static int MaxSnowballsInHand = 5; + private static int MaxCoinsInHand = 10; + private static bool GameEndsAfterTime = true; + private static int GameEndTime = 300; + private static bool GameEndsWhenPointsReached = true; + private static int PointsToReach = 5; + private static Dictionary PowerUpPrices = new() + { + [PowerUp.FillUp] = 3, + [PowerUp.IncreaseCapacity] = 1, + [PowerUp.FasterPickup] = 2 + }; + + private static OptionItem SnowballThrowSpeedOption; + private static OptionItem SnowballGainFrequencyOption; + private static OptionItem MaxSnowballsInHandOption; + private static OptionItem MaxCoinsInHandOption; + private static OptionItem GameEndsAfterTimeOption; + private static OptionItem GameEndTimeOption; + private static OptionItem GameEndsWhenPointsReachedOption; + private static OptionItem PointsToReachOption; + private static Dictionary PowerUpPriceOptions = []; + + public static Dictionary Data = []; + private static List Snowballs = []; + public static ((float Left, float Right) X, (float Bottom, float Top) Y) MapBounds; + private static long GameStartTS; + + private static readonly char[] Nums1 = "⓿❶❷❸❹❺❻❼❽❾❿".ToCharArray(); + private static readonly char[] Nums2 = "⓪①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳".ToCharArray(); + private static readonly char[] Nums3 = "⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇".ToCharArray(); + + public static void SetupCustomOption() + { + var id = 69_225_001; + Color color = Utils.GetRoleColor(CustomRoles.SnowdownPlayer); + const CustomGameMode gameMode = CustomGameMode.Snowdown; + const TabGroup tab = TabGroup.GameSettings; + + SnowballThrowSpeedOption = new FloatOptionItem(id++, "Snowdown.SnowballThrowSpeedOption", new(0.25f, 10f, 0.25f), 4f, tab) + .SetHeader(true) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Multiplier); + + SnowballGainFrequencyOption = new IntegerOptionItem(id++, "Snowdown.SnowballGainFrequencyOption", new(1, 30, 1), 5, tab) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Seconds); + + MaxSnowballsInHandOption = new IntegerOptionItem(id++, "Snowdown.MaxSnowballsInHandOption", new(1, 10, 1), 5, tab) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Pieces); + + MaxCoinsInHandOption = new IntegerOptionItem(id++, "Snowdown.MaxCoinsInHandOption", new(1, 20, 1), 10, tab) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Pieces); + + GameEndsAfterTimeOption = new BooleanOptionItem(id++, "Snowdown.GameEndsAfterTimeOption", true, tab) + .SetColor(color) + .SetGameMode(gameMode); + + GameEndTimeOption = new IntegerOptionItem(id++, "Snowdown.GameEndTimeOption", new(15, 1800, 15), 300, tab) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Seconds); + + GameEndsWhenPointsReachedOption = new BooleanOptionItem(id++, "Snowdown.GameEndsWhenPointsReachedOption", true, tab) + .SetColor(color) + .SetGameMode(gameMode); + + PointsToReachOption = new IntegerOptionItem(id++, "Snowdown.PointsToReachOption", new(1, 100, 1), 5, tab) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Pieces); + + PowerUpPriceOptions = Enum.GetValues().ToDictionary(x => x, x => new IntegerOptionItem(id++, "Snowdown.PowerUpPriceOption", new(1, 20, 1), PowerUpPrices[x], tab) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Pieces) + .AddReplacement(("{powerup}", Translator.GetString($"Snowdown.PowerUp.{x}")))); + } + + public static void ApplyGameOptions(IGameOptions opt, PlayerControl player) + { + AURoleOptions.PhantomCooldown = 0.1f; + AURoleOptions.PhantomDuration = 0.1f; + } + + public static string GetSuffix(PlayerControl seer, PlayerControl target) + { + if (!Data.TryGetValue(seer.PlayerId, out PlayerData seerData) || !Data.TryGetValue(target.PlayerId, out PlayerData targetData)) return string.Empty; + + if (seer.PlayerId == target.PlayerId) + return seerData.GetSelfSuffix(); + + StringBuilder sb = new("<#ffffff>"); + AddStats(sb, targetData); + sb.Append(""); + return sb.ToString(); + } + + private static void AddStats(StringBuilder sb, PlayerData targetData) + { + sb.Append("<#e4fdff>"); + sb.Append(Nums1[targetData.SnowballsReady]); + sb.Append(""); + + sb.Append(' '); + + sb.Append("<#dfc57b>"); + sb.Append(Nums2[targetData.Coins]); + sb.Append(""); + + if (targetData.Points > 0) + { + sb.Append(' '); + + sb.Append("<#e5acff>"); + sb.Append(Nums3[targetData.Points - 1]); + sb.Append(""); + } + } + + public static string GetStatistics(byte id) + { + if (!Data.TryGetValue(id, out PlayerData data)) return string.Empty; + StringBuilder sb = new("<#ffffff>"); + AddStats(sb, data); + sb.Append(""); + return sb.ToString(); + } + + public static string GetHudText() + { + if (!GameEndsAfterTime) return string.Empty; + long timeLeft = GameEndTime - (Utils.TimeStamp - GameStartTS); + return $"{timeLeft / 60:00}:{timeLeft % 60:00}"; + } + + public static bool CheckGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorsByKill; + if (GameStates.IsEnded || !Main.IntroDestroyed) return false; + PlayerControl[] aapc = Main.AllAlivePlayerControls; + + switch (aapc.Length) + { + case 1: + { + PlayerControl winner = aapc[0]; + Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "Snowdown"); + CustomWinnerHolder.WinnerIds = [winner.PlayerId]; + Main.DoBlockNameChange = true; + return true; + } + case 0: + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.None); + Logger.Warn("No players alive. Force ending the game", "Snowdown"); + return true; + } + default: + { + if (GameEndsAfterTime && Utils.TimeStamp - GameStartTS >= GameEndTime) + { + int max = Data.Values.Max(x => x.Points); + CustomWinnerHolder.WinnerIds = Data.Where(x => x.Value.Points == max && x.Key.GetPlayer() != null).Select(x => x.Key).ToHashSet(); + Logger.Info($"Winners: {(string.Join(", ", CustomWinnerHolder.WinnerIds.Select(x => Main.AllPlayerNames.GetValueOrDefault(x, "[Unknown player]"))))}", "Snowdown"); + Main.DoBlockNameChange = true; + return true; + } + + if (GameEndsWhenPointsReached && Data.IntersectBy(Main.AllAlivePlayerControls.Select(p => p.PlayerId), x => x.Key).FindFirst(x => x.Value.Points >= PointsToReach, out var winnerData)) + { + CustomWinnerHolder.WinnerIds = [winnerData.Key]; + Logger.Info($"Winners: {Main.AllPlayerNames.GetValueOrDefault(winnerData.Key, "[Unknown player]")}", "Snowdown"); + Main.DoBlockNameChange = true; + return true; + } + + return false; + } + } + } + + public static void Init() + { + SnowballThrowSpeed = SnowballThrowSpeedOption.GetFloat(); + SnowballGainFrequency = SnowballGainFrequencyOption.GetInt(); + MaxSnowballsInHand = MaxSnowballsInHandOption.GetInt(); + MaxCoinsInHand = MaxCoinsInHandOption.GetInt(); + GameEndsAfterTime = GameEndsAfterTimeOption.GetBool(); + GameEndTime = GameEndTimeOption.GetInt(); + GameEndsWhenPointsReached = GameEndsWhenPointsReachedOption.GetBool(); + PointsToReach = PointsToReachOption.GetInt(); + PowerUpPrices = PowerUpPriceOptions.ToDictionary(x => x.Key, x => x.Value.GetInt()); + + Data = Main.AllPlayerControls.ToDictionary(x => x.PlayerId, _ => new PlayerData()); + Snowballs = []; + + Dictionary.ValueCollection rooms = RandomSpawn.SpawnMap.GetSpawnMap().Positions?.Values; + if (rooms == null) return; + + float[] x = rooms.Select(r => r.x).ToArray(); + float[] y = rooms.Select(r => r.y).ToArray(); + + const float extend = 5f; + MapBounds = ((x.Min() - extend, x.Max() + extend), (y.Min() - extend, y.Max() + extend)); + } + + public static void GameStart() // Called as non-host client too! + { + GameStartTS = Utils.TimeStamp; + } + + public static void OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!Data.TryGetValue(killer.PlayerId, out PlayerData killerData) || !Data.TryGetValue(target.PlayerId, out PlayerData targetData) || killerData.SnowballsReady < 1 || targetData.SnowballsReady >= targetData.MaxSnowballsReady) return; + killerData.SnowballsReady--; + targetData.SnowballsReady++; + } + + //[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + public static class FixedUpdatePatch + { + public static void Postfix(PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks || Options.CurrentGameMode != CustomGameMode.Snowdown || !Main.IntroDestroyed) return; + if (!__instance.IsAlive() || !Data.TryGetValue(__instance.PlayerId, out PlayerData data)) return; + + var now = Utils.TimeStamp; + var pos = __instance.Pos(); + var touchingSnowball = Snowballs.Find(x => x.Thrower != __instance && Vector2.Distance(x.Position, pos) < 1.5f); + + if (touchingSnowball != null) + { + touchingSnowball.Despawn(); + + if (touchingSnowball.Thrower != null && Data.TryGetValue(touchingSnowball.Thrower.PlayerId, out PlayerData throwerData) && throwerData.Coins < throwerData.MaxCoins) + throwerData.Coins++; + } + + if (data.SnowballsReady < data.MaxSnowballsReady && now - data.LastSnowballGainTS >= data.SnowballGainInterval) + { + data.SnowballsReady++; + data.LastSnowballGainTS = now; + } + + if (Vector2.Distance(pos, data.LastPosition) > 0.01f) + { + data.LastLastPosition = data.LastPosition; + data.LastPosition = pos; + } + + foreach (PlayerControl target in Main.AllAlivePlayerControls) + { + string suffix = GetSuffix(__instance, target); + + if (!data.LastSuffix.TryGetValue(target.PlayerId, out string lastSuffix)) + goto Skip; + + if (lastSuffix != suffix) + Utils.NotifyRoles(SpecifySeer: __instance, SpecifyTarget: target); + + Skip: + + data.LastSuffix[target.PlayerId] = suffix; + } + } + } + + public class PlayerData + { + public Vector2 LastPosition = new(0f, 0f); + public Vector2 LastLastPosition = new(0f, 0f); + public int SnowballsReady; + public int MaxSnowballsReady = MaxSnowballsInHand; + public long LastSnowballGainTS; + public int SnowballGainInterval = SnowballGainFrequency; + public int Coins; + public int MaxCoins = MaxCoinsInHand; + public bool InShop; + private int ShopSelectedIndex; + public int Points; + private bool ShowHelp; + public readonly Dictionary LastSuffix = []; + + public void OnSabotage(PlayerControl pc) + { + InShop = (SnowballsReady != MaxSnowballsReady || MaxSnowballsReady < 20 || MaxCoins < 10 || SnowballGainInterval > 1) && !InShop; + ShowHelp = !Main.HasPlayedGM[CustomGameMode.Snowdown].Contains(pc.FriendCode); + if (pc.AmOwner) HudSpritePatch.ForceUpdate = true; + } + + public void OnPet(PlayerControl pc) + { + if (!InShop) + { + if (Coins >= 20) + { + Points++; + Coins -= 20; + } + + return; + } + + PowerUp powerUp = Enum.GetValues()[ShopSelectedIndex]; + int cost = PowerUpPrices[powerUp]; + if (Coins < cost) return; + + switch (powerUp) + { + case PowerUp.FillUp: + { + if (SnowballsReady == MaxSnowballsReady) return; + SnowballsReady = MaxSnowballsReady; + break; + } + case PowerUp.IncreaseCapacity: + { + switch (MaxSnowballsReady, MaxCoins) + { + case (>= 10, >= 20): + return; + case (>= 10, < 20): + MaxCoins++; + break; + case (< 10, >= 20): + MaxSnowballsReady++; + break; + case (< 10, < 20): + MaxSnowballsReady++; + MaxCoins++; + break; + } + + break; + } + case PowerUp.FasterPickup: + { + if (SnowballGainInterval <= 1) return; + SnowballGainInterval--; + break; + } + } + + Coins -= cost; + + if (SnowballsReady == MaxSnowballsReady && MaxSnowballsReady >= 20 && MaxCoins >= 10 && SnowballGainInterval <= 1) + InShop = false; + } + + public void OnVanish(PlayerControl pc) + { + if (InShop) + { + ShopSelectedIndex++; + + if (ShopSelectedIndex >= Enum.GetValues().Length) + ShopSelectedIndex = 0; + } + else + { + if (SnowballsReady <= 0) return; + SnowballsReady--; + Snowballs.Add(new Snowball(pc.Pos(), (LastPosition - LastLastPosition).normalized, pc)); + } + } + + public string GetSelfSuffix() + { + StringBuilder sb = new("<#ffffff>"); + + AddStats(sb, this); + + sb.Append(""); + + if (InShop) + { + PowerUp powerUp = Enum.GetValues()[ShopSelectedIndex]; + sb.Append('\n'); + sb.Append('\n'); + sb.Append(Translator.GetString($"Snowdown.PowerUp.{powerUp}")); + sb.Append(' '); + sb.Append("<#dfc57b>"); + sb.Append(Nums2[PowerUpPrices[powerUp]]); + sb.Append(""); + sb.Append('\n'); + sb.Append(""); + sb.Append(Translator.GetString($"Snowdown.PowerUp.{powerUp}.Description")); + sb.Append(""); + + if (ShowHelp) + { + sb.Append('\n'); + sb.Append('\n'); + sb.Append(""); + sb.Append(Translator.GetString("Snowdown.ShopHelp")); + sb.Append(""); + } + } + + sb.Append(""); + return sb.ToString(); + } + } + + public enum PowerUp + { + FillUp, + IncreaseCapacity, + FasterPickup + } +} + +internal class SnowdownPlayer : RoleBase +{ + public static bool On; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override bool OnSabotage(PlayerControl pc) + { + if (Snowdown.Data.TryGetValue(pc.PlayerId, out Snowdown.PlayerData data)) + data.OnSabotage(pc); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + if (Snowdown.Data.TryGetValue(pc.PlayerId, out Snowdown.PlayerData data)) + data.OnVanish(pc); + + return false; + } + + public override void OnPet(PlayerControl pc) + { + if (Snowdown.Data.TryGetValue(pc.PlayerId, out Snowdown.PlayerData data)) + data.OnPet(pc); + } +} \ No newline at end of file diff --git a/Main.cs b/Main.cs index 718b5019d..bfc3a7a64 100644 --- a/Main.cs +++ b/Main.cs @@ -137,7 +137,8 @@ public class Main : BasePlugin [CustomGameMode.HideAndSeek] = [], [CustomGameMode.Speedrun] = [], [CustomGameMode.CaptureTheFlag] = [], - [CustomGameMode.NaturalDisasters] = [] + [CustomGameMode.NaturalDisasters] = [], + [CustomGameMode.Snowdown] = [] }; public static Dictionary GameModeColors = []; @@ -258,7 +259,7 @@ public class Main : BasePlugin foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { - if (pc == null || pc.PlayerId >= 254 || !pc.IsAlive() || pc.Data == null || (pc.Data.Disconnected && IntroDestroyed) || Pelican.IsEaten(pc.PlayerId)) continue; + if (pc == null || pc.PlayerId >= 254 || !pc.IsAlive() || pc.Data == null || (pc.Data.Disconnected && IntroDestroyed) || Pelican.IsEaten(pc.PlayerId) || pc.Is(CustomRoles.GM)) continue; result[i++] = pc; } @@ -776,6 +777,8 @@ public class Main : BasePlugin { CustomRoles.Racer, "#AFAFAF" }, // Mingle { CustomRoles.MinglePlayer, "#FE9900" }, + // Snowdown + { CustomRoles.SnowdownPlayer, "#e4fdff" }, // Hide And Seek { CustomRoles.Seeker, "#ff1919" }, { CustomRoles.Hider, "#345eeb" }, @@ -858,7 +861,8 @@ public class Main : BasePlugin [CustomGameMode.TheMindGame] = Color.yellow, [CustomGameMode.BedWars] = Utils.GetRoleColor(CustomRoles.BedWarsPlayer), [CustomGameMode.Deathrace] = Utils.GetRoleColor(CustomRoles.Racer), - [CustomGameMode.Mingle] = Utils.GetRoleColor(CustomRoles.MinglePlayer) + [CustomGameMode.Mingle] = Utils.GetRoleColor(CustomRoles.MinglePlayer), + [CustomGameMode.Snowdown] = Utils.GetRoleColor(CustomRoles.SnowdownPlayer) }; IL2CPPChainloader.Instance.Finished += () => diff --git a/Modules/AutoHaunt.cs b/Modules/AutoHaunt.cs index 5c5eb2e41..69960983c 100644 --- a/Modules/AutoHaunt.cs +++ b/Modules/AutoHaunt.cs @@ -26,6 +26,7 @@ public static class AutoHaunt CustomGameMode.RoomRush => RoomRush.PointsSystem ? validPCs.MaxBy(x => RoomRush.GetPoints(x.PlayerId)) : validPCs.RandomElement(), CustomGameMode.KingOfTheZones => validPCs.MaxBy(x => KingOfTheZones.GetZoneTime(x.PlayerId)), CustomGameMode.Deathrace => validPCs.MaxBy(x => Deathrace.Data.TryGetValue(x.PlayerId, out var drData) ? drData.Lap : 0), + CustomGameMode.Snowdown => validPCs.Where(x => Snowdown.Data.ContainsKey(x.PlayerId)).Select(x => (pc: x, data: Snowdown.Data[x.PlayerId])).OrderByDescending(x => x.data.Points).ThenBy(x => x.data.SnowballGainInterval).ThenByDescending(x => x.data.Coins).ThenByDescending(x => x.data.SnowballsReady).Select(x => x.pc).FirstOrDefault(), _ => validPCs.RandomElement() }; } diff --git a/Modules/CustomNetObject.cs b/Modules/CustomNetObject.cs index 97680981f..46151faae 100644 --- a/Modules/CustomNetObject.cs +++ b/Modules/CustomNetObject.cs @@ -239,7 +239,9 @@ namespace EHR catch (Exception exception) { Utils.ThrowException(exception); } } - playerControl.RawSetName(sprite); + try { playerControl.RawSetName("
" + sprite); } + catch (Exception e) { Utils.ThrowException(e); } + string name = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].PlayerName; int colorId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].ColorId; string hatId = PlayerControl.LocalPlayer.Data.Outfits[PlayerOutfitType.Default].HatId; @@ -285,6 +287,8 @@ namespace EHR }, 0.6f); } + playerControl.cosmetics.currentBodySprite.BodySprite.color = Color.clear; + playerControl.cosmetics.colorBlindText.color = Color.clear; Position = position; Sprite = sprite; ++MaxId; @@ -868,7 +872,7 @@ namespace EHR public sealed class DeathracePowerUp : CustomNetObject { - public Deathrace.PowerUp PowerUp; + public readonly Deathrace.PowerUp PowerUp; public DeathracePowerUp(Vector2 position, Deathrace.PowerUp powerUp) { @@ -897,6 +901,33 @@ namespace EHR CreateNetObject(Utils.ColorString(color, $"{icon}\n{Translator.GetString($"Deathrace.PowerUpDisplay.{powerUp}").ToUpper()}"), position); } } + + internal sealed class Snowball : CustomNetObject + { + public PlayerControl Thrower; + public Vector2 Direction; + + public Snowball(Vector2 from, Vector2 direction, PlayerControl thrower) + { + Thrower = thrower; + Direction = direction; + CreateNetObject("<#0000>WWWWW<#0000>W\nWWWWWW\nWWWWWW\nWWWWWW\nWWWWWW\n<#0000>WWWWW<#0000>W", from); + } + + protected override void OnFixedUpdate() + { + Vector2 newPos = Position + Direction * Time.fixedDeltaTime * Snowdown.SnowballThrowSpeed; + + if ((PhysicsHelpers.AnythingBetween(Position, newPos, Constants.ShipOnlyMask, false)) || + newPos.x < Snowdown.MapBounds.X.Left || newPos.x > Snowdown.MapBounds.X.Right || newPos.y < Snowdown.MapBounds.Y.Bottom || newPos.y > Snowdown.MapBounds.Y.Top) + { + Despawn(); + return; + } + + TP(newPos); + } + } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RawSetName))] diff --git a/Modules/CustomRoleSelector.cs b/Modules/CustomRoleSelector.cs index 012d35a72..1aa3d2fcf 100644 --- a/Modules/CustomRoleSelector.cs +++ b/Modules/CustomRoleSelector.cs @@ -36,7 +36,8 @@ internal static class CustomRoleSelector { CustomGameMode.TheMindGame, CustomRoles.TMGPlayer }, { CustomGameMode.BedWars, CustomRoles.BedWarsPlayer }, { CustomGameMode.Deathrace, CustomRoles.Racer }, - { CustomGameMode.Mingle, CustomRoles.MinglePlayer } + { CustomGameMode.Mingle, CustomRoles.MinglePlayer }, + { CustomGameMode.Snowdown, CustomRoles.SnowdownPlayer } }; public static void SelectCustomRoles() diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 10061e19a..9fb217f1d 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -73,7 +73,8 @@ internal static class CustomRolesHelper CustomRoles.TMGPlayer or CustomRoles.BedWarsPlayer or CustomRoles.Racer or - CustomRoles.MinglePlayer; + CustomRoles.MinglePlayer or + CustomRoles.SnowdownPlayer; } public static RoleBase GetRoleClass(this CustomRoles role) @@ -470,6 +471,8 @@ internal static class CustomRolesHelper CustomRoles.BedWarsPlayer => RoleTypes.Phantom, // Deathrace CustomRoles.Racer => RoleTypes.Phantom, + // Snowdown + CustomRoles.SnowdownPlayer => RoleTypes.Phantom, // Standard CustomRoles.Sheriff => UsePets && Sheriff.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, CustomRoles.Crusader => UsePets && Crusader.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, @@ -1052,6 +1055,7 @@ internal static class CustomRolesHelper or CustomRoles.CTFPlayer or CustomRoles.BedWarsPlayer or CustomRoles.Racer + or CustomRoles.SnowdownPlayer or CustomRoles.Wizard or CustomRoles.AntiAdminer or CustomRoles.Stasis diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index dbe7fa31f..3ae83b7ed 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -355,7 +355,7 @@ internal static class ExtendedPlayerControl RoleTypes newRoleType = state.MainRole.GetRoleTypes(); - if (Options.CurrentGameMode is CustomGameMode.SoloPVP or CustomGameMode.FFA or CustomGameMode.CaptureTheFlag or CustomGameMode.KingOfTheZones or CustomGameMode.BedWars) + if (Options.CurrentGameMode is CustomGameMode.SoloPVP or CustomGameMode.FFA or CustomGameMode.CaptureTheFlag or CustomGameMode.KingOfTheZones or CustomGameMode.BedWars or CustomGameMode.Snowdown) hasValue |= sender.RpcSetRole(player, newRoleType, player.OwnerId); player.ResetKillCooldown(); @@ -1081,7 +1081,7 @@ internal static class ExtendedPlayerControl { try { - bool addRoleName = GameStates.IsInGame && Options.CurrentGameMode is not CustomGameMode.FFA and not CustomGameMode.StopAndGo and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.Quiz and not CustomGameMode.TheMindGame and not CustomGameMode.BedWars and not CustomGameMode.Deathrace and not CustomGameMode.Mingle; + bool addRoleName = GameStates.IsInGame && Options.CurrentGameMode is not CustomGameMode.FFA and not CustomGameMode.StopAndGo and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.Quiz and not CustomGameMode.TheMindGame and not CustomGameMode.BedWars and not CustomGameMode.Deathrace and not CustomGameMode.Mingle and not CustomGameMode.Snowdown; return $"{player?.Data?.PlayerName}" + (addRoleName ? $" ({player?.GetAllRoleName(forUser).RemoveHtmlTags().Replace('\n', ' ')})" : string.Empty); } catch (Exception e) @@ -1365,6 +1365,7 @@ internal static class ExtendedPlayerControl case CustomGameMode.CaptureTheFlag: case CustomGameMode.BedWars: case CustomGameMode.Deathrace: + case CustomGameMode.Snowdown: return true; } @@ -1425,6 +1426,7 @@ internal static class ExtendedPlayerControl CustomGameMode.Quiz => false, CustomGameMode.TheMindGame => false, CustomGameMode.Mingle => false, + CustomGameMode.Snowdown => true, CustomGameMode.Deathrace => Deathrace.CanUseVent(pc, pc.GetClosestVent().Id), CustomGameMode.Standard when CopyCat.Instances.Any(x => x.CopyCatPC.PlayerId == pc.PlayerId) => true, @@ -1735,6 +1737,7 @@ internal static class ExtendedPlayerControl CustomRoles.QuizPlayer => 3f, CustomRoles.BedWarsPlayer => 1f, CustomRoles.Racer => 3f, + CustomRoles.SnowdownPlayer => 1f, _ when player.Is(CustomRoles.Underdog) => Main.AllAlivePlayerControls.Length <= Underdog.UnderdogMaximumPlayersNeededToKill.GetInt() ? Underdog.UnderdogKillCooldownWithLessPlayersAlive.GetInt() : Underdog.UnderdogKillCooldownWithMorePlayersAlive.GetInt(), _ => Main.AllPlayerKillCooldown[player.PlayerId] }; diff --git a/Modules/FixedUpdateCaller.cs b/Modules/FixedUpdateCaller.cs index 963672ede..d6b8a8aa1 100644 --- a/Modules/FixedUpdateCaller.cs +++ b/Modules/FixedUpdateCaller.cs @@ -175,6 +175,9 @@ public static class FixedUpdateCaller case CustomGameMode.BedWars: BedWars.FixedUpdatePatch.Postfix(pc); break; + case CustomGameMode.Snowdown: + Snowdown.FixedUpdatePatch.Postfix(pc); + break; } CheckInvalidMovementPatch.Postfix(pc); diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 1ecdd549e..f4c290ad9 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -171,6 +171,11 @@ public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsS CaptureTheFlag.ApplyGameOptions(); goto case CustomGameMode.RoomRush; } + case CustomGameMode.Snowdown: + { + Snowdown.ApplyGameOptions(opt, player); + goto case CustomGameMode.RoomRush; + } case CustomGameMode.NaturalDisasters: { SetMaxVision(); diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 3ecfe36ad..e8db39006 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -46,6 +46,7 @@ public static class NameColorManager if (FreeForAll.PlayerTeams.TryGetValue(target.PlayerId, out int team)) color = FreeForAll.TeamColors.GetValueOrDefault(team, "#00ffff"); return true; + case CustomGameMode.Snowdown: case CustomGameMode.Mingle: case CustomGameMode.RoomRush: case CustomGameMode.NaturalDisasters: diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index b3c951913..f5a282153 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -35,6 +35,7 @@ public enum CustomGameMode BedWars = 0x0E, Deathrace = 0x0F, Mingle = 0x10, + Snowdown = 0x11, All = int.MaxValue } @@ -88,7 +89,8 @@ public static class Options "TheMindGame", "BedWars", "Deathrace", - "Mingle" + "Mingle", + "Snowdown" ]; private static Dictionary roleCounts; @@ -919,6 +921,7 @@ public static class Options 13 => CustomGameMode.BedWars, 14 => CustomGameMode.Deathrace, 15 => CustomGameMode.Mingle, + 16 => CustomGameMode.Snowdown, _ => CustomGameMode.Standard }; @@ -1841,6 +1844,8 @@ public static class Options Deathrace.SetupCustomOption(); // Mingle Mingle.SetupCustomOption(); + // Snowdown + Snowdown.SetupCustomOption(); yield return null; @@ -3205,10 +3210,11 @@ public static class Options foreach (CustomGameMode customGameMode in Enum.GetValues()[..^1]) { - OptionItem chanceToSelectGMInGroup = new IntegerOptionItem(id++, $"AGMR.RandomGroup.GMChance.{customGameMode}", new(0, 100, 5), 50, TabGroup.SystemSettings) + OptionItem chanceToSelectGMInGroup = new IntegerOptionItem(id++, $"AGMR.RandomGroup.GMChance", new(0, 100, 5), 50, TabGroup.SystemSettings) .SetParent(EnableAutoGMRotation) .SetValueFormat(OptionFormat.Percent) - .SetColor(Main.GameModeColors[customGameMode]); + .SetColor(Main.GameModeColors[customGameMode]) + .AddReplacement(("{gm}", Translator.GetString($"{customGameMode}"))); dict[customGameMode] = chanceToSelectGMInGroup; } @@ -3291,6 +3297,7 @@ public static class Options 13 => CustomGameMode.BedWars, 14 => CustomGameMode.Deathrace, 15 => CustomGameMode.Mingle, + 16 => CustomGameMode.Snowdown, _ => CustomGameMode.Standard }; AutoGMRotationCompiled.AddRange(Enumerable.Repeat(gm, times)); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 15e6199c7..2860b366e 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -822,6 +822,7 @@ public static class Utils case CustomGameMode.BedWars: case CustomGameMode.Deathrace: case CustomGameMode.Mingle: + case CustomGameMode.Snowdown: return false; case CustomGameMode.HideAndSeek: return CustomHnS.HasTasks(p); @@ -1046,7 +1047,7 @@ public static class Utils { switch (Options.CurrentGameMode) { - case CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle: + case CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle or CustomGameMode.Snowdown: case CustomGameMode.Standard when IsRevivingRoleAlive() && Main.DiedThisRound.Contains(PlayerControl.LocalPlayer.PlayerId): return PlayerControl.LocalPlayer.Is(CustomRoles.GM); case CustomGameMode.FFA or CustomGameMode.SoloPVP or CustomGameMode.StopAndGo or CustomGameMode.HotPotato or CustomGameMode.Speedrun: @@ -1551,6 +1552,7 @@ public static class Utils } break; + case CustomGameMode.Snowdown: case CustomGameMode.Mingle: case CustomGameMode.Deathrace: case CustomGameMode.BedWars: @@ -2176,6 +2178,7 @@ public static class Utils CustomGameMode.BedWars => ColorString(GetRoleColor(CustomRoles.BedWarsPlayer), $"{modeText}\r\n") + name, CustomGameMode.Deathrace => ColorString(GetRoleColor(CustomRoles.Racer), $"{modeText}\r\n") + name, CustomGameMode.Mingle => ColorString(GetRoleColor(CustomRoles.MinglePlayer), $"{modeText}\r\n") + name, + CustomGameMode.Snowdown => ColorString(GetRoleColor(CustomRoles.SnowdownPlayer), $"{modeText}\r\n") + name, _ => name }; } @@ -2641,13 +2644,16 @@ public static class Utils case CustomGameMode.Mingle: additionalSuffixes.Add(Mingle.GetSuffix(seer)); break; + case CustomGameMode.Snowdown: + additionalSuffixes.Add(Snowdown.GetSuffix(seer, seer)); + break; } List addSuff = additionalSuffixes.ConvertAll(x => x.Trim()).FindAll(x => !string.IsNullOrEmpty(x)); if (addSuff.Count > 0) { - if (SelfSuffix.Length > 0 && SelfSuffix[^1] != '\n') + if (SelfSuffix.ToString().RemoveHtmlTags().Length > 0 && SelfSuffix[^1] != '\n') SelfSuffix.Append('\n'); SelfSuffix.Append(string.Join('\n', addSuff)); @@ -2664,7 +2670,7 @@ public static class Utils if ((Options.CurrentGameMode == CustomGameMode.FFA && FreeForAll.FFATeamMode.GetBool()) || Options.CurrentGameMode == CustomGameMode.HotPotato) seerRealName = seerRealName.ApplyNameColorData(seer, seer, forMeeting); - if (!forMeeting && MeetingStates.FirstMeeting && Options.ChangeNameToRoleInfo.GetBool() && Options.CurrentGameMode is not CustomGameMode.FFA and not CustomGameMode.StopAndGo and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.KingOfTheZones and not CustomGameMode.Quiz and not CustomGameMode.TheMindGame and not CustomGameMode.BedWars and not CustomGameMode.Deathrace and not CustomGameMode.Mingle) + if (!forMeeting && MeetingStates.FirstMeeting && Options.ChangeNameToRoleInfo.GetBool() && Options.CurrentGameMode is not CustomGameMode.FFA and not CustomGameMode.StopAndGo and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.KingOfTheZones and not CustomGameMode.Quiz and not CustomGameMode.TheMindGame and not CustomGameMode.BedWars and not CustomGameMode.Deathrace and not CustomGameMode.Mingle and not CustomGameMode.Snowdown) { CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(seer.PlayerId); @@ -2702,7 +2708,7 @@ public static class Utils SelfSuffix.Append($"\n\n<#ffffff>{GetString($"GameModeTutorial.{Options.CurrentGameMode}")}\n"); } - bool noRoleText = GameStates.IsLobby || Options.CurrentGameMode is CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle; + bool noRoleText = GameStates.IsLobby || Options.CurrentGameMode is CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle or CustomGameMode.Snowdown; // Combine the seer's job title and SelfTaskText with the seer's player name and SelfMark string selfRoleName = noRoleText ? string.Empty : $"{seer.GetDisplayRoleName()}{selfTaskText}"; @@ -2756,7 +2762,7 @@ public static class Utils break; } - selfName += SelfSuffix.ToString() == string.Empty ? string.Empty : $"\r\n{SelfSuffix}"; + selfName += SelfSuffix.ToString() == string.Empty ? string.Empty : $"\r\n{SelfSuffix.ToString().Trim()}"; if (!forMeeting) selfName += "\r\n"; } @@ -2879,7 +2885,7 @@ public static class Utils if (IsRevivingRoleAlive() && Main.DiedThisRound.Contains(seer.PlayerId)) targetRoleText = string.Empty; - if (Options.CurrentGameMode is CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle) + if (Options.CurrentGameMode is CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle or CustomGameMode.Snowdown) targetRoleText = string.Empty; if (!GameStates.IsLobby) @@ -2991,6 +2997,9 @@ public static class Utils case CustomGameMode.Deathrace: additionalSuffixes.Add(Deathrace.GetSuffix(seer, target, false)); break; + case CustomGameMode.Snowdown: + additionalSuffixes.Add(Snowdown.GetSuffix(seer, target)); + break; } if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode) && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.FFA or CustomGameMode.Speedrun) @@ -3008,7 +3017,7 @@ public static class Utils if (addSuff.Count > 0) { - if (TargetSuffix.Length > 0 && TargetSuffix[^1] != '\n') + if (TargetSuffix.ToString().RemoveHtmlTags().Length > 0 && TargetSuffix[^1] != '\n') TargetSuffix.Append('\n'); TargetSuffix.Append(string.Join('\n', addSuff)); @@ -3027,7 +3036,7 @@ public static class Utils targetPlayerName = EmptyMessage; var targetName = $"{targetRoleText}{targetPlayerName}{targetDeathReason}{TargetMark}"; - targetName += GameStates.IsLobby || TargetSuffix.ToString() == string.Empty ? string.Empty : $"{newLineBeforeSuffix}{TargetSuffix}"; + targetName += GameStates.IsLobby || TargetSuffix.ToString() == string.Empty ? string.Empty : $"{newLineBeforeSuffix}{TargetSuffix.ToString().Trim()}"; // Camouflage if (Camouflage.IsCamouflage && !camouflageIsForMeeting) targetName = $"{targetName}"; @@ -3989,6 +3998,9 @@ public static class Utils int time3 = Mingle.GetSurvivalTime(id); summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time3 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time3}s")} ({GetVitalText(id, true)})"; break; + case CustomGameMode.Snowdown: + summary = $"{ColorString(Main.PlayerColors[id], name)} - {Snowdown.GetStatistics(id)}"; + break; } return check && GetDisplayRoleName(id, true).RemoveHtmlTags().Contains("INVALID:NotAssigned") diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 5f26ae86c..5b8f67e26 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -560,6 +560,11 @@ internal static class GameEndChecker Predicate = new MingleGameEndPredicate(); } + public static void SetPredicateToSnowdown() + { + Predicate = new SnowdownGameEndPredicate(); + } + private class NormalGameEndPredicate : GameEndPredicate { public override bool CheckForGameEnd(out GameOverReason reason) @@ -1133,6 +1138,20 @@ internal static class GameEndChecker return Mingle.CheckGameEnd(out reason); } } + + private class SnowdownGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorsByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + return Snowdown.CheckGameEnd(out reason); + } + } public abstract class GameEndPredicate { diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 3a52946f2..e7b33800c 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -1167,6 +1167,7 @@ public static class GameSettingMenuPatch Vector3 gameSettingsLabelPos = gameSettingsLabel.transform.localPosition; CustomGameMode[] gms = Enum.GetValues()[..^1]; + gms = gms.Without(CustomGameMode.TheMindGame).ToArray(); if (SubmergedCompatibility.Loaded && Main.NormalOptions.MapId == 6) gms = gms.Where(SubmergedCompatibility.IsSupported).ToArray(); int totalCols = Mathf.Max(1, Mathf.CeilToInt(gms.Length / 7f)); diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 282fc914b..81267d690 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -250,6 +250,9 @@ internal static class HudManagerPatch case CustomRoles.RRPlayer when __instance.AbilityButton != null && RoomRush.VentLimit.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out int ventLimit): __instance.AbilityButton.SetUsesRemaining(ventLimit); break; + case CustomRoles.SnowdownPlayer: + __instance.AbilityButton?.OverrideText(GetString("SnowdownButtonText")); + break; } if (role.PetActivatedAbility() && Options.CurrentGameMode == CustomGameMode.Standard && player.GetRoleTypes() != RoleTypes.Engineer && !role.OnlySpawnsWithPets() && !role.AlwaysUsesPhantomBase() && !player.GetCustomSubRoles().Any(StartGameHostPatch.BasisChangingAddons.ContainsKey) && role is not CustomRoles.Changeling and not CustomRoles.Ninja and not CustomRoles.Duality && (!role.SimpleAbilityTrigger() || !Options.UsePhantomBasis.GetBool() || !(player.IsNeutralKiller() && Options.UsePhantomBasisForNKs.GetBool())) && !(Options.UseMeetingShapeshift.GetBool() && player.UsesMeetingShapeshift())) @@ -275,6 +278,7 @@ internal static class HudManagerPatch CustomGameMode.HideAndSeek when player.IsHost() => CustomHnS.GetSuffixText(player, player, true), CustomGameMode.NaturalDisasters => NaturalDisasters.SuffixText(), CustomGameMode.Deathrace => Deathrace.GetSuffix(player, player, true), + CustomGameMode.Snowdown => Snowdown.GetHudText(), CustomGameMode.Standard => state.Role.GetSuffix(player, player, true, GameStates.IsMeeting) + GetAddonSuffixes(), _ => string.Empty }; @@ -575,6 +579,14 @@ internal static class SetHudActivePatch switch (Options.CurrentGameMode) { + case CustomGameMode.Snowdown: + __instance.AbilityButton?.ToggleVisible(true); + __instance.ReportButton?.ToggleVisible(false); + __instance.KillButton?.ToggleVisible(true); + __instance.ImpostorVentButton?.ToggleVisible(true); + __instance.SabotageButton?.ToggleVisible(true); + __instance.PetButton?.ToggleVisible(true); + return; case CustomGameMode.BedWars: __instance.AbilityButton?.ToggleVisible(true); __instance.ReportButton?.ToggleVisible(false); diff --git a/Patches/HudSpritePatch.cs b/Patches/HudSpritePatch.cs index 85ae4d31d..9fcc81b6a 100644 --- a/Patches/HudSpritePatch.cs +++ b/Patches/HudSpritePatch.cs @@ -49,6 +49,11 @@ public static class HudSpritePatch switch (player.GetCustomRole()) { + case CustomRoles.SnowdownPlayer when Snowdown.Data.TryGetValue(player.PlayerId, out Snowdown.PlayerData snowdownData) && !snowdownData.InShop: + { + newAbilityButton = CustomButton.Get("Snowdown"); + break; + } case CustomRoles.CTFPlayer: { newAbilityButton = CustomButton.Get("Tag"); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 962f580fd..99480fe94 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -301,6 +301,16 @@ internal static class SetUpRoleTextPatch __instance.RoleBlurbText.text = GetString("MinglePlayerInfo"); break; } + case CustomGameMode.Snowdown: + { + Color color = Utils.GetRoleColor(CustomRoles.SnowdownPlayer); + __instance.YouAreText.transform.gameObject.SetActive(false); + __instance.RoleText.text = GetString("SnowdownPlayer"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("SnowdownPlayerInfo"); + break; + } default: { CustomRoles role = lp.GetCustomRole(); @@ -992,6 +1002,15 @@ internal static class BeginCrewmatePatch __instance.ImpostorText.text = GetString("MinglePlayerInfo"); break; } + case CustomGameMode.Snowdown: + { + __instance.TeamTitle.text = GetString("SnowdownPlayer"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.SnowdownPlayer); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Detective); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SnowdownPlayerInfo"); + break; + } } return; @@ -1116,16 +1135,6 @@ internal static class IntroCutsceneDestroyPatch if (Main.NormalOptions.MapId != 4) { - foreach (PlayerControl pc in aapc) - { - pc.RpcResetAbilityCooldown(); - - if (pc.GetCustomRole().UsesPetInsteadOfKill()) - pc.AddAbilityCD(10); - else - pc.AddAbilityCD(false); - } - if (Options.CurrentGameMode == CustomGameMode.Standard) { if (Options.FixFirstKillCooldown.GetBool()) @@ -1167,8 +1176,17 @@ internal static class IntroCutsceneDestroyPatch LateTask.New(() => { - lp.RpcResetAbilityCooldown(); lp.SetKillCooldown(10f); + + foreach (PlayerControl pc in aapc) + { + pc.RpcResetAbilityCooldown(); + + if (pc.GetCustomRole().UsesPetInsteadOfKill()) + pc.AddAbilityCD(10); + else + pc.AddAbilityCD(false); + } }, 0.8f, log: false); StartGameHostPatch.RpcSetRoleReplacer.SetActualSelfRolesAfterOverride(); @@ -1186,7 +1204,7 @@ internal static class IntroCutsceneDestroyPatch } }, 0.1f, log: false); - if (Options.UsePets.GetBool() && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek or CustomGameMode.CaptureTheFlag or CustomGameMode.BedWars) + if (Options.UsePets.GetBool() && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek or CustomGameMode.CaptureTheFlag or CustomGameMode.BedWars or CustomGameMode.Snowdown) { void GrantPetForEveryone() { @@ -1309,6 +1327,9 @@ internal static class IntroCutsceneDestroyPatch case CustomGameMode.Mingle: Main.Instance.StartCoroutine(Mingle.GameStart()); break; + case CustomGameMode.Snowdown: + Snowdown.GameStart(); + break; } Utils.CheckAndSetVentInteractions(); @@ -1322,6 +1343,9 @@ internal static class IntroCutsceneDestroyPatch { foreach (PlayerControl player in Main.AllPlayerControls) Main.PlayerStates[player.PlayerId].InitTask(player); + + if (Options.CurrentGameMode == CustomGameMode.Snowdown) + Snowdown.GameStart(); } Logger.Info("OnDestroy", "IntroCutscene"); diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index e20f5fbc5..bf59bba73 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -1047,6 +1047,9 @@ internal static class StartGameHostPatch case CustomGameMode.Deathrace: Deathrace.Init(); goto default; + case CustomGameMode.Snowdown: + Snowdown.Init(); + goto default; default: if (Options.IntegrateNaturalDisasters.GetBool()) goto case CustomGameMode.NaturalDisasters; break; @@ -1116,6 +1119,9 @@ internal static class StartGameHostPatch case CustomGameMode.Mingle: GameEndChecker.SetPredicateToMingle(); break; + case CustomGameMode.Snowdown: + GameEndChecker.SetPredicateToSnowdown(); + break; } // Add players with unclassified roles to the list of players who require ResetCam. diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index 3a135a898..e9ecdc15f 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -66,7 +66,7 @@ internal static class EndGamePatch byte killerId = value.GetRealKiller(); bool gmIsFm = Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.StopAndGo; - bool gmIsFmhh = gmIsFm || Options.CurrentGameMode is CustomGameMode.HotPotato or CustomGameMode.HideAndSeek or CustomGameMode.Speedrun or CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle; + bool gmIsFmhh = gmIsFm || Options.CurrentGameMode is CustomGameMode.HotPotato or CustomGameMode.HideAndSeek or CustomGameMode.Speedrun or CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.KingOfTheZones or CustomGameMode.Quiz or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle or CustomGameMode.Snowdown; sb.Append($"\n{date:T} {Main.AllPlayerNames[key]} ({(gmIsFmhh ? string.Empty : Utils.GetDisplayRoleName(key, true))}{(gmIsFm ? string.Empty : Utils.GetSubRolesText(key, summary: true))}) [{Utils.GetVitalText(key)}]"); if (killerId != byte.MaxValue && killerId != key) sb.Append($"\n\t⇐ {Main.AllPlayerNames[killerId]} ({(gmIsFmhh ? string.Empty : Utils.GetDisplayRoleName(killerId, true))}{(gmIsFm ? string.Empty : Utils.GetSubRolesText(killerId, summary: true))})"); } @@ -342,6 +342,14 @@ internal static class SetEverythingUpPatch goto EndOfText; } + case CustomGameMode.Snowdown: + { + Color color = Utils.GetRoleColor(CustomRoles.SnowdownPlayer); + __instance.BackgroundBar.material.color = color; + winnerText.text = (CustomWinnerHolder.WinnerIds.Count <= 1 ? CustomWinnerHolder.WinnerIds.FirstOrDefault().ColoredPlayerName() : CustomWinnerHolder.WinnerIds.Select(x => x.ColoredPlayerName()).Join()) + GetString("Win"); + winnerText.color = color; + goto EndOfText; + } } } @@ -550,6 +558,7 @@ internal static class SetEverythingUpPatch break; } + case CustomGameMode.Snowdown: case CustomGameMode.Mingle: case CustomGameMode.Deathrace: case CustomGameMode.BedWars: diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index ad1c69465..669be5bb7 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -259,6 +259,9 @@ internal static class CheckMurderPatch case CustomGameMode.Deathrace: Deathrace.OnCheckMurder(killer, target); return false; + case CustomGameMode.Snowdown: + Snowdown.OnCheckMurder(killer, target); + return false; } PlagueBearer.CheckAndSpreadInfection(killer, target); @@ -1514,7 +1517,7 @@ internal static class FixedUpdatePatch { Camouflage.OnFixedUpdate(player); - if (localPlayer && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.FFA or CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters && GameStartTimeStamp + 44 == TimeStamp) + if (localPlayer && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.FFA or CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.Snowdown && GameStartTimeStamp + 44 == TimeStamp) NotifyRoles(); } } @@ -1975,6 +1978,9 @@ internal static class FixedUpdatePatch case CustomGameMode.Mingle when self: additionalSuffixes.Add(Mingle.GetSuffix(seer)); break; + case CustomGameMode.Snowdown: + additionalSuffixes.Add(Snowdown.GetSuffix(seer, target)); + break; } if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode) && !self && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.SoloPVP or CustomGameMode.FFA) @@ -1984,7 +1990,7 @@ internal static class FixedUpdatePatch if (addSuff.Count > 0) { - if (Suffix.Length > 0 && Suffix[^1] != '\n') + if (Suffix.ToString().RemoveHtmlTags().Length > 0 && Suffix[^1] != '\n') Suffix.Append('\n'); Suffix.Append(string.Join('\n', addSuff)); @@ -2010,10 +2016,11 @@ internal static class FixedUpdatePatch else roleText = string.Empty; - string newLineBeforeSuffix = !(Options.CurrentGameMode == CustomGameMode.BedWars && !self && GameStates.InGame) ? "\r\n" : " - "; + string suffix = Suffix.ToString().Trim(); + string newLineBeforeSuffix = !(Options.CurrentGameMode == CustomGameMode.BedWars && !self && GameStates.InGame) ? "\n" : " - "; string deathReason = !seer.IsAlive() && seer.KnowDeathReason(target) ? $"{newLineBeforeSuffix}『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))}』" : string.Empty; - target.cosmetics.nameText.text = $"{roleText}{realName}{deathReason}{Mark}{newLineBeforeSuffix}{Suffix}"; + target.cosmetics.nameText.text = $"{roleText}{realName}{deathReason}{Mark}{newLineBeforeSuffix}{suffix}"; // Camouflage if (Camouflage.IsCamouflage) target.cosmetics.nameText.text = $"{target.cosmetics.nameText.text}"; diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 1a385cbf8..ef804d668 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -372,7 +372,7 @@ public static class SabotageSystemTypeRepairDamagePatch public static bool Prefix(SabotageSystemType __instance, [HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] MessageReader msgReader) { Instance = __instance; - if (Options.CurrentGameMode != CustomGameMode.Standard || __instance.Timer > 0f) return false; + if (Options.CurrentGameMode is not (CustomGameMode.Standard or CustomGameMode.Snowdown) || __instance.Timer > 0f) return false; SystemTypes systemTypes; { diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 4ca212003..b0efc176d 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -91,7 +91,7 @@ internal static class RepairSystemPatch if (!AmongUsClient.Instance.AmHost) return true; // Execute the following only on the host - if ((Options.CurrentGameMode != CustomGameMode.Standard || Options.DisableSabotage.GetBool()) && systemType == SystemTypes.Sabotage) return false; + if ((Options.CurrentGameMode is not (CustomGameMode.Standard or CustomGameMode.Snowdown) || Options.DisableSabotage.GetBool()) && systemType == SystemTypes.Sabotage) return false; if (player.Is(CustomRoles.Fool) && systemType is SystemTypes.Comms or SystemTypes.Electrical) return false; if (SubmergedCompatibility.IsSubmerged() && systemType is not (SystemTypes.Electrical or SystemTypes.Comms)) return true; @@ -253,7 +253,7 @@ internal static class CloseDoorsPatch { public static bool Prefix([HarmonyArgument(0)] SystemTypes room) { - bool allow = !Options.DisableSabotage.GetBool() && !AntiBlackout.SkipTasks && Options.CurrentGameMode is not CustomGameMode.SoloPVP and not CustomGameMode.FFA and not CustomGameMode.StopAndGo and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.KingOfTheZones and not CustomGameMode.Quiz and not CustomGameMode.TheMindGame and not CustomGameMode.BedWars and not CustomGameMode.Deathrace and not CustomGameMode.Mingle; + bool allow = !Options.DisableSabotage.GetBool() && !AntiBlackout.SkipTasks && Options.CurrentGameMode is not CustomGameMode.SoloPVP and not CustomGameMode.FFA and not CustomGameMode.StopAndGo and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.KingOfTheZones and not CustomGameMode.Quiz and not CustomGameMode.TheMindGame and not CustomGameMode.BedWars and not CustomGameMode.Deathrace and not CustomGameMode.Mingle and not CustomGameMode.Snowdown; if (Doorjammer.JammedRooms.Contains(room)) allow = false; if (SecurityGuard.BlockSabo.Count > 0) allow = false; diff --git a/Patches/TaskAssignPatch.cs b/Patches/TaskAssignPatch.cs index e65c83b97..34551706d 100644 --- a/Patches/TaskAssignPatch.cs +++ b/Patches/TaskAssignPatch.cs @@ -177,7 +177,7 @@ internal static class RpcSetTasksPatch } // GM and Lazy Guy have no tasks - if (pc.Is(CustomRoles.GM) || pc.Is(CustomRoles.LazyGuy) || Options.CurrentGameMode is CustomGameMode.SoloPVP or CustomGameMode.FFA or CustomGameMode.HotPotato or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.Quiz or CustomGameMode.CaptureTheFlag or CustomGameMode.KingOfTheZones or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle) + if (pc.Is(CustomRoles.GM) || pc.Is(CustomRoles.LazyGuy) || Options.CurrentGameMode is CustomGameMode.SoloPVP or CustomGameMode.FFA or CustomGameMode.HotPotato or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.Quiz or CustomGameMode.CaptureTheFlag or CustomGameMode.KingOfTheZones or CustomGameMode.TheMindGame or CustomGameMode.BedWars or CustomGameMode.Deathrace or CustomGameMode.Mingle or CustomGameMode.Snowdown) { hasCommonTasks = false; numShortTasks = 0; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 2de93178f..1fc85a6bf 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -4943,6 +4943,7 @@ "GameModeTutorial.Speedrun": "The current game mode is <#800080>Speedrun.\\nComplete all tasks to gain the kill button, then kill everyone to win.\\nYou must do a task or kill within your time limit, else you suicide.", "GameModeTutorial.CaptureTheFlag": "The current game mode is <#1313c2>Capture The Flag.\\nGet to the base of the enemy team and press Vanish to pick up their flag.\\nBring it back to your own base for your team to win.\\nPress Kill on someone to tag them, this will get rid of that player temporarily or permanently.", "GameModeTutorial.NaturalDisasters": "The current game mode is <#03fc4a>Natural Disasters.\\nVarious disasters will constantly spawn on the map.\\nSome kill you instantly, some just debuff you temporarily.\\nBe the last player alive to win!", + "GameModeTutorial.Snowdown": "The current game mode is <#e4fdff>Snowdown.\\nVanish to throw a snowball in your movement direction.\\nIf it hits someone, you get a coin. Pet to turn 20 coins into 1 point.\\nCall a critical sabotage to enter/exit the shop. Highest points wins!", "GameModeListTitle": "Game Modes", "ModeStandard": "Gamemode: Standard", "FFA": "FREE FOR ALL", @@ -5484,6 +5485,29 @@ "MinglePlayer": "MINGLE", "MinglePlayerInfo": "SURVIVE BY BEING IN THE RIGHT ROOMS", "MinglePlayerInfoLong": "SURVIVE BY BEING IN THE RIGHT ROOMS", + "Snowdown": "SNOWDOWN", + "ModeSnowdown": "Gamemode: Snowdown", + "ModeDescribe.Snowdown": "In the Snowdown gamemode, the player that successfully throws the most snowballs at other players wins! You get 1 coin for every hit. You can spend coins on powerups in the shop, or turn pet to turn 20 coins into 1 point. Highest points (not coins) wins! Triggers:\\n- Vanish: Throw a snowball in your movement direction / Cycle between powerups while in shop\\n- Calling a ciritical Sabotage: Enter/Exit shop\\n- Pet: Turn 20 coins into 1 point / Buy selected powerup in shop\\n- Kill: Give the target one of your snowballs", + "Snowdown.SnowballThrowSpeedOption": "Snowball Throwing Speed", + "Snowdown.SnowballGainFrequencyOption": "Initial Snowball Gaining Frequency", + "Snowdown.MaxSnowballsInHandOption": "Max Snowballs Everyone Can Hold Initially", + "Snowdown.MaxCoinsInHandOption": "Max Coins Everyone Can Hold Initially", + "Snowdown.GameEndsAfterTimeOption": "Game Ends After a Certain Time", + "Snowdown.GameEndTimeOption": "Game Ends After", + "Snowdown.GameEndsWhenPointsReachedOption": "Game Ends When Someone Reached a Specific Number of Points", + "Snowdown.PointsToReachOption": "Points to Reach to Win", + "Snowdown.PowerUpPriceOption": "{powerup} Powerup Price (Coins)", + "Snowdown.PowerUp.FillUp": "Fill-Up", + "Snowdown.PowerUp.FillUp.Description": "Sets your snowball counter to its max", + "Snowdown.PowerUp.IncreaseCapacity": "Capacity Increment", + "Snowdown.PowerUp.IncreaseCapacity.Description": "Increases how many snowballs and coins you can hold", + "Snowdown.PowerUp.FasterPickup": "Pickup Boost", + "Snowdown.PowerUp.FasterPickup.Description": "Increases your rate of getting snowballs", + "Snowdown.ShopHelp": "Vanish: Cycle between powerups | Pet: Buy selected powerup | Sabotage: Exit shop", + "SnowdownButtonText": "THROW", + "SnowdownPlayer": "SNOWDOWN", + "SnowdownPlayerInfo": "THROW SNOWBALLS AT PLAYERS", + "SnowdownPlayerInfoLong": "THROW SNOWBALLS AT PLAYERS", "Quiz": "QUIZ", "ModeQuiz": "Gamemode: Quiz", "ModeDescribe.Quiz": "In the Quiz game mode, you must answer questions correctly to survive. There are various difficulties with varying time limits. Each question has 5 answer choices: A, B, C, D, E. Each choice has a room name next to the answer. To pick an answer, you must enter the room corresponding to the answer. If you answer correctly, you survive. If you don't answer enough questions correctly, you eventually die or compete in a Free For All (FFA) match with the other players who got the questions wrong.", @@ -10195,22 +10219,7 @@ "AGMR.Slot.RandomGroupId": "Number/ID of the random group to use for choosing the game mode for this slot/game", "AGMR.Slot.Count": "How many times should we repeat this?", "MenuTitle.AGMR.RandomGroup": "Random Group {index}", - "AGMR.RandomGroup.GMChance.Standard": "Chance of selecting Standard", - "AGMR.RandomGroup.GMChance.SoloPVP": "Chance of selecting Solo PVP", - "AGMR.RandomGroup.GMChance.FFA": "Chance of selecting FFA", - "AGMR.RandomGroup.GMChance.StopAndGo": "Chance of selecting Stop And Go", - "AGMR.RandomGroup.GMChance.HotPotato": "Chance of selecting Hot Potato", - "AGMR.RandomGroup.GMChance.HideAndSeek": "Chance of selecting (Custom) Hide And Seek", - "AGMR.RandomGroup.GMChance.Speedrun": "Chance of selecting Speedrun", - "AGMR.RandomGroup.GMChance.CaptureTheFlag": "Chance of selecting Capture The Flag", - "AGMR.RandomGroup.GMChance.NaturalDisasters": "Chance of selecting Natural Disasters", - "AGMR.RandomGroup.GMChance.RoomRush": "Chance of selecting Room Rush", - "AGMR.RandomGroup.GMChance.KingOfTheZones": "Chance of selecting King Of The Zones", - "AGMR.RandomGroup.GMChance.Quiz": "Chance of selecting Quiz", - "AGMR.RandomGroup.GMChance.TheMindGame": "Chance of selecting The Mind Game", - "AGMR.RandomGroup.GMChance.BedWars": "Chance of selecting Bed Wars", - "AGMR.RandomGroup.GMChance.Deathrace": "Chance of selecting Deathrace", - "AGMR.RandomGroup.GMChance.Mingle": "Chance of selecting Mingle", + "AGMR.RandomGroup.GMChance": "Chance of selecting {gm}", "AutoGMRotationStatusText": "AUTOMATIC GAME MODE ROTATION IS ENABLED!", "AutoGMRotationStatusText.NextGM": "Next", "AutoGMRotationStatusText.GMPoll": "Player Vote (/gmpoll)", diff --git a/Roles/Impostor/Swooper.cs b/Roles/Impostor/Swooper.cs index 653cab908..161bf8188 100644 --- a/Roles/Impostor/Swooper.cs +++ b/Roles/Impostor/Swooper.cs @@ -122,10 +122,10 @@ public class Swooper : RoleBase try { if (UsedRole == CustomRoles.Chameleon) - AURoleOptions.EngineerCooldown = Cooldown + 1f; + AURoleOptions.EngineerCooldown = Cooldown + 2f; else if (UsePhantomBasis.GetBool() && (UsedRole == CustomRoles.Swooper || UsePhantomBasisForNKs.GetBool())) { - AURoleOptions.PhantomCooldown = Cooldown + 1f; + AURoleOptions.PhantomCooldown = Cooldown + 2f; AURoleOptions.PhantomDuration = 0.1f; } } diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index 51a1daacc..c58fe4784 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -62,29 +62,32 @@ public class Executioner : RoleBase { PlayerIdList.Add(playerId); - LateTask.New(() => + if (AmongUsClient.Instance.AmHost) { - try + LateTask.New(() => { - List targetList = []; - targetList.AddRange(from target in Main.AllPlayerControls where playerId != target.PlayerId where CanTargetImpostor.GetBool() || !target.Is(CustomRoleTypes.Impostor) where CanTargetNeutralKiller.GetBool() || !target.IsNeutralKiller() where CanTargetNeutralBenign.GetBool() || !target.IsNeutralBenign() where CanTargetNeutralEvil.GetBool() || (!target.IsNeutralEvil() && !target.IsNeutralPariah()) where CanTargetCoven.GetBool() || !target.Is(CustomRoleTypes.Coven) where target.GetCustomRole() is not (CustomRoles.GM or CustomRoles.SuperStar) where Main.LoversPlayers.TrueForAll(x => x.PlayerId != playerId) select target); - targetList.AddRange(Main.AllPlayerControls.Where(x => x.IsCrewmate() && x.GetCustomRole() is not (CustomRoles.Swapper or CustomRoles.Mayor or CustomRoles.NiceGuesser or CustomRoles.Dictator))); - targetList.RemoveAll(x => x.GetCustomRole() is CustomRoles.Swapper or CustomRoles.Mayor or CustomRoles.NiceGuesser or CustomRoles.Dictator or CustomRoles.Executioner or CustomRoles.Terrorist or CustomRoles.GM); - if (!CanTargetNeutralBenign.GetBool() && !CanTargetNeutralEvil.GetBool() && !CanTargetNeutralKiller.GetBool()) targetList.RemoveAll(x => x.GetCustomRole().IsNeutral() || x.Is(CustomRoles.Bloodlust)); - - if (targetList.Count == 0) + try { - LateTask.New(() => ChangeRole(Utils.GetPlayerById(playerId)), 8f, log: false); - return; - } + List targetList = []; + targetList.AddRange(from target in Main.AllPlayerControls where playerId != target.PlayerId where CanTargetImpostor.GetBool() || !target.Is(CustomRoleTypes.Impostor) where CanTargetNeutralKiller.GetBool() || !target.IsNeutralKiller() where CanTargetNeutralBenign.GetBool() || !target.IsNeutralBenign() where CanTargetNeutralEvil.GetBool() || (!target.IsNeutralEvil() && !target.IsNeutralPariah()) where CanTargetCoven.GetBool() || !target.Is(CustomRoleTypes.Coven) where target.GetCustomRole() is not (CustomRoles.GM or CustomRoles.SuperStar) where Main.LoversPlayers.TrueForAll(x => x.PlayerId != playerId) select target); + targetList.AddRange(Main.AllPlayerControls.Where(x => x.IsCrewmate() && x.GetCustomRole() is not (CustomRoles.Swapper or CustomRoles.Mayor or CustomRoles.NiceGuesser or CustomRoles.Dictator))); + targetList.RemoveAll(x => x.GetCustomRole() is CustomRoles.Swapper or CustomRoles.Mayor or CustomRoles.NiceGuesser or CustomRoles.Dictator or CustomRoles.Executioner or CustomRoles.Terrorist or CustomRoles.GM); + if (!CanTargetNeutralBenign.GetBool() && !CanTargetNeutralEvil.GetBool() && !CanTargetNeutralKiller.GetBool()) targetList.RemoveAll(x => x.GetCustomRole().IsNeutral() || x.Is(CustomRoles.Bloodlust)); - PlayerControl selectedTarget = targetList.RandomElement(); - Target[playerId] = selectedTarget.PlayerId; - SendRPC(playerId, selectedTarget.PlayerId, "SetTarget"); - Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()}'s target: {selectedTarget.GetNameWithRole().RemoveHtmlTags()}", "Executioner"); - } - catch (Exception ex) { Logger.Error(ex.ToString(), "Executioner.Add"); } - }, 0.5f, log: false); + if (targetList.Count == 0) + { + LateTask.New(() => ChangeRole(Utils.GetPlayerById(playerId)), 8f, log: false); + return; + } + + PlayerControl selectedTarget = targetList.RandomElement(); + Target[playerId] = selectedTarget.PlayerId; + SendRPC(playerId, selectedTarget.PlayerId, "SetTarget"); + Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()}'s target: {selectedTarget.GetNameWithRole().RemoveHtmlTags()}", "Executioner"); + } + catch (Exception ex) { Logger.Error(ex.ToString(), "Executioner.Add"); } + }, 0.5f, log: false); + } } public override void Remove(byte playerId) @@ -95,7 +98,7 @@ public class Executioner : RoleBase public static void SendRPC(byte executionerId, byte targetId = 0x73, string Progress = "") { - if (!AmongUsClient.Instance.AmHost || !Utils.DoRPC) return; + if (!AmongUsClient.Instance.AmHost) return; MessageWriter writer; diff --git a/Roles/Neutral/Hater.cs b/Roles/Neutral/Hater.cs index a8fa64228..91e6d40e8 100644 --- a/Roles/Neutral/Hater.cs +++ b/Roles/Neutral/Hater.cs @@ -123,10 +123,10 @@ public class Hater : RoleBase target.SetRealKiller(killer); killer.Kill(target); target.Data.IsDead = true; - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Misfire; + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; } - killer.Suicide(PlayerState.DeathReason.Sacrifice); + killer.Suicide(PlayerState.DeathReason.Misfire); Logger.Info($"{killer.GetRealName()} killed incorrect target => misfire", "Hater"); return false; }