From 5098de77028b97fcb49b581edf6feeaa718d5d62 Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:40:14 +0530 Subject: [PATCH 01/12] cops without abilities --- GameModes/CopsAndRobbersManager.cs | 268 ++++++++++++++++++++++++ Modules/CustomRolesHelper.cs | 9 +- Modules/ExtendedPlayerControl.cs | 5 + Modules/OptionHolder.cs | 18 +- Modules/Utils.cs | 1 + Patches/CheckGameEndPatch.cs | 84 +++++++- Patches/GameOptionsMenuPatch.cs | 6 +- Patches/GameSettingMenuPatch.cs | 1 + Patches/HudPatch.cs | 18 ++ Patches/PlayerControlPatch.cs | 6 + Patches/onGameStartedPatch.cs | 13 +- Resources/Lang/en_US.json | 32 +++ Resources/roleColor.json | 3 +- Roles/Core/AssignManager/AddonAssign.cs | 8 +- Roles/Core/AssignManager/RoleAssign.cs | 8 + main.cs | 6 + 16 files changed, 474 insertions(+), 12 deletions(-) create mode 100644 GameModes/CopsAndRobbersManager.cs diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs new file mode 100644 index 0000000000..ef79f65bae --- /dev/null +++ b/GameModes/CopsAndRobbersManager.cs @@ -0,0 +1,268 @@ +using AmongUs.GameOptions; +using UnityEngine; + + +namespace TOHE; + +internal static class CopsAndRobbersManager +{ + public static readonly HashSet cops = []; + public static readonly HashSet robbers = []; + public static readonly HashSet captured = []; + private static readonly Dictionary capturedScore = []; + private static readonly Dictionary timesCaptured = []; + private static readonly Dictionary points = []; + private static readonly Dictionary defaultSpeed = []; + public static int numCops; + + public static OptionItem CandR_NumCops; + public static OptionItem CandR_NotifyRobbersWhenCaptured; + public static OptionItem CandR_InitialPoints; + public static OptionItem CandR_PointsForCapture; + public static OptionItem CandR_PointsForRescue; + + + public static void SetupCustomOption() + { + CandR_NumCops = IntegerOptionItem.Create(67_224_002, "C&R_NumCops", new(1, 5, 1), 2, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 0, 200, byte.MaxValue)) + .SetHeader(true); + CandR_NotifyRobbersWhenCaptured = BooleanOptionItem.Create(67_224_003, "C&R_NotifyRobbersWhenCaptured", true, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 0, 200, byte.MaxValue)); + CandR_InitialPoints = IntegerOptionItem.Create(67_224_005, "C&R_InitialPoints", new(0, 50, 1), 3, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 0, 200, byte.MaxValue)); + CandR_PointsForCapture = IntegerOptionItem.Create(67_224_006, "C&R_PointsForCapture", new(0, 5, 1), 1, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 0, 200, byte.MaxValue)); + CandR_PointsForRescue = IntegerOptionItem.Create(67_224_007, "C&R_PointsForRescue", new(0, 5, 1), 1, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 0, 200, byte.MaxValue)); + } + + public enum RoleType + { + Cop, + Robber, + Captured + } + public static RoleTypes RoleBase(CustomRoles role) + { + return role switch + { + CustomRoles.Cop => RoleTypes.Shapeshifter, + CustomRoles.Robber => RoleTypes.Engineer, + _ => RoleTypes.Engineer + }; + } + public static bool HasTasks(CustomRoles role) + { + return role switch + { + CustomRoles.Cop => false, + CustomRoles.Robber => true, + _ => false, + }; + } + public static void Init() + { + if (Options.CurrentGameMode != CustomGameMode.CandR) return; + + cops.Clear(); + robbers.Clear(); + captured.Clear(); + capturedScore.Clear(); + timesCaptured.Clear(); + points.Clear(); + numCops = CandR_NumCops.GetInt(); + defaultSpeed.Clear(); + } + public static Dictionary SetRoles() + { + Logger.Warn("---- Started SetRoles c&r ----", "SetRoles"); + Dictionary finalRoles = []; + var random = IRandom.Instance; + List AllPlayers = Main.AllPlayerControls.Shuffle(random).ToList(); + + if (Main.EnableGM.Value) + { + finalRoles[PlayerControl.LocalPlayer.PlayerId] = CustomRoles.GM; + AllPlayers.Remove(PlayerControl.LocalPlayer); + } + + int optImpNum = numCops; + foreach (PlayerControl pc in AllPlayers) + { + if (pc == null) continue; + if (optImpNum > 0) + { + finalRoles[pc.PlayerId] = CustomRoles.Cop; + RoleType.Cop.Add(pc.PlayerId); + optImpNum--; + } + else + { + finalRoles[pc.PlayerId] = CustomRoles.Robber; + RoleType.Robber.Add(pc.PlayerId); + } + Logger.Warn($"set role for {pc.PlayerId}: {finalRoles[pc.PlayerId]}", "SetRoles"); + } + Logger.Warn("---- finished SetRoles c&r ----", "SetRoles"); + + return finalRoles; + } + private static void Add(this RoleType role, byte playerId) + { + points[playerId] = CandR_InitialPoints.GetInt(); + defaultSpeed[playerId] = Main.AllPlayerSpeed[playerId]; + role.SetCostume(playerId: playerId); + + switch (role) + { + case RoleType.Cop: + cops.Add(playerId); + capturedScore[playerId] = 0; + return; + + case RoleType.Robber: + robbers.Add(playerId); + timesCaptured[playerId] = 0; + return; + } + } + private static void AddCaptured(this PlayerControl robber) + { + captured.Add(robber.PlayerId); + RoleType.Captured.SetCostume(playerId: robber.PlayerId); + Main.AllPlayerSpeed[robber.PlayerId] = Main.MinSpeed; + robber?.MarkDirtySettings(); + } + private static void RemoveCaptured(this PlayerControl rescued) + { + captured.Remove(rescued.PlayerId); + RoleType.Robber.SetCostume(playerId: rescued.PlayerId); //for robber + Main.AllPlayerSpeed[rescued.PlayerId] = defaultSpeed[rescued.PlayerId]; + rescued?.MarkDirtySettings(); + } + + private static void SetCostume(this RoleType opMode, byte playerId) + { + if (playerId == byte.MaxValue) return; + PlayerControl player = Utils.GetPlayerById(playerId); + if (player == null) return; + + switch (opMode) + { + case RoleType.Cop: + player.RpcSetColor(1); //blue + player.RpcSetHat("hat_police"); + player.RpcSetSkin("skin_Police"); + break; + + case RoleType.Robber: + player.RpcSetColor(6); //black + player.RpcSetHat("hat_pk04_Vagabond"); + player.RpcSetSkin("skin_None"); + break; + + case RoleType.Captured: + player.RpcSetColor(5); //yellow + player.RpcSetHat("hat_tombstone"); + player.RpcSetSkin("skin_prisoner"); + player.RpcSetVisor("visor_pk01_DumStickerVisor"); + break; + } + } + + public static void OnCopAttack(PlayerControl cop, PlayerControl robber) + { + if (cop == null || robber == null || Options.CurrentGameMode != CustomGameMode.CandR) return; + if (!cop.Is(CustomRoles.Cop) || !robber.Is(CustomRoles.Robber)) return; + + if (captured.Contains(robber.PlayerId)) + { + cop.Notify("C&R_AlreadyCaptured"); + return; + } + if (robber.inVent) + { + Logger.Info($"Robber, playerID {robber.PlayerId}, is in a vent, capture blocked", "C&R"); + return; + } + + robber.AddCaptured(); + + if (CandR_NotifyRobbersWhenCaptured.GetBool()) + { + foreach (byte pid in robbers) + { + if (pid == byte.MaxValue) continue; + PlayerControl pc = Utils.GetPlayerById(pid); + pc?.KillFlash(); + } + } + + if (!capturedScore.ContainsKey(cop.PlayerId)) capturedScore[cop.PlayerId] = 0; + capturedScore[cop.PlayerId]++; + + if (!timesCaptured.ContainsKey(robber.PlayerId)) timesCaptured[robber.PlayerId] = 0; + timesCaptured[robber.PlayerId]++; + + if (!points.ContainsKey(cop.PlayerId)) points[cop.PlayerId] = CandR_InitialPoints.GetInt(); + points[cop.PlayerId] += CandR_PointsForCapture.GetInt(); + + cop.ResetKillCooldown(); + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + class FixedUpdateInGameModeCandRPatch + { + public static void Postfix() + { + if (!GameStates.IsInTask || Options.CurrentGameMode != CustomGameMode.CandR) return; + + if (!AmongUsClient.Instance.AmHost) return; + + captured.Remove(byte.MaxValue); + if (!captured.Any()) return; + + robbers.Remove(byte.MaxValue); + + + Dictionary toRemove = []; + foreach (byte capturedId in captured) + { + PlayerControl capturedPC = Utils.GetPlayerById(capturedId); + if (capturedPC == null) continue; + + var capturedPos = capturedPC.transform.position; + + foreach (byte robberId in robbers) + { + if (captured.Contains(robberId)) continue; + PlayerControl robberPC = Utils.GetPlayerById(robberId); + if (robberPC == null) continue; + + float dis = Vector2.Distance(capturedPos, robberPC.transform.position); + if (dis < 0.3f) + { + toRemove[capturedId] = robberId; + Logger.Info($"to remove cap {capturedId}, rob: {robberId}", "to Remove fixupdate"); + break; + } + } + } + + if (!toRemove.Any()) return; + foreach ((byte rescued, byte saviour) in toRemove) + { + if (!points.ContainsKey(saviour)) points[saviour] = CandR_InitialPoints.GetInt(); + points[saviour] += CandR_PointsForRescue.GetInt(); + + Utils.GetPlayerById(rescued).RemoveCaptured(); + } + } + } +} \ No newline at end of file diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index c2e89bd780..b46340952e 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -39,7 +39,14 @@ public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor public static RoleTypes GetDYRole(this CustomRoles role) // Role has a kill button (Non-Impostor) { - if (role is CustomRoles.Killer) return RoleTypes.Impostor; // FFA + switch (Options.CurrentGameMode) + { + case CustomGameMode.FFA: //FFA + if (role is CustomRoles.Killer) return RoleTypes.Impostor; + break; + case CustomGameMode.CandR: //C&R + return CopsAndRobbersManager.RoleBase(role); + } return (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor or CustomRoles.Shapeshifter) && !role.IsImpostor() ? role.GetStaticRoleClass().ThisRoleBase.GetRoleTypes() diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index f2f1d02e40..99fdc32d04 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1002,6 +1002,11 @@ public static string GetRealName(this PlayerControl player, bool isMeeting = fal public static bool CanUseKillButton(this PlayerControl pc) { if (GameStates.IsLobby) return false; + if (Options.CurrentGameMode is CustomGameMode.CandR) //C&R + { + + return (pc.Is(CustomRoles.Cop)); + } if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId) || DollMaster.IsDoll(pc.PlayerId)) return false; if (pc.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && !Options.ShieldedCanUseKillButton.GetBool() && MeetingStates.FirstMeeting) return false; if (pc.Is(CustomRoles.Killer) || Mastermind.PlayerIsManipulated(pc)) return true; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index edc3e409b4..dda56e9805 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -13,6 +13,7 @@ public enum CustomGameMode { Standard = 0x01, FFA = 0x02, + CandR = 0x03, HidenSeekTOHE = 0x08, // HidenSeekTOHE must be after other game modes All = int.MaxValue @@ -48,14 +49,26 @@ public static CustomGameMode CurrentGameMode => GameMode.GetInt() switch { 1 => CustomGameMode.FFA, - 2 => CustomGameMode.HidenSeekTOHE, // HidenSeekTOHE must be after other game modes + 2 => CustomGameMode.CandR, + 3 => CustomGameMode.HidenSeekTOHE, // HidenSeekTOHE must be after other game modes _ => CustomGameMode.Standard }; + public static int GetGameModeInt(CustomGameMode mode) + => mode switch + { + CustomGameMode.FFA => 1, + CustomGameMode.CandR => 2, + CustomGameMode.HidenSeekTOHE => 3, // HidenSeekTOHE must be after other game modes + _ => 0 + }; + public static readonly string[] gameModes = [ "Standard", "FFA", + "Cops&Robbers", + "Hide&SeekTOHE", // HidenSeekTOHE must be after other game modes @@ -1139,6 +1152,9 @@ private static System.Collections.IEnumerator CoLoadOptions() //FFA FFAManager.SetupCustomOption(); + //C&R + CopsAndRobbersManager.SetupCustomOption(); + // Hide & Seek TextOptionItem.Create(10000055, "MenuTitle.Hide&Seek", TabGroup.ModSettings) .SetGameMode(CustomGameMode.HidenSeekTOHE) diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 2b7cf4acb8..698d5f14d5 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -609,6 +609,7 @@ public static bool HasTasks(NetworkedPlayerInfo playerData, bool ForRecompute = if (GameStates.IsHideNSeek) return hasTasks; var role = States.MainRole; + if (Options.CurrentGameMode == CustomGameMode.CandR) return CopsAndRobbersManager.HasTasks(role); //C&R if (States.RoleClass != null && States.RoleClass.HasTasks(playerData, role, ForRecompute) == false) hasTasks = false; diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index b91ac1f29e..3cca7747d9 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -54,9 +54,12 @@ public static bool Prefix() predicate.CheckForEndGame(out reason); // FFA - if (Options.CurrentGameMode == CustomGameMode.FFA) + switch (Options.CurrentGameMode) { - if (WinnerIds.Count > 0 || WinnerTeam != CustomWinner.Default) + case CustomGameMode.FFA: + case CustomGameMode.CandR: + + if (WinnerIds.Count > 0 || WinnerTeam != CustomWinner.Default) { ShipStatus.Instance.enabled = false; StartEndGame(reason); @@ -535,6 +538,8 @@ void SetGhostRole(bool ToGhostImpostor) public static void SetPredicateToNormal() => predicate = new NormalGameEndPredicate(); public static void SetPredicateToFFA() => predicate = new FFAGameEndPredicate(); + public static void SetPredicateToCandR() => predicate = new CandRGameEndPredicate(); //C&R + // ===== Check Game End ===== @@ -653,6 +658,81 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) } } +// For C&R +class CandRGameEndPredicate : GameEndPredicate +{ + public override bool CheckForEndGame(out GameOverReason reason) + { + // task win + reason = GameOverReason.ImpostorByKill; + if (WinnerTeam != CustomWinner.Default) return false; + if (CheckGameEndByLivingPlayers(out reason) || CheckGameEndByTask(out reason)) return true; + return false; + } + public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + + // everyone died + reason = GameOverReason.ImpostorByKill; + if (!Main.AllAlivePlayerControls.Any()) + { + reason = GameOverReason.ImpostorByKill; + ResetAndSetWinner(CustomWinner.None); + Logger.Info("Game end because all players dead", "C&R"); + return true; + } + + bool copsAlive = false; + + + bool allCaptured = true; + foreach (var pc in Main.AllAlivePlayerControls) + { + if (copsAlive && !allCaptured) break; + if (pc.Is(CustomRoles.Cop)) copsAlive = true; + else if (pc.Is(CustomRoles.Robber) && !CopsAndRobbersManager.captured.Contains(pc.PlayerId)) allCaptured = false; + } + + // no cops left + if (!copsAlive) + { + reason = GameOverReason.ImpostorDisconnect; + ResetAndSetWinner(CustomWinner.Robbers); + WinnerIds = [.. CopsAndRobbersManager.robbers]; + Logger.Info("Game end because No cops left", "C&R"); + return true; + } + + // all robbers captured + if (allCaptured) + { + reason = GameOverReason.ImpostorByKill; + ResetAndSetWinner(CustomWinner.Cops); + WinnerIds = [.. CopsAndRobbersManager.cops]; + Logger.Info("Game end because all robbers captured", "C&R"); + return true; + } + + return false; + } + + public override bool CheckGameEndByTask(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) + { + reason = GameOverReason.HumansByTask; + ResetAndSetWinner(CustomWinner.Robbers); + WinnerIds = [.. CopsAndRobbersManager.robbers]; + Logger.Info("Game end because robbers completed all tasks", "C&R"); + return true; + } + return false; + } +} + + // For FFA Games class FFAGameEndPredicate : GameEndPredicate { diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index c2d7f0bd94..05f831eca0 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -656,13 +656,13 @@ private static bool UpdateValuePrefix(StringOption __instance) item.SetValue(__instance.GetInt()); if (item is PresetOptionItem || (item is StringOptionItem && item.Name == "GameMode")) { - if (Options.GameMode.GetInt() == 2 && !GameStates.IsHideNSeek) //Hide And Seek + if (Options.CurrentGameMode == CustomGameMode.HidenSeekTOHE && !GameStates.IsHideNSeek) //Hide And Seek { Options.GameMode.SetValue(0); } - else if (Options.GameMode.GetInt() != 2 && GameStates.IsHideNSeek) + else if (Options.CurrentGameMode == CustomGameMode.HidenSeekTOHE && GameStates.IsHideNSeek) { - Options.GameMode.SetValue(2); + Options.GameMode.SetValue(Options.GetGameModeInt(CustomGameMode.HidenSeekTOHE)); } GameOptionsMenuPatch.ReOpenSettings(item.Name != "GameMode" ? 1 : 4); } diff --git a/Patches/GameSettingMenuPatch.cs b/Patches/GameSettingMenuPatch.cs index 18c57c3a48..1ee6072644 100644 --- a/Patches/GameSettingMenuPatch.cs +++ b/Patches/GameSettingMenuPatch.cs @@ -32,6 +32,7 @@ public static void StartPostfix(GameSettingMenu __instance) { CustomGameMode.HidenSeekTOHE => Enum.GetValues().Skip(3).ToArray(), CustomGameMode.FFA => Enum.GetValues().Skip(2).ToArray(), + CustomGameMode.CandR => Enum.GetValues().Skip(3).ToArray(), _ => [] }; diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index b05864299c..c3f6d534ed 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -342,6 +342,24 @@ public static void Postfix(TaskPanelBehaviour __instance) AllText = $"{AllText}"; + break; + case CustomGameMode.CandR: //C&R + var lines1 = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); + StringBuilder sb1 = new(); + foreach (var eachLine in lines1) + { + var line = eachLine.Trim(); + if ((line.StartsWith("") || line.StartsWith("")) && sb1.Length < 1 && !line.Contains('(')) continue; + sb1.Append(line + "\r\n"); + } + + if (sb1.Length > 1) + { + var text = sb1.ToString().TrimEnd('\n').TrimEnd('\r'); + if (!Utils.HasTasks(player.Data, false) && sb1.ToString().Count(s => (s == '\n')) >= 2) + text = $"{Utils.ColorString(Utils.GetRoleColor(player.GetCustomRole()).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}"; + AllText += $"\r\n\r\n{text}"; + } break; } diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index e99e436ee2..eb2a445424 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -200,6 +200,12 @@ public static bool CheckForInvalidMurdering(PlayerControl killer, PlayerControl FFAManager.OnPlayerAttack(killer, target); return false; } + //C&R + if (Options.CurrentGameMode == CustomGameMode.CandR) + { + CopsAndRobbersManager.OnCopAttack(killer, target); + return false; + } // if player hacked by Glitch if (Glitch.HasEnabled && !Glitch.OnCheckMurderOthers(killer, target)) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index e1dc9ad1c6..5a5a8cb630 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -220,6 +220,9 @@ public static void Postfix(AmongUsClient __instance) //FFA FFAManager.Init(); + //C&R + CopsAndRobbersManager.Init(); + FallFromLadder.Reset(); CustomWinnerHolder.Reset(); @@ -417,9 +420,12 @@ public static System.Collections.IEnumerator AssignRoles() Main.PlayerStates[pc.PlayerId].SetMainRole(role); } - if (Options.CurrentGameMode == CustomGameMode.FFA) + switch (Options.CurrentGameMode) { - foreach (var pair in Main.PlayerStates) + case CustomGameMode.FFA: + case CustomGameMode.CandR: + + foreach (var pair in Main.PlayerStates) { ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); } @@ -500,6 +506,9 @@ public static System.Collections.IEnumerator AssignRoles() case CustomGameMode.FFA: GameEndCheckerForNormal.SetPredicateToFFA(); break; + //case CustomGameMode.CandR: + // GameEndCheckerForNormal.SetPredicateToCandR(); + // break; } EAC.LogAllRoles(); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 96d835e4da..05f05f02c9 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3611,6 +3611,38 @@ "LongMode": "Enable to have a long neck", "InfluencedChangeVote": "Oops! You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", + "C&R": "Cops and Robbers", + "C&RShortInfo": "Robbers complete tasks, while Cops race to capture them!", + "ModeC&R": "Gamemode: Cops And Robbers", + "C&R_NumCops": "Number of Cops", + "C&R_CopAbilityTriggerChance": "Ability trigger chance", + "C&R_CopAbilityDuration": "Ability Duration", + "C&R_HotPursuitChance": "Hot Pursuit activation chance", + "C&R_HotPursuitSpeed": "Increase speed by", + "C&R_SpikeStripChance": "Spike Strip activation chance", + "C&R_SpikeStripRadius": "Spike Strip trigger radius", + "C&R_SpikeStripDuration": "Spike Strip duration", + "C&R_FlashBangChance": "Flash Bang activation chance", + "C&R_FlashBangRadius": "Flash Bang radius", + "C&R_FlashBangDuration": "Flash Bang duration", + "C&R_RadarChance": "Radar chance", + "C&R_ScopeChance": "Scope chance", + "C&R_ScopeIncrease": "Scope Increase", + "C&R_CopAbilityActivated": "{Ability.Name} activated", + "CopAbility.HotPursuit": "Hot Pursuit", + "CopAbility.SpikeStrip": "Spike Strip", + "CopAbility.FlashBang": "Flash Bang", + "CopAbility.Radar": "Radar", + "CopAbility.Scope": "Scope", + + "C&R_NotifyRobbersWhenCaptured": "Notify Robbers when captured", + "C&R_InitialPoints": "Initial points", + "C&R_PointsForCapture": "Points for capture", + "C&R_PointsForRescue": "Points for rescue", + "Cop": "Cop", + "CopInfo": "Capture all Robbers to win", + "CopInfoLong": "As a cop, use your kill button to capture robbers before they finish their tasks. When caught, robbers freeze in place. Depending on the settings, repeated captures may send them to jail.\n\n Each capture earns you points, which you can spend to activate powerful abilities for a limited duration.\nYou can trigger a random ability with the shapeshift button or use chat commands for activating specific ability\u2014just enter \"/ability cop\" to view all your options.\n\nTrack down the robbers and secure your win!", + "FFA": "Free For All", "ModeFFA": "Gamemode: FFA", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index 92684cb179..e4a20597cf 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -247,5 +247,6 @@ "Radar": "#1eff1e", "Rebirth": "#f08c22", "Sloth": "#376db8", - "Eavesdropper": "#ffe6bf" + "Eavesdropper": "#ffe6bf", + "Cop": "#007BFF" } diff --git a/Roles/Core/AssignManager/AddonAssign.cs b/Roles/Core/AssignManager/AddonAssign.cs index 1d58f36b2f..845463e452 100644 --- a/Roles/Core/AssignManager/AddonAssign.cs +++ b/Roles/Core/AssignManager/AddonAssign.cs @@ -31,8 +31,12 @@ private static bool NotAssignAddOnInGameStarted(CustomRoles role) public static void StartSelect() { - if (Options.CurrentGameMode == CustomGameMode.FFA) return; - + switch (Options.CurrentGameMode) + { + case CustomGameMode.FFA: + case CustomGameMode.CandR: + return; + } AddonRolesList.Clear(); foreach (var cr in CustomRolesHelper.AllRoles) { diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index a517e56189..0951ec05f7 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -58,6 +58,14 @@ public static void StartSelect() RoleResult[pc.PlayerId] = CustomRoles.Killer; } return; + case CustomGameMode.CandR: + RoleResult = []; + RoleResult = CopsAndRobbersManager.SetRoles(); + foreach ((byte playerId, CustomRoles rl) in RoleResult) + { + Logger.Warn($"set role for {playerId}: {rl}", "Start select"); + } + return; } var rd = IRandom.Instance; diff --git a/main.cs b/main.cs index ce097ccbc8..cc2dbb4074 100644 --- a/main.cs +++ b/main.cs @@ -867,6 +867,10 @@ public enum CustomRoles //FFA Killer, + //C&R + Cop, + Robber, + //GM GM, @@ -1019,6 +1023,8 @@ public enum CustomWinner Doppelganger = CustomRoles.Doppelganger, Solsticer = CustomRoles.Solsticer, Apocalypse = CustomRoles.Apocalypse, + Robbers = CustomRoles.Robber, //C&R + Cops = CustomRoles.Cop, //C&R } public enum AdditionalWinners { From 824ff5ecf3b49bdf5b313203e8e9ff12f24b7551 Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Sat, 28 Sep 2024 01:16:21 +0530 Subject: [PATCH 02/12] cops with abilities (no progress text) --- GameModes/CopsAndRobbersManager.cs | 662 ++++++++++++++++-- Modules/CustomRolesHelper.cs | 1 + Modules/ExtendedPlayerControl.cs | 92 +-- .../PlayerGameOptionsSender.cs | 34 +- Modules/ModUpdater.cs | 3 +- Modules/OptionHolder.cs | 2 +- Modules/OptionItem/OptionItem.cs | 3 + Modules/RPC.cs | 4 + Modules/Utils.cs | 3 + Patches/ChatCommandPatch.cs | 50 +- Patches/CheckGameEndPatch.cs | 2 +- Patches/HudPatch.cs | 3 + Patches/IntroPatch.cs | 311 ++++---- Patches/PlayerControlPatch.cs | 57 +- Patches/onGameStartedPatch.cs | 6 +- Resources/Lang/en_US.json | 4 + Roles/Core/AssignManager/RoleAssign.cs | 4 - 17 files changed, 954 insertions(+), 287 deletions(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index ef79f65bae..df88d2b686 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -1,45 +1,158 @@ using AmongUs.GameOptions; +using static TOHE.Translator; using UnityEngine; - +using Hazel; +using static UnityEngine.GraphicsBuffer; +using TOHE.Modules; +using MS.Internal.Xml.XPath; namespace TOHE; internal static class CopsAndRobbersManager { + private const int Id = 67_224_001; + public static readonly HashSet cops = []; public static readonly HashSet robbers = []; - public static readonly HashSet captured = []; + public static readonly Dictionary captured = []; + private static readonly Dictionary capturedScore = []; private static readonly Dictionary timesCaptured = []; - private static readonly Dictionary points = []; + private static readonly Dictionary saved = []; private static readonly Dictionary defaultSpeed = []; - public static int numCops; + + private static readonly Dictionary copAbilityChances = []; + private static readonly Dictionary RemoveAbility = []; + private static readonly Dictionary trapLocation = []; + private static readonly HashSet removeTrap = []; + private static readonly Dictionary spikeTrigger = []; + private static readonly Dictionary flashTrigger = []; + private static readonly Dictionary radar = []; + private static readonly Dictionary scopeAltered = []; + private static int killDistance; + + + private static int numCops; public static OptionItem CandR_NumCops; - public static OptionItem CandR_NotifyRobbersWhenCaptured; - public static OptionItem CandR_InitialPoints; - public static OptionItem CandR_PointsForCapture; - public static OptionItem CandR_PointsForRescue; + private static OptionItem CandR_NotifyRobbersWhenCaptured; + private static OptionItem CandR_CaptureCooldown; + private static OptionItem CandR_CopAbilityTriggerChance; + private static OptionItem CandR_AbilityCooldown; + private static OptionItem CandR_CopAbilityDuration; + private static OptionItem CandR_HotPursuitChance; + private static OptionItem CandR_HotPursuitSpeed; + private static OptionItem CandR_SpikeStripChance; + private static OptionItem CandR_SpikeStripRadius; + private static OptionItem CandR_SpikeStripDuration; + private static OptionItem CandR_FlashBangChance; + private static OptionItem CandR_FlashBangRadius; + private static OptionItem CandR_FlashBangDuration; + private static OptionItem CandR_RadarChance; + private static OptionItem CandR_ScopeChance; + private static OptionItem CandR_ScopeIncrease; + public static OptionItem CopHeader; + //public static OptionItem CopActiveHidden; + public static OptionItem RobberHeader; public static void SetupCustomOption() { - CandR_NumCops = IntegerOptionItem.Create(67_224_002, "C&R_NumCops", new(1, 5, 1), 2, TabGroup.ModSettings, false) + CopHeader = TextOptionItem.Create(Id-1, "Cop", TabGroup.ModSettings) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 0, 200, byte.MaxValue)) - .SetHeader(true); - CandR_NotifyRobbersWhenCaptured = BooleanOptionItem.Create(67_224_003, "C&R_NotifyRobbersWhenCaptured", true, TabGroup.ModSettings, false) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)); + + CandR_NumCops = IntegerOptionItem.Create(Id + 1, "C&R_NumCops", new(1, 5, 1), 2, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)); + CandR_CaptureCooldown = FloatOptionItem.Create(Id + 2, "C&R_CaptureCooldown", new(10f, 60f, 2.5f), 25f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 0, 200, byte.MaxValue)); - CandR_InitialPoints = IntegerOptionItem.Create(67_224_005, "C&R_InitialPoints", new(0, 50, 1), 3, TabGroup.ModSettings, false) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + + CandR_CopAbilityTriggerChance = IntegerOptionItem.Create(Id + 3, "C&R_CopAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent); + //.SetParent(CopActiveHidden); + + CandR_CopAbilityDuration = IntegerOptionItem.Create(Id + 4, "C&R_CopAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 0, 200, byte.MaxValue)); - CandR_PointsForCapture = IntegerOptionItem.Create(67_224_006, "C&R_PointsForCapture", new(0, 5, 1), 1, TabGroup.ModSettings, false) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetParent(CandR_CopAbilityTriggerChance); + CandR_AbilityCooldown = FloatOptionItem.Create(Id + 5, "C&R_AbilityCooldown", new(10f, 60f, 2.5f), 20f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 0, 200, byte.MaxValue)); - CandR_PointsForRescue = IntegerOptionItem.Create(67_224_007, "C&R_PointsForRescue", new(0, 5, 1), 1, TabGroup.ModSettings, false) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetParent(CandR_CopAbilityTriggerChance); + + + CandR_HotPursuitChance = IntegerOptionItem.Create(Id + 6, "C&R_HotPursuitChance", new(0, 100, 5), 35, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_CopAbilityTriggerChance); + CandR_HotPursuitSpeed = FloatOptionItem.Create(Id + 7, "C&R_HotPursuitSpeed", new(0f, 2f, 0.25f), 1f, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier) + .SetParent(CandR_HotPursuitChance); + + + CandR_SpikeStripChance = IntegerOptionItem.Create(Id + 8, "C&R_SpikeStripChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_CopAbilityTriggerChance); + CandR_SpikeStripRadius = FloatOptionItem.Create(Id + 9, "C&R_SpikeStripRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier) + .SetParent(CandR_SpikeStripChance); + CandR_SpikeStripDuration = IntegerOptionItem.Create(Id + 10, "C&R_SpikeStripDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetParent(CandR_SpikeStripChance); + + CandR_FlashBangChance = IntegerOptionItem.Create(Id + 11, "C&R_FlashBangChance", new(0, 100, 5), 15, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 0, 200, byte.MaxValue)); + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_CopAbilityTriggerChance); + CandR_FlashBangRadius = FloatOptionItem.Create(Id + 12, "C&R_FlashBangRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier) + .SetParent(CandR_FlashBangChance); + CandR_FlashBangDuration = IntegerOptionItem.Create(Id + 13, "C&R_FlashBangDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetParent(CandR_FlashBangChance); + + CandR_RadarChance = IntegerOptionItem.Create(Id + 14, "C&R_RadarChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_CopAbilityTriggerChance); + + CandR_ScopeChance = IntegerOptionItem.Create(Id + 15, "C&R_ScopeChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_CopAbilityTriggerChance); + CandR_ScopeIncrease = IntegerOptionItem.Create(Id + 16, "C&R_ScopeIncrease", new(1, 5, 1), 1, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier) + .SetParent(CandR_ScopeChance); + + + CandR_NotifyRobbersWhenCaptured = BooleanOptionItem.Create(Id + 17, "C&R_NotifyRobbersWhenCaptured", true, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)); } public enum RoleType @@ -48,6 +161,23 @@ public enum RoleType Robber, Captured } + private enum RobberAbility + { + AdrenalineRush, // Increases speed for a set amount of time. + EnergyShield, // Protects from the next capture attempt. + SmokeBomb, // Blinds the Cop when captured. + Disguise, // Wear the same costume as a Cop for confusion. + InvisibilityCloak // Become invisible for a set amount of duration. + } + private enum CopAbility + { + HotPursuit, // Speed boost for a set amount of time. + SpikeStrip, // Sets a trap that slows down the robber. + FlashBang, // Sets a trap that blinds the robber temporarily. + Radar, // Points to the closest robber. + Scope // Increases the capture range. + } + public static RoleTypes RoleBase(CustomRoles role) { return role switch @@ -66,6 +196,27 @@ public static bool HasTasks(CustomRoles role) _ => false, }; } + + private static void CopCumulativeChances() + { + Dictionary copOptionItems = new() + { + {CopAbility.HotPursuit, CandR_HotPursuitChance }, + {CopAbility.SpikeStrip, CandR_SpikeStripChance }, + {CopAbility.FlashBang, CandR_FlashBangChance }, + {CopAbility.Radar, CandR_RadarChance }, + {CopAbility.Scope, CandR_ScopeChance }, + }; + copAbilityChances.Clear(); + var cumulativeChance = 0; + foreach (var optionItem in copOptionItems) + { + if (optionItem.Value.GetInt() == 0) continue; + cumulativeChance += optionItem.Value.GetInt(); + copAbilityChances.Add(optionItem.Key, cumulativeChance); + } + } + public static void Init() { if (Options.CurrentGameMode != CustomGameMode.CandR) return; @@ -75,13 +226,21 @@ public static void Init() captured.Clear(); capturedScore.Clear(); timesCaptured.Clear(); - points.Clear(); + saved.Clear(); numCops = CandR_NumCops.GetInt(); defaultSpeed.Clear(); + RemoveAbility.Clear(); + trapLocation.Clear(); + removeTrap.Clear(); + spikeTrigger.Clear(); + flashTrigger.Clear(); + radar.Clear(); + scopeAltered.Clear(); + killDistance = Main.RealOptionsData.GetInt(Int32OptionNames.KillDistance); + CopCumulativeChances(); } public static Dictionary SetRoles() { - Logger.Warn("---- Started SetRoles c&r ----", "SetRoles"); Dictionary finalRoles = []; var random = IRandom.Instance; List AllPlayers = Main.AllPlayerControls.Shuffle(random).ToList(); @@ -107,15 +266,13 @@ public static Dictionary SetRoles() finalRoles[pc.PlayerId] = CustomRoles.Robber; RoleType.Robber.Add(pc.PlayerId); } - Logger.Warn($"set role for {pc.PlayerId}: {finalRoles[pc.PlayerId]}", "SetRoles"); + Logger.Msg($"set role for {pc.PlayerId}: {finalRoles[pc.PlayerId]}", "SetRoles"); } - Logger.Warn("---- finished SetRoles c&r ----", "SetRoles"); return finalRoles; } private static void Add(this RoleType role, byte playerId) { - points[playerId] = CandR_InitialPoints.GetInt(); defaultSpeed[playerId] = Main.AllPlayerSpeed[playerId]; role.SetCostume(playerId: playerId); @@ -124,64 +281,237 @@ private static void Add(this RoleType role, byte playerId) case RoleType.Cop: cops.Add(playerId); capturedScore[playerId] = 0; + Main.UnShapeShifter.Add(playerId); return; case RoleType.Robber: robbers.Add(playerId); timesCaptured[playerId] = 0; + saved[playerId] = 0; return; } } - private static void AddCaptured(this PlayerControl robber) + private static void AddCaptured(this PlayerControl robber, Vector2 capturedLocation) { - captured.Add(robber.PlayerId); + captured[robber.PlayerId] = capturedLocation; RoleType.Captured.SetCostume(playerId: robber.PlayerId); Main.AllPlayerSpeed[robber.PlayerId] = Main.MinSpeed; robber?.MarkDirtySettings(); } private static void RemoveCaptured(this PlayerControl rescued) { + if (rescued == null) return; captured.Remove(rescued.PlayerId); RoleType.Robber.SetCostume(playerId: rescued.PlayerId); //for robber Main.AllPlayerSpeed[rescued.PlayerId] = defaultSpeed[rescued.PlayerId]; rescued?.MarkDirtySettings(); } - private static void SetCostume(this RoleType opMode, byte playerId) + public static void SetCostume(this RoleType opMode, byte playerId) { if (playerId == byte.MaxValue) return; PlayerControl player = Utils.GetPlayerById(playerId); if (player == null) return; - + var playerOutfit = new NetworkedPlayerInfo.PlayerOutfit(); switch (opMode) { case RoleType.Cop: - player.RpcSetColor(1); //blue - player.RpcSetHat("hat_police"); - player.RpcSetSkin("skin_Police"); - break; + playerOutfit.Set(player.GetRealName(isMeeting:true), + 1, //blue + "hat_police", //hat + "skin_Police", //skin + "visor_pk01_Security1Visor", //visor + player.CurrentOutfit.PetId, + player.CurrentOutfit.NamePlateId); + + //player.RpcSetColor(1); //blue + //player.RpcSetHat("hat_police"); + //player.RpcSetSkin("skin_Police"); + //player.RpcSetVisor("visor_pk01_Security1Visor"); + + break; case RoleType.Robber: - player.RpcSetColor(6); //black - player.RpcSetHat("hat_pk04_Vagabond"); - player.RpcSetSkin("skin_None"); + playerOutfit.Set(player.GetRealName(isMeeting: true), + 6, //black + "hat_pk04_Vagabond", //hat + "skin_None", //skin + "visor_None", //visor + player.CurrentOutfit.PetId, + player.CurrentOutfit.NamePlateId); + //player.RpcSetColor(6); //black + //player.RpcSetHat("hat_pk04_Vagabond"); + //player.RpcSetSkin("skin_None"); break; - case RoleType.Captured: - player.RpcSetColor(5); //yellow - player.RpcSetHat("hat_tombstone"); - player.RpcSetSkin("skin_prisoner"); - player.RpcSetVisor("visor_pk01_DumStickerVisor"); + playerOutfit.Set(player.GetRealName(isMeeting: true), + 5, //yellow + "hat_tombstone", //hat + "skin_prisoner", //skin + "visor_pk01_DumStickerVisor", //visor + player.CurrentOutfit.PetId, + player.CurrentOutfit.NamePlateId); + //player.RpcSetColor(5); //yellow + //player.RpcSetHat("hat_tombstone"); + //player.RpcSetSkin("skin_prisoner"); + //player.RpcSetVisor("visor_pk01_DumStickerVisor"); + break; + } + player.SetNewOutfit(newOutfit: playerOutfit, setName: false, setNamePlate: false); + Main.OvverideOutfit[player.PlayerId] = (playerOutfit, Main.PlayerStates[player.PlayerId].NormalOutfit.PlayerName); + } + public static void CaptureCooldown(PlayerControl cop) => + Main.AllPlayerKillCooldown[cop.PlayerId] = CandR_CaptureCooldown.GetFloat(); + + private static void SendCandRData(byte op, byte copId) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncCandRData, SendOption.Reliable, -1); + writer.Write(op); + switch (op) + { + case 0: + writer.Write(copId); + writer.Write(radar[copId]); + break; + case 1: + writer.Write(copId); + break; + } + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + public static void ReceiveCandRData(MessageReader reader) + { + byte op = reader.ReadByte(); + switch (op) + { + case 0: + byte copId = reader.ReadByte(); + byte radarId = reader.ReadByte(); + radar[copId] = radarId; + break; + case 1: + byte removeCopId = reader.ReadByte(); + radar.Remove(removeCopId); + break; + } + + } + + private static CopAbility? RandomCopAbility() + { + var random = IRandom.Instance; + var shouldTrigger = random.Next(100); + if (copAbilityChances.Count == 0 || shouldTrigger >= CandR_CopAbilityTriggerChance.GetInt()) return null; + + int randomChance = random.Next(copAbilityChances.Values.Last()); + + foreach (var ability in copAbilityChances) + { + if (randomChance < ability.Value) + { + return ability.Key; + } + } + return null; // shouldn't happen + } + private static void DeactivateCopAbility(this PlayerControl cop, CopAbility? ability, Vector2 loc) + { + if (ability == null || cop == null) return; + switch (ability) + { + case CopAbility.HotPursuit: + Main.AllPlayerSpeed[cop.PlayerId] -= CandR_HotPursuitSpeed.GetFloat(); + cop.MarkDirtySettings(); + break; + case CopAbility.SpikeStrip: //it might also be removed in fixed update when trap triggered. + case CopAbility.FlashBang: //it might also be removed in fixed update when trap triggered. + trapLocation.Remove(loc); + break; + case CopAbility.Radar: + byte targetId = radar[cop.PlayerId]; + Logger.Info($"Removed radar for {cop.PlayerId}", "Remove radar"); + radar.Remove(cop.PlayerId); + SendCandRData(1, cop.PlayerId); + TargetArrow.Remove(cop.PlayerId, targetId); + break; + case CopAbility.Scope: + scopeAltered.Remove(cop.PlayerId); + cop.MarkDirtySettings(); break; + default: + return; } + RemoveAbility.Remove(cop.PlayerId); } + private static void ActivateCopAbility(this PlayerControl cop, CopAbility? ability) + { + Vector2 loc = cop.GetCustomPosition(); + switch (ability) + { + case CopAbility.HotPursuit: //increase speed of cop + Main.AllPlayerSpeed[cop.PlayerId] += CandR_HotPursuitSpeed.GetFloat(); + cop?.MarkDirtySettings(); + break; + case CopAbility.SpikeStrip: //add location of spike trap, when triggered, player speed will reduce + case CopAbility.FlashBang: //add location of flash trap, when triggered, player vision will reduce + if (trapLocation.ContainsKey(loc)) + { + Logger.Info("Location was already trapped", "SpikeStrip activate"); + return; + } + trapLocation.Add(loc, ability); + break; + case CopAbility.Radar: + if (radar.ContainsKey(cop.PlayerId)) + return; + radar.Add(cop.PlayerId, byte.MaxValue); + SendCandRData(0, cop.PlayerId); + Logger.Info($"Added {cop.PlayerId} for radar", "Ability activated"); + break; + case CopAbility.Scope: + if (scopeAltered.ContainsKey(cop.PlayerId)) + return; + scopeAltered[cop.PlayerId] = false; + cop.MarkDirtySettings(); + break; + default: + return; + } + RemoveAbility[cop.PlayerId] = ability; + var notifyMsg = GetString("C&R_CopAbilityActivated"); + cop.Notify(string.Format(notifyMsg.Replace("{Ability.Name}", "{0}"), GetString($"CopAbility.{ability}")), CandR_CopAbilityDuration.GetFloat()); + _ = new LateTask(() => + { + if (!GameStates.IsInGame || !RemoveAbility.ContainsKey(cop.PlayerId)) return; + cop.DeactivateCopAbility(ability: ability, loc: loc); + }, CandR_CopAbilityDuration.GetInt(), "Remove cop ability"); + } + public static void UnShapeShiftButton(PlayerControl shapeshifter) + { + if (!AmongUsClient.Instance.AmHost) return; + if (shapeshifter == null) return; + if (!shapeshifter.Is(CustomRoles.Cop)) return; + + + CopAbility? ability = RandomCopAbility(); + if (ability == null) return; + shapeshifter.ActivateCopAbility(ability); + Logger.Info($"Activating {ability} for id: {shapeshifter.PlayerId}", "C&R OnCheckShapeshift"); + return; + } + public static string GetClosestArrow(PlayerControl seer, PlayerControl target, bool isForMeeting = false) + { + if (isForMeeting || !radar.ContainsKey(seer.PlayerId) || seer.PlayerId != target.PlayerId) return string.Empty; + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cop), TargetArrow.GetArrows(seer)); + } public static void OnCopAttack(PlayerControl cop, PlayerControl robber) { if (cop == null || robber == null || Options.CurrentGameMode != CustomGameMode.CandR) return; if (!cop.Is(CustomRoles.Cop) || !robber.Is(CustomRoles.Robber)) return; - if (captured.Contains(robber.PlayerId)) + if (captured.ContainsKey(robber.PlayerId)) { cop.Notify("C&R_AlreadyCaptured"); return; @@ -192,7 +522,8 @@ public static void OnCopAttack(PlayerControl cop, PlayerControl robber) return; } - robber.AddCaptured(); + Vector2 robberLocation = robber.GetCustomPosition(); + robber.AddCaptured(robberLocation); if (CandR_NotifyRobbersWhenCaptured.GetBool()) { @@ -209,60 +540,255 @@ public static void OnCopAttack(PlayerControl cop, PlayerControl robber) if (!timesCaptured.ContainsKey(robber.PlayerId)) timesCaptured[robber.PlayerId] = 0; timesCaptured[robber.PlayerId]++; - - if (!points.ContainsKey(cop.PlayerId)) points[cop.PlayerId] = CandR_InitialPoints.GetInt(); - points[cop.PlayerId] += CandR_PointsForCapture.GetInt(); + CaptureCooldown(cop); cop.ResetKillCooldown(); + cop.SetKillCooldown(); } + public static void ApplyGameOptions(ref IGameOptions opt, PlayerControl player) + { + if (player.Is(CustomRoles.Cop) && CandR_CopAbilityTriggerChance.GetFloat() > 0f) + { + AURoleOptions.ShapeshifterCooldown = CandR_AbilityCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = CandR_CopAbilityDuration.GetFloat(); + } + if (scopeAltered.TryGetValue(player.PlayerId, out bool isAltered)) + { + if (!isAltered) + { + killDistance = opt.GetInt(Int32OptionNames.KillDistance) + CandR_ScopeIncrease.GetInt(); + scopeAltered[player.PlayerId] = true; + } + opt.SetInt(Int32OptionNames.KillDistance, killDistance); + } + if (flashTrigger.ContainsKey(player.PlayerId)) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, 0.25f); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0.25f); + Logger.Warn($"vision for {player.PlayerId} set to 0.25f", "flash vision"); + } + else + { + opt.SetVision(player.Is(CustomRoles.Cop)); + opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultImpostorVision); + } + return; + } + + public static void SetAbilityButtonText(HudManager hud, byte playerId) + { + if (playerId == byte.MaxValue) return; + PlayerControl player = Utils.GetPlayerById(playerId); + if (player == null) return; + if (player.Is(CustomRoles.Cop)) + { + hud.AbilityButton?.OverrideText(GetString("CopAbilityText")); + hud.KillButton?.OverrideText(GetString("CopKillButtonText")); + } + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] class FixedUpdateInGameModeCandRPatch { - public static void Postfix() + private static long LastChecked; + public static void Postfix(PlayerControl __instance) { if (!GameStates.IsInTask || Options.CurrentGameMode != CustomGameMode.CandR) return; if (!AmongUsClient.Instance.AmHost) return; + if (__instance.AmOwner) + { + if (Main.UnShapeShifter.Any(x => Utils.GetPlayerById(x) != null && Utils.GetPlayerById(x).CurrentOutfitType != PlayerOutfitType.Shapeshifted) + && !__instance.IsMushroomMixupActive() && Main.GameIsLoaded) + { + foreach (var UnShapeshifterId in Main.UnShapeShifter) + { + var UnShapeshifter = Utils.GetPlayerById(UnShapeshifterId); + if (UnShapeshifter == null) + { + Main.UnShapeShifter.Remove(UnShapeshifterId); + continue; + } + if (UnShapeshifter.CurrentOutfitType == PlayerOutfitType.Shapeshifted) continue; + + var randomPlayer = Main.AllPlayerControls.FirstOrDefault(x => x != UnShapeshifter); + UnShapeshifter.RpcShapeshift(randomPlayer, false); + UnShapeshifter.RpcRejectShapeshift(); + RoleType.Cop.SetCostume(UnShapeshifter.PlayerId); + Utils.NotifyRoles(SpecifyTarget: UnShapeshifter); + Logger.Info($"Revert to shapeshifting state for: {__instance.GetRealName()}", "UnShapeShifer_FixedUpdate"); + } + } + } + captured.Remove(byte.MaxValue); - if (!captured.Any()) return; robbers.Remove(byte.MaxValue); + captured.Remove(byte.MaxValue); + var now = Utils.GetTimeStamp(); - - Dictionary toRemove = []; - foreach (byte capturedId in captured) + Dictionary removeCaptured = []; + foreach (byte robberId in robbers) { - PlayerControl capturedPC = Utils.GetPlayerById(capturedId); - if (capturedPC == null) continue; + if (robberId == byte.MaxValue) continue; + PlayerControl robber = Utils.GetPlayerById(robberId); + if (robber == null) continue; + Vector2 currentRobberLocation = robber.GetCustomPosition(); - var capturedPos = capturedPC.transform.position; + // check if duration of trap is completed every second + if (now != LastChecked) + { + LastChecked = now; + // If Spike duration finished, reset the speed of trapped player + if (spikeTrigger.ContainsKey(robberId) && now - spikeTrigger[robberId] > CandR_SpikeStripDuration.GetFloat()) + { + Main.AllPlayerSpeed[robberId] = defaultSpeed[robberId]; + robber?.MarkDirtySettings(); + spikeTrigger.Remove(robberId); + } + // If flash duration finished, reset the vision of trapped player + if (flashTrigger.ContainsKey(robberId) && now - flashTrigger[robberId] > CandR_FlashBangDuration.GetFloat()) + { + flashTrigger.Remove(robberId); + robber.MarkDirtySettings(); + Logger.Warn($"Removed {robberId} from Flash trigger", "Flash remove"); + } + } - foreach (byte robberId in robbers) + // Check if captured release + if (captured.Any()) { - if (captured.Contains(robberId)) continue; - PlayerControl robberPC = Utils.GetPlayerById(robberId); - if (robberPC == null) continue; + foreach ((var captureId, Vector2 capturedLocation) in captured) + { + if (captureId == byte.MaxValue) continue; + PlayerControl capturedPC = Utils.GetPlayerById(captureId); + if (capturedPC == null) continue; + + if (captured.ContainsKey(robberId)) continue; // excluding all the captured players + + float dis = Utils.GetDistance(capturedLocation, currentRobberLocation); + if (dis < 0.3f) + { + removeCaptured[captureId] = robberId; + Logger.Info($"to remove captured {captureId}, rob: {robberId}", "C&R FixedUpdate"); + } + } + // remove capture players if possible + if (removeCaptured.Any()) + { + foreach ((byte rescued, byte saviour) in removeCaptured) + { + if (!saved.ContainsKey(saviour)) saved[saviour] = 0; + saved[saviour]++; + Utils.GetPlayerById(rescued).RemoveCaptured(); + } + removeCaptured.Clear(); + } + } - float dis = Vector2.Distance(capturedPos, robberPC.transform.position); - if (dis < 0.3f) + // Check if trap triggered + if (trapLocation.Any()) + { + foreach (KeyValuePair trap in trapLocation) { - toRemove[capturedId] = robberId; - Logger.Info($"to remove cap {capturedId}, rob: {robberId}", "to Remove fixupdate"); - break; + // captured player can not trigger a trap + if (captured.ContainsKey(robberId)) continue; + + // If player already trapped then continue + if (spikeTrigger.ContainsKey(robberId)) continue; + if (flashTrigger.ContainsKey(robberId)) continue; + + var trapDistance = Utils.GetDistance(trap.Key, currentRobberLocation); + // check for spike strip + if (trap.Value is CopAbility.SpikeStrip && trapDistance <= CandR_SpikeStripRadius.GetFloat()) + { + spikeTrigger[robberId] = now; + Main.AllPlayerSpeed[robberId] = Main.MinSpeed; + robber?.MarkDirtySettings(); + removeTrap.Add(trap.Key); // removed the trap from trap location because it was triggered + break; + } + // check for flash bang + if (trap.Value is CopAbility.FlashBang && trapDistance <= CandR_FlashBangRadius.GetFloat()) + { + flashTrigger[robberId] = now; + robber.MarkDirtySettings(); + Logger.Warn($"added {robberId} to flashtrigger", "Flash trigger"); + removeTrap.Add(trap.Key); + break; + } + } + if (removeTrap.Any()) + { + foreach (Vector2 removeTrapLoc in removeTrap) + trapLocation.Remove(removeTrapLoc); } } } - if (!toRemove.Any()) return; - foreach ((byte rescued, byte saviour) in toRemove) + cops.Remove(byte.MaxValue); + foreach (byte copId in cops) { - if (!points.ContainsKey(saviour)) points[saviour] = CandR_InitialPoints.GetInt(); - points[saviour] += CandR_PointsForRescue.GetInt(); - - Utils.GetPlayerById(rescued).RemoveCaptured(); + if (copId == byte.MaxValue) continue; + PlayerControl copPC = Utils.GetPlayerById(copId); + if (copPC == null) continue; + //check for radar + if (radar.ContainsKey(copId)) + { + PlayerControl closest = Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoles.Robber) && !captured.ContainsKey(pc.PlayerId)) + .MinBy(robberPC => Utils.GetDistance(copPC.GetCustomPosition(), robberPC.GetCustomPosition())); + if (closest == null) continue; + if (radar.TryGetValue(copId, out var targetId) && targetId != byte.MaxValue) + { + if (targetId != closest.PlayerId) + { + radar[copId] = closest.PlayerId; + SendCandRData(0, copId); + Logger.Info($"Set radar for {copId}, closest: {closest.PlayerId}", "Arrow Change"); + TargetArrow.Remove(copId, targetId); + TargetArrow.Add(copId, closest.PlayerId); + } + } + else + { + radar[copId] = closest.PlayerId; + SendCandRData(0, copId); + TargetArrow.Add(copId, closest.PlayerId); + Logger.Info($"Add radar for {copId}, closest: {closest.PlayerId}", "Arrow Change"); + } + } } + + //// below this only captured + //if (!captured.Any()) return; + + + + + //foreach (byte capturedId in captured) + //{ + // PlayerControl capturedPC = Utils.GetPlayerById(capturedId); + // if (capturedPC == null) continue; + + // var capturedPos = capturedPC.GetCustomPosition(); + + // foreach (byte robberId in robbers) + // { + // if (captured.Contains(robberId)) continue; + // PlayerControl robberPC = Utils.GetPlayerById(robberId); + // if (robberPC == null) continue; + + // float dis = Utils.GetDistance(capturedPos, robberPC.GetCustomPosition()); + + // } + //} + } } + } \ No newline at end of file diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index b46340952e..8952316888 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -56,6 +56,7 @@ public static RoleTypes GetDYRole(this CustomRoles role) // Role has a kill butt /* Needs recode, awaiting phantom role base*/ public static bool HasImpKillButton(this PlayerControl player, bool considerVanillaShift = false) { + if (Options.CurrentGameMode is CustomGameMode.CandR && player.Is(CustomRoles.Cop)) return true; if (player == null) return false; var customRole = player.GetCustomRole(); bool ModSideHasKillButton = customRole.GetDYRole() == RoleTypes.Impostor || customRole.GetVNRole() is CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom; diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 99fdc32d04..2b5a17170c 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1041,6 +1041,7 @@ public static bool HasKillButton(this PlayerControl pc) public static bool CanUseImpostorVentButton(this PlayerControl pc) { + if (Options.CurrentGameMode is CustomGameMode.CandR) return false; if (!pc.IsAlive()) return false; if (GameStates.IsHideNSeek) return true; if (pc.Is(CustomRoles.Killer) || pc.Is(CustomRoles.Nimble)) return true; @@ -1055,6 +1056,7 @@ public static bool CanUseImpostorVentButton(this PlayerControl pc) } public static bool CanUseSabotage(this PlayerControl pc) { + if (Options.CurrentGameMode is CustomGameMode.CandR) return false; if (pc.Is(Custom_Team.Impostor) && !pc.IsAlive() && Options.DeadImpCantSabotage.GetBool()) return false; var playerRoleClass = pc.GetRoleClass(); @@ -1067,57 +1069,67 @@ public static void ResetKillCooldown(this PlayerControl player) Main.AllPlayerKillCooldown[player.PlayerId] = Options.DefaultKillCooldown; // FFA - if (player.Is(CustomRoles.Killer)) + switch (Options.CurrentGameMode) { - Main.AllPlayerKillCooldown[player.PlayerId] = FFAManager.FFA_KCD.GetFloat(); - } - else - { - player.GetRoleClass()?.SetKillCooldown(player.PlayerId); - } + case CustomGameMode.FFA: + if (player.Is(CustomRoles.Killer)) + { + Main.AllPlayerKillCooldown[player.PlayerId] = FFAManager.FFA_KCD.GetFloat(); + } + break; + case CustomGameMode.CandR: + if (player.Is(CustomRoles.Cop)) + CopsAndRobbersManager.CaptureCooldown(player); + break; - var playerSubRoles = player.GetCustomSubRoles(); + default: + player.GetRoleClass()?.SetKillCooldown(player.PlayerId); - if (playerSubRoles.Any()) - foreach (var subRole in playerSubRoles) - { - switch (subRole) - { - case CustomRoles.LastImpostor when player.PlayerId == LastImpostor.currentId: - LastImpostor.SetKillCooldown(); - break; - case CustomRoles.Mare: - Main.AllPlayerKillCooldown[player.PlayerId] = Mare.KillCooldownInLightsOut.GetFloat(); - break; + var playerSubRoles = player.GetCustomSubRoles(); - case CustomRoles.Overclocked: - Main.AllPlayerKillCooldown[player.PlayerId] -= Main.AllPlayerKillCooldown[player.PlayerId] * (Overclocked.OverclockedReduction.GetFloat() / 100); - break; + if (playerSubRoles.Any()) + foreach (var subRole in playerSubRoles) + { + switch (subRole) + { + case CustomRoles.LastImpostor when player.PlayerId == LastImpostor.currentId: + LastImpostor.SetKillCooldown(); + break; - case CustomRoles.Diseased: - Diseased.IncreaseKCD(player); - break; + case CustomRoles.Mare: + Main.AllPlayerKillCooldown[player.PlayerId] = Mare.KillCooldownInLightsOut.GetFloat(); + break; - case CustomRoles.Antidote: - Antidote.ReduceKCD(player); - break; - } - } + case CustomRoles.Overclocked: + Main.AllPlayerKillCooldown[player.PlayerId] -= Main.AllPlayerKillCooldown[player.PlayerId] * (Overclocked.OverclockedReduction.GetFloat() / 100); + break; - if (!player.HasImpKillButton(considerVanillaShift: false)) - Main.AllPlayerKillCooldown[player.PlayerId] = 300f; + case CustomRoles.Diseased: + Diseased.IncreaseKCD(player); + break; - if (player.GetRoleClass() is Chronomancer ch) - { - ch.realcooldown = Main.AllPlayerKillCooldown[player.PlayerId]; - ch.SetCooldown(); - } + case CustomRoles.Antidote: + Antidote.ReduceKCD(player); + break; + } + } + if (!player.HasImpKillButton(considerVanillaShift: false)) + Main.AllPlayerKillCooldown[player.PlayerId] = 300f; - if (Main.AllPlayerKillCooldown[player.PlayerId] == 0) - { - Main.AllPlayerKillCooldown[player.PlayerId] = 0.3f; + if (player.GetRoleClass() is Chronomancer ch) + { + ch.realcooldown = Main.AllPlayerKillCooldown[player.PlayerId]; + ch.SetCooldown(); + } + + + if (Main.AllPlayerKillCooldown[player.PlayerId] == 0) + { + Main.AllPlayerKillCooldown[player.PlayerId] = 0.3f; + } + break; } } public static bool IsNonCrewSheriff(this PlayerControl sheriff) diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 77b422d3b8..b0d6103777 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -82,20 +82,26 @@ public override IGameOptions BuildGameOptions() opt.BlackOut(state.IsBlackOut); CustomRoles role = player.GetCustomRole(); - if (Options.CurrentGameMode == CustomGameMode.FFA) + switch (Options.CurrentGameMode) { - if (FFAManager.FFALowerVisionList.ContainsKey(player.PlayerId)) - { - opt.SetVision(true); - opt.SetFloat(FloatOptionNames.CrewLightMod, FFAManager.FFA_LowerVision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, FFAManager.FFA_LowerVision.GetFloat()); - } - else - { - opt.SetVision(true); - opt.SetFloat(FloatOptionNames.CrewLightMod, 1.25f); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, 1.25f); - } + case CustomGameMode.FFA: + if (FFAManager.FFALowerVisionList.ContainsKey(player.PlayerId)) + { + opt.SetVision(true); + opt.SetFloat(FloatOptionNames.CrewLightMod, FFAManager.FFA_LowerVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, FFAManager.FFA_LowerVision.GetFloat()); + } + else + { + opt.SetVision(true); + opt.SetFloat(FloatOptionNames.CrewLightMod, 1.25f); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, 1.25f); + } + break; + + case CustomGameMode.CandR: + CopsAndRobbersManager.ApplyGameOptions(ref opt, player); + break; } if (player.Is(Custom_Team.Impostor)) @@ -126,7 +132,7 @@ public override IGameOptions BuildGameOptions() state.taskState.hasTasks = Utils.HasTasks(player.Data, false); - if (Main.UnShapeShifter.Contains(player.PlayerId)) + if (Main.UnShapeShifter.Contains(player.PlayerId) && Options.CurrentGameMode != CustomGameMode.CandR) { AURoleOptions.ShapeshifterDuration = 1f; } diff --git a/Modules/ModUpdater.cs b/Modules/ModUpdater.cs index 43c97d11d5..710c3561d3 100644 --- a/Modules/ModUpdater.cs +++ b/Modules/ModUpdater.cs @@ -15,7 +15,8 @@ namespace TOHE; public class ModUpdater { //private static readonly string URL_2018k = "http://api.tohre.dev"; - private static readonly string URL_Github = "https://api.github.com/repos/0xDrMoe/TownofHost-Enhanced"; + //private static readonly string URL_Github = "https://api.github.com/repos/0xDrMoe/TownofHost-Enhanced"; + private static readonly string URL_Github = "https://api.github.com/repos/EnhancedNetwork/TownofHost-Enhanced"; //public static readonly string downloadTest = "https://github.com/Pietrodjaowjao/TOHEN-Contributions/releases/download/v123123123/TOHE.dll"; public static bool hasUpdate = false; //public static bool isNewer = false; diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index dda56e9805..72521a4f64 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -67,7 +67,7 @@ public static int GetGameModeInt(CustomGameMode mode) [ "Standard", "FFA", - "Cops&Robbers", + "C&R", diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index ff38d57a90..7f122038a4 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -28,6 +28,7 @@ public abstract class OptionItem public OptionFormat ValueFormat { get; protected set; } public CustomGameMode GameMode { get; protected set; } public CustomGameMode HideOptionInFFA { get; protected set; } + public CustomGameMode HideOptionInCandR { get; protected set; } public CustomGameMode HideOptionInHnS { get; protected set; } public bool IsHeader { get; protected set; } public bool IsHidden { get; protected set; } @@ -76,6 +77,7 @@ public OptionItem(int id, string name, int defaultValue, TabGroup tab, bool isSi ValueFormat = OptionFormat.None; GameMode = CustomGameMode.All; HideOptionInFFA = CustomGameMode.All; + HideOptionInCandR = CustomGameMode.All; HideOptionInHnS = CustomGameMode.All; IsHeader = false; IsHidden = false; @@ -130,6 +132,7 @@ public OptionItem Do(Action action) public OptionItem SetHidden(bool value) => Do(i => i.IsHidden = value); public OptionItem SetText(bool value) => Do(i => i.IsText = value); public OptionItem HideInFFA(CustomGameMode value = CustomGameMode.FFA) => Do(i => i.HideOptionInFFA = value); + public OptionItem HideInCandR(CustomGameMode value = CustomGameMode.CandR) => Do(i => i.HideOptionInCandR = value); //C&R public OptionItem HideInHnS(CustomGameMode value = CustomGameMode.HidenSeekTOHE) => Do(i => i.HideOptionInHnS = value); public OptionItem SetParent(OptionItem parent) => Do(i => diff --git a/Modules/RPC.cs b/Modules/RPC.cs index d204b4018b..23a358ea4e 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -109,6 +109,7 @@ enum CustomRPC : byte // 185/255 USED //FFA SyncFFAPlayer, SyncFFANameNotify, + SyncCandRData, } public enum Sounds { @@ -599,6 +600,9 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c case CustomRPC.SyncFFAPlayer: FFAManager.ReceiveRPCSyncFFAPlayer(reader); break; + case CustomRPC.SyncCandRData: + CopsAndRobbersManager.ReceiveCandRData(reader); + break; case CustomRPC.SyncAllPlayerNames: Main.AllPlayerNames.Clear(); Main.AllClientRealNames.Clear(); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 698d5f14d5..9e328cbf9e 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1965,6 +1965,9 @@ public static Task DoNotifyRoles(PlayerControl SpecifySeer = null, PlayerControl case CustomGameMode.FFA: SelfSuffix.Append(FFAManager.GetPlayerArrow(seer)); break; + case CustomGameMode.CandR: + SelfSuffix.Append(CopsAndRobbersManager.GetClosestArrow(seer, seer)); + break; } diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index e258f3c2cb..f5d8c23131 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1788,13 +1788,53 @@ public static bool GetRoleByName(string name, out CustomRoles role) } public static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) { - if (Options.CurrentGameMode == CustomGameMode.FFA) - { - Utils.SendMessage(GetString("ModeDescribe.FFA"), playerId); - return; - } role = role.Trim().ToLower(); if (role.StartsWith("/r")) _ = role.Replace("/r", string.Empty); + switch (Options.CurrentGameMode) + { + case CustomGameMode.FFA: + Utils.SendMessage(GetString("ModeDescribe.FFA"), playerId); + return; + case CustomGameMode.CandR: + var copName = GetString(CustomRoles.Cop.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); + var robberName = GetString(CustomRoles.Robber.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); + var Conf1 = new StringBuilder(); + + CustomRoles rl1; + OptionItem option; + if (role == copName) + { + rl1 = CustomRoles.Cop; + option = CopsAndRobbersManager.CopHeader; + } + else if (role == robberName) + { + rl1 = CustomRoles.Robber; + option = CopsAndRobbersManager.RobberHeader; + } + else + { + Utils.SendMessage(GetString("ModeDescribe.C&R"), playerId); + return; + } + + var description = rl1.GetInfoLong(); + var title1 = Utils.ColorString(Utils.GetRoleColor(rl1), GetString($"{rl1}")); + string rlHex1 = Utils.GetRoleColorCode(rl1); + //Conf1.Append($"{option.GetName(true)}: {option.GetString()}\n"); + Utils.ShowChildrenSettings(option, ref Conf1); + var cleared1 = Conf1.ToString(); + var Setting1 = $"{GetString(rl1.ToString())} {GetString("Settings:")}\n"; + Conf1.Clear().Append($"{Setting1}{cleared1}"); + + // Show role info + Utils.SendMessage(description, playerId, title1, noReplay: true); + + // Show role settings + Utils.SendMessage("", playerId, Conf1.ToString(), noReplay: true); + return; + } + if (role.StartsWith("/up")) _ = role.Replace("/up", string.Empty); if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 3cca7747d9..d0a3466616 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -690,7 +690,7 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) { if (copsAlive && !allCaptured) break; if (pc.Is(CustomRoles.Cop)) copsAlive = true; - else if (pc.Is(CustomRoles.Robber) && !CopsAndRobbersManager.captured.Contains(pc.PlayerId)) allCaptured = false; + else if (pc.Is(CustomRoles.Robber) && !CopsAndRobbersManager.captured.ContainsKey(pc.PlayerId)) allCaptured = false; } // no cops left diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index c3f6d534ed..c792beff97 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -80,6 +80,9 @@ public static void Postfix(HudManager __instance) player.GetRoleClass()?.SetAbilityButtonText(__instance, player.PlayerId); + if (Options.CurrentGameMode is CustomGameMode.CandR) + CopsAndRobbersManager.SetAbilityButtonText(__instance, player.PlayerId); + // Set lower info text for modded players if (LowerInfoText == null) { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index d7f4d53984..9091e38afb 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -119,30 +119,40 @@ public static void Postfix(IntroCutscene __instance) { PlayerControl localPlayer = PlayerControl.LocalPlayer; CustomRoles role = localPlayer.GetCustomRole(); - if (Options.CurrentGameMode == CustomGameMode.FFA) + switch (Options.CurrentGameMode) { - var color = ColorUtility.TryParseHtmlString("#00ffff", out var c) ? c : new(255, 255, 255, 255); - __instance.YouAreText.transform.gameObject.SetActive(false); - __instance.RoleText.text = "FREE FOR ALL"; - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = "KILL EVERYONE TO WIN"; - } - else - { - if (!role.IsVanilla()) - { + case CustomGameMode.FFA: //ffa + var color = ColorUtility.TryParseHtmlString("#00ffff", out var c) ? c : new(255, 255, 255, 255); + __instance.YouAreText.transform.gameObject.SetActive(false); + __instance.RoleText.text = GetString("FFA"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("KillerInfo"); + break; + + case CustomGameMode.CandR: //C&R __instance.YouAreText.color = Utils.GetRoleColor(role); __instance.RoleText.text = Utils.GetRoleName(role); __instance.RoleText.color = Utils.GetRoleColor(role); __instance.RoleBlurbText.color = Utils.GetRoleColor(role); __instance.RoleBlurbText.text = localPlayer.GetRoleInfo(); - } + break; - foreach (var subRole in Main.PlayerStates[localPlayer.PlayerId].SubRoles.ToArray()) - __instance.RoleBlurbText.text += "\n" + Utils.ColorString(Utils.GetRoleColor(subRole), GetString($"{subRole}Info")); + default: + if (!role.IsVanilla()) + { + __instance.YouAreText.color = Utils.GetRoleColor(role); + __instance.RoleText.text = Utils.GetRoleName(role); + __instance.RoleText.color = Utils.GetRoleColor(role); + __instance.RoleBlurbText.color = Utils.GetRoleColor(role); + __instance.RoleBlurbText.text = localPlayer.GetRoleInfo(); + } + + foreach (var subRole in Main.PlayerStates[localPlayer.PlayerId].SubRoles.ToArray()) + __instance.RoleBlurbText.text += "\n" + Utils.ColorString(Utils.GetRoleColor(subRole), GetString($"{subRole}Info")); - __instance.RoleText.text += Utils.GetSubRolesText(localPlayer.PlayerId, false, true); + __instance.RoleText.text += Utils.GetSubRolesText(localPlayer.PlayerId, false, true); + break; } }, 0.0001f, "Override Role Text"); @@ -342,129 +352,166 @@ public static void Postfix(IntroCutscene __instance) CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); __instance.ImpostorText.gameObject.SetActive(false); - - switch (role.GetCustomRoleTeam()) + if (role is CustomRoles.GM) { - case Custom_Team.Impostor: - __instance.TeamTitle.text = GetString("TeamImpostor"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Impostor"); - break; - case Custom_Team.Crewmate: - __instance.TeamTitle.text = GetString("TeamCrewmate"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(140, 255, 255, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Crewmate"); - break; - case Custom_Team.Neutral: - __instance.TeamTitle.text = GetString("TeamNeutral"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(127, 140, 141, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Neutral"); - break; + __instance.TeamTitle.text = Utils.GetRoleName(role); + __instance.TeamTitle.color = Utils.GetRoleColor(role); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); + __instance.ImpostorText.gameObject.SetActive(false); + PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.TaskCompleteSound; } - switch (role) + switch (Options.CurrentGameMode) { - case CustomRoles.ShapeMaster: - case CustomRoles.ShapeshifterTOHE: + case CustomGameMode.FFA: //FFA + __instance.TeamTitle.text = GetString("FFA"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(0, 255, 255, byte.MaxValue); PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("KillerInfo"); break; - case CustomRoles.PhantomTOHE: - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Phantom); - break; - case CustomRoles.TrackerTOHE: - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Tracker); - break; - case CustomRoles.NoisemakerTOHE: - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Noisemaker); - break; - case CustomRoles.EngineerTOHE: - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Engineer); - break; - case CustomRoles.Doctor: - case CustomRoles.Medic: - case CustomRoles.ScientistTOHE: - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Scientist); - break; - - case CustomRoles.Terrorist: - case CustomRoles.Bomber: - var sound = ShipStatus.Instance.CommonTasks.FirstOrDefault(task => task.TaskType == TaskTypes.FixWiring) - .MinigamePrefab.OpenSound; - PlayerControl.LocalPlayer.Data.Role.IntroSound = sound; - break; - - case CustomRoles.Workaholic: - case CustomRoles.Snitch: - case CustomRoles.TaskManager: - PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.TaskCompleteSound; - break; - - case CustomRoles.Opportunist: - case CustomRoles.Hater: - case CustomRoles.Revolutionist: - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); + case CustomGameMode.CandR: //C&R + __instance.TeamTitle.text = $"{GetString("C&R")}"; + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("C&RShortInfo"); + __instance.TeamTitle.color = Color.blue; + __instance.BackgroundBar.material.color = Color.blue; + StartFadeIntro(__instance, Color.blue, Color.red, changeInterval: 400); + switch (role) + { + case CustomRoles.Cop: + PlayerControl.LocalPlayer.Data.Role.IntroSound = ShipStatus.Instance.SabotageSound; + break; + case CustomRoles.Robber: + PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.TaskCompleteSound; + break; + } break; - case CustomRoles.Addict: - case CustomRoles.Ventguard: - PlayerControl.LocalPlayer.Data.Role.IntroSound = ShipStatus.Instance.VentEnterSound; - break; + default: - case CustomRoles.Saboteur: - case CustomRoles.Inhibitor: - case CustomRoles.Mechanic: - case CustomRoles.Provocateur: - PlayerControl.LocalPlayer.Data.Role.IntroSound = ShipStatus.Instance.SabotageSound; - break; + switch (role.GetCustomRoleTeam()) + { + case Custom_Team.Impostor: + __instance.TeamTitle.text = GetString("TeamImpostor"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Impostor"); + break; + case Custom_Team.Crewmate: + __instance.TeamTitle.text = GetString("TeamCrewmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(140, 255, 255, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Crewmate"); + break; + case Custom_Team.Neutral: + __instance.TeamTitle.text = GetString("TeamNeutral"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(127, 140, 141, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Neutral"); + break; + } + switch (role) + { + case CustomRoles.ShapeMaster: + case CustomRoles.ShapeshifterTOHE: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + break; + case CustomRoles.PhantomTOHE: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Phantom); + break; + case CustomRoles.TrackerTOHE: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Tracker); + break; + case CustomRoles.NoisemakerTOHE: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Noisemaker); + break; + case CustomRoles.EngineerTOHE: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Engineer); + break; + case CustomRoles.Doctor: + case CustomRoles.Medic: + case CustomRoles.ScientistTOHE: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Scientist); + break; + + case CustomRoles.Terrorist: + case CustomRoles.Bomber: + var sound = ShipStatus.Instance.CommonTasks.FirstOrDefault(task => task.TaskType == TaskTypes.FixWiring) + .MinigamePrefab.OpenSound; + PlayerControl.LocalPlayer.Data.Role.IntroSound = sound; + break; + + case CustomRoles.Workaholic: + case CustomRoles.Snitch: + case CustomRoles.TaskManager: + PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.TaskCompleteSound; + break; + + case CustomRoles.Opportunist: + case CustomRoles.Hater: + case CustomRoles.Revolutionist: + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); + break; + + case CustomRoles.Addict: + case CustomRoles.Ventguard: + PlayerControl.LocalPlayer.Data.Role.IntroSound = ShipStatus.Instance.VentEnterSound; + break; + + case CustomRoles.Saboteur: + case CustomRoles.Inhibitor: + case CustomRoles.Mechanic: + case CustomRoles.Provocateur: + PlayerControl.LocalPlayer.Data.Role.IntroSound = ShipStatus.Instance.SabotageSound; + break; + + case CustomRoles.GM: + __instance.TeamTitle.text = Utils.GetRoleName(role); + __instance.TeamTitle.color = Utils.GetRoleColor(role); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); + __instance.ImpostorText.gameObject.SetActive(false); + PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.TaskCompleteSound; + break; + + case CustomRoles.Sheriff: + case CustomRoles.Veteran: + case CustomRoles.Knight: + case CustomRoles.KillingMachine: + case CustomRoles.Reverie: + case CustomRoles.NiceGuesser: + case CustomRoles.Vigilante: + PlayerControl.LocalPlayer.Data.Role.IntroSound = PlayerControl.LocalPlayer.KillSfx; + break; + case CustomRoles.Swooper: + case CustomRoles.Wraith: + case CustomRoles.Chameleon: + PlayerControl.LocalPlayer.Data.Role.IntroSound = PlayerControl.LocalPlayer.MyPhysics.ImpostorDiscoveredSound; + break; + } - case CustomRoles.GM: - __instance.TeamTitle.text = Utils.GetRoleName(role); - __instance.TeamTitle.color = Utils.GetRoleColor(role); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); - __instance.ImpostorText.gameObject.SetActive(false); - PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.TaskCompleteSound; - break; + if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || role.IsMadmate()) + { + __instance.TeamTitle.text = GetString("TeamMadmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Madmate"); + } - case CustomRoles.Sheriff: - case CustomRoles.Veteran: - case CustomRoles.Knight: - case CustomRoles.KillingMachine: - case CustomRoles.Reverie: - case CustomRoles.NiceGuesser: - case CustomRoles.Vigilante: - PlayerControl.LocalPlayer.Data.Role.IntroSound = PlayerControl.LocalPlayer.KillSfx; - break; - case CustomRoles.Swooper: - case CustomRoles.Wraith: - case CustomRoles.Chameleon: - PlayerControl.LocalPlayer.Data.Role.IntroSound = PlayerControl.LocalPlayer.MyPhysics.ImpostorDiscoveredSound; + if (Options.CurrentGameMode == CustomGameMode.FFA) + { + __instance.TeamTitle.text = "FREE FOR ALL"; + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(0, 255, 255, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = "KILL EVERYONE TO WIN"; + } break; } - - if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || role.IsMadmate()) - { - __instance.TeamTitle.text = GetString("TeamMadmate"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Madmate"); - } - - if (Options.CurrentGameMode == CustomGameMode.FFA) - { - __instance.TeamTitle.text = "FREE FOR ALL"; - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(0, 255, 255, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = "KILL EVERYONE TO WIN"; - } - - // I hope no one notices this in code + // I hope no one notices this in code if (Input.GetKey(KeyCode.RightShift)) { __instance.TeamTitle.text = "Damn!!"; @@ -486,14 +533,14 @@ public static AudioClip GetIntroSound(RoleTypes roleType) { return RoleManager.Instance.AllRoles.FirstOrDefault((role) => role.Role == roleType)?.IntroSound; } - private static async void StartFadeIntro(IntroCutscene __instance, Color start, Color end) + private static async void StartFadeIntro(IntroCutscene __instance, Color start, Color end, int changeInterval = 20) { await Task.Delay(1000); int milliseconds = 0; while (true) { - await Task.Delay(20); - milliseconds += 20; + await Task.Delay(changeInterval); + milliseconds += changeInterval; float time = milliseconds / (float)500; Color LerpingColor = Color.Lerp(start, end, time); if (__instance == null || milliseconds > 500) @@ -582,7 +629,8 @@ public static void Prefix() var firstPlayer = Main.AllPlayerControls.FirstOrDefault(x => x != PC); PC.RpcShapeshift(firstPlayer, false); PC.RpcRejectShapeshift(); - PC.ResetPlayerOutfit(force: true); + if (Options.CurrentGameMode is CustomGameMode.CandR) CopsAndRobbersManager.SetCostume(CopsAndRobbersManager.RoleType.Cop, PC.PlayerId); + else PC.ResetPlayerOutfit(force: true); Main.CheckShapeshift[x] = false; }); Main.GameIsLoaded = true; @@ -661,6 +709,7 @@ public static void Postfix() bool chatVisible = Options.CurrentGameMode switch { CustomGameMode.FFA => true, + CustomGameMode.CandR => true, _ => false }; try diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index eb2a445424..c686f11c34 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -570,7 +570,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC bool resetCooldown = true; logger.Info($"Self:{shapeshifter.PlayerId == target.PlayerId} - Is animate:{shouldAnimate} - In Meeting:{GameStates.IsMeeting}"); - + var shapeshifterRoleClass = shapeshifter.GetRoleClass(); if (shapeshifterRoleClass?.OnCheckShapeshift(shapeshifter, target, ref resetCooldown, ref shouldAnimate) == false) { @@ -632,25 +632,38 @@ private static bool CheckInvalidShapeshifting(PlayerControl instance, PlayerCont Logger.Info("Checking while AntiBlackOut protect, shapeshift was canceled", "CheckShapeshift"); return false; } - if (!(instance.Is(CustomRoles.ShapeshifterTOHE) || instance.Is(CustomRoles.Shapeshifter)) && target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) + if (Options.CurrentGameMode is CustomGameMode.CandR) { - instance.RpcGuardAndKill(instance); - instance.Notify(Utils.ColorString(Utils.GetRoleColor(instance.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); - logger.Info($"Cancel shapeshifting because {target.GetRealName()} is protected by the game"); - return false; + if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) + { + if (!instance.IsMushroomMixupActive() && !GameStates.IsMeeting) CopsAndRobbersManager.UnShapeShiftButton(instance); + instance.RpcResetAbilityCooldown(); // Just incase + logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); + return false; + } } - if (Pelican.IsEaten(instance.PlayerId)) + else { - logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is eaten by Pelican"); - return false; - } + if (!(instance.Is(CustomRoles.ShapeshifterTOHE) || instance.Is(CustomRoles.Shapeshifter)) && target.GetClient().GetHashedPuid() == Main.FirstDiedPrevious && MeetingStates.FirstMeeting) + { + instance.RpcGuardAndKill(instance); + instance.Notify(Utils.ColorString(Utils.GetRoleColor(instance.GetCustomRole()), GetString("PlayerIsShieldedByGame"))); + logger.Info($"Cancel shapeshifting because {target.GetRealName()} is protected by the game"); + return false; + } + if (Pelican.IsEaten(instance.PlayerId)) + { + logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is eaten by Pelican"); + return false; + } - if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) - { - if (!instance.IsMushroomMixupActive() && !GameStates.IsMeeting) instance.GetRoleClass().UnShapeShiftButton(instance); - instance.RpcResetAbilityCooldown(); // Just incase - logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); - return false; + if (instance == target && Main.UnShapeShifter.Contains(instance.PlayerId)) + { + if (!instance.IsMushroomMixupActive() && !GameStates.IsMeeting) instance.GetRoleClass().UnShapeShiftButton(instance); + instance.RpcResetAbilityCooldown(); // Just incase + logger.Info($"Cancel shapeshifting because {instance.GetRealName()} is using un-shapeshift ability button"); + return false; + } } return true; } @@ -1330,9 +1343,15 @@ public static Task DoPostfix(PlayerControl __instance) } - if (Options.CurrentGameMode == CustomGameMode.FFA) - Suffix.Append(FFAManager.GetPlayerArrow(seer, target)); - + switch (Options.CurrentGameMode) + { + case CustomGameMode.FFA: + Suffix.Append(FFAManager.GetPlayerArrow(seer, target)); + break; + case CustomGameMode.CandR: + Suffix.Append(CopsAndRobbersManager.GetClosestArrow(seer, target)); + break; + } /*if(main.AmDebugger.Value && main.BlockKilling.TryGetValue(target.PlayerId, out var isBlocked)) { Mark = isBlocked ? "(true)" : "(false)";}*/ diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 5a5a8cb630..b2a5695a5a 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -506,9 +506,9 @@ public static System.Collections.IEnumerator AssignRoles() case CustomGameMode.FFA: GameEndCheckerForNormal.SetPredicateToFFA(); break; - //case CustomGameMode.CandR: - // GameEndCheckerForNormal.SetPredicateToCandR(); - // break; + case CustomGameMode.CandR: + GameEndCheckerForNormal.SetPredicateToCandR(); + break; } EAC.LogAllRoles(); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 05f05f02c9..dde70c93ef 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3616,7 +3616,9 @@ "ModeC&R": "Gamemode: Cops And Robbers", "C&R_NumCops": "Number of Cops", "C&R_CopAbilityTriggerChance": "Ability trigger chance", + "C&R_CaptureCooldown": "Capture Cooldown", "C&R_CopAbilityDuration": "Ability Duration", + "C&R_AbilityCooldown": "Ability Cooldown", "C&R_HotPursuitChance": "Hot Pursuit activation chance", "C&R_HotPursuitSpeed": "Increase speed by", "C&R_SpikeStripChance": "Spike Strip activation chance", @@ -3634,6 +3636,8 @@ "CopAbility.FlashBang": "Flash Bang", "CopAbility.Radar": "Radar", "CopAbility.Scope": "Scope", + "CopAbilityText": "Ability", + "CopKillButtonText": "Capture", "C&R_NotifyRobbersWhenCaptured": "Notify Robbers when captured", "C&R_InitialPoints": "Initial points", diff --git a/Roles/Core/AssignManager/RoleAssign.cs b/Roles/Core/AssignManager/RoleAssign.cs index 0951ec05f7..1dcab18bfd 100644 --- a/Roles/Core/AssignManager/RoleAssign.cs +++ b/Roles/Core/AssignManager/RoleAssign.cs @@ -61,10 +61,6 @@ public static void StartSelect() case CustomGameMode.CandR: RoleResult = []; RoleResult = CopsAndRobbersManager.SetRoles(); - foreach ((byte playerId, CustomRoles rl) in RoleResult) - { - Logger.Warn($"set role for {playerId}: {rl}", "Start select"); - } return; } From 2649a9ac6db7cca2eff120bb70944f605d8cd510 Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Sat, 28 Sep 2024 01:27:25 +0530 Subject: [PATCH 03/12] fix conflict bug --- Modules/ExtendedPlayerControl.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 7c8147f1df..2e69554362 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1117,9 +1117,10 @@ public static void ResetKillCooldown(this PlayerControl player) break; } } - - if (!player.HasImpKillButton(considerVanillaShift: false)) - Main.AllPlayerKillCooldown[player.PlayerId] = 300f; + break; + } + if (!player.HasImpKillButton(considerVanillaShift: false)) + Main.AllPlayerKillCooldown[player.PlayerId] = 300f; if (Main.AllPlayerKillCooldown[player.PlayerId] == 0) { From 37ce095a963288f78fa893bf82b0f00355fed24f Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Sat, 28 Sep 2024 01:57:46 +0530 Subject: [PATCH 04/12] rename ability radar to k9 --- GameModes/CopsAndRobbersManager.cs | 54 ++++++++++++++---------------- Resources/Lang/en_US.json | 10 ++---- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index df88d2b686..2457d798ee 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -2,9 +2,7 @@ using static TOHE.Translator; using UnityEngine; using Hazel; -using static UnityEngine.GraphicsBuffer; using TOHE.Modules; -using MS.Internal.Xml.XPath; namespace TOHE; @@ -27,7 +25,7 @@ internal static class CopsAndRobbersManager private static readonly HashSet removeTrap = []; private static readonly Dictionary spikeTrigger = []; private static readonly Dictionary flashTrigger = []; - private static readonly Dictionary radar = []; + private static readonly Dictionary k9 = []; private static readonly Dictionary scopeAltered = []; private static int killDistance; @@ -48,7 +46,7 @@ internal static class CopsAndRobbersManager private static OptionItem CandR_FlashBangChance; private static OptionItem CandR_FlashBangRadius; private static OptionItem CandR_FlashBangDuration; - private static OptionItem CandR_RadarChance; + private static OptionItem CandR_K9Chance; private static OptionItem CandR_ScopeChance; private static OptionItem CandR_ScopeIncrease; public static OptionItem CopHeader; @@ -132,7 +130,7 @@ public static void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_FlashBangChance); - CandR_RadarChance = IntegerOptionItem.Create(Id + 14, "C&R_RadarChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + CandR_K9Chance = IntegerOptionItem.Create(Id + 14, "C&R_K9Chance", new(0, 100, 5), 20, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) @@ -174,7 +172,7 @@ private enum CopAbility HotPursuit, // Speed boost for a set amount of time. SpikeStrip, // Sets a trap that slows down the robber. FlashBang, // Sets a trap that blinds the robber temporarily. - Radar, // Points to the closest robber. + K9, // Points to the closest robber. Scope // Increases the capture range. } @@ -204,7 +202,7 @@ private static void CopCumulativeChances() {CopAbility.HotPursuit, CandR_HotPursuitChance }, {CopAbility.SpikeStrip, CandR_SpikeStripChance }, {CopAbility.FlashBang, CandR_FlashBangChance }, - {CopAbility.Radar, CandR_RadarChance }, + {CopAbility.K9, CandR_K9Chance }, {CopAbility.Scope, CandR_ScopeChance }, }; copAbilityChances.Clear(); @@ -234,7 +232,7 @@ public static void Init() removeTrap.Clear(); spikeTrigger.Clear(); flashTrigger.Clear(); - radar.Clear(); + k9.Clear(); scopeAltered.Clear(); killDistance = Main.RealOptionsData.GetInt(Int32OptionNames.KillDistance); CopCumulativeChances(); @@ -371,7 +369,7 @@ private static void SendCandRData(byte op, byte copId) { case 0: writer.Write(copId); - writer.Write(radar[copId]); + writer.Write(k9[copId]); break; case 1: writer.Write(copId); @@ -387,12 +385,12 @@ public static void ReceiveCandRData(MessageReader reader) { case 0: byte copId = reader.ReadByte(); - byte radarId = reader.ReadByte(); - radar[copId] = radarId; + byte k9Id = reader.ReadByte(); + k9[copId] = k9Id; break; case 1: byte removeCopId = reader.ReadByte(); - radar.Remove(removeCopId); + k9.Remove(removeCopId); break; } @@ -428,10 +426,10 @@ private static void DeactivateCopAbility(this PlayerControl cop, CopAbility? abi case CopAbility.FlashBang: //it might also be removed in fixed update when trap triggered. trapLocation.Remove(loc); break; - case CopAbility.Radar: - byte targetId = radar[cop.PlayerId]; - Logger.Info($"Removed radar for {cop.PlayerId}", "Remove radar"); - radar.Remove(cop.PlayerId); + case CopAbility.K9: + byte targetId = k9[cop.PlayerId]; + Logger.Info($"Removed k9 for {cop.PlayerId}", "Remove k9"); + k9.Remove(cop.PlayerId); SendCandRData(1, cop.PlayerId); TargetArrow.Remove(cop.PlayerId, targetId); break; @@ -463,12 +461,12 @@ private static void ActivateCopAbility(this PlayerControl cop, CopAbility? abili trapLocation.Add(loc, ability); break; - case CopAbility.Radar: - if (radar.ContainsKey(cop.PlayerId)) + case CopAbility.K9: + if (k9.ContainsKey(cop.PlayerId)) return; - radar.Add(cop.PlayerId, byte.MaxValue); + k9.Add(cop.PlayerId, byte.MaxValue); SendCandRData(0, cop.PlayerId); - Logger.Info($"Added {cop.PlayerId} for radar", "Ability activated"); + Logger.Info($"Added {cop.PlayerId} for k9", "Ability activated"); break; case CopAbility.Scope: if (scopeAltered.ContainsKey(cop.PlayerId)) @@ -503,7 +501,7 @@ public static void UnShapeShiftButton(PlayerControl shapeshifter) } public static string GetClosestArrow(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (isForMeeting || !radar.ContainsKey(seer.PlayerId) || seer.PlayerId != target.PlayerId) return string.Empty; + if (isForMeeting || !k9.ContainsKey(seer.PlayerId) || seer.PlayerId != target.PlayerId) return string.Empty; return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cop), TargetArrow.GetArrows(seer)); } public static void OnCopAttack(PlayerControl cop, PlayerControl robber) @@ -737,29 +735,29 @@ public static void Postfix(PlayerControl __instance) if (copId == byte.MaxValue) continue; PlayerControl copPC = Utils.GetPlayerById(copId); if (copPC == null) continue; - //check for radar - if (radar.ContainsKey(copId)) + //check for k9 + if (k9.ContainsKey(copId)) { PlayerControl closest = Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoles.Robber) && !captured.ContainsKey(pc.PlayerId)) .MinBy(robberPC => Utils.GetDistance(copPC.GetCustomPosition(), robberPC.GetCustomPosition())); if (closest == null) continue; - if (radar.TryGetValue(copId, out var targetId) && targetId != byte.MaxValue) + if (k9.TryGetValue(copId, out var targetId) && targetId != byte.MaxValue) { if (targetId != closest.PlayerId) { - radar[copId] = closest.PlayerId; + k9[copId] = closest.PlayerId; SendCandRData(0, copId); - Logger.Info($"Set radar for {copId}, closest: {closest.PlayerId}", "Arrow Change"); + Logger.Info($"Set k9 for {copId}, closest: {closest.PlayerId}", "Arrow Change"); TargetArrow.Remove(copId, targetId); TargetArrow.Add(copId, closest.PlayerId); } } else { - radar[copId] = closest.PlayerId; + k9[copId] = closest.PlayerId; SendCandRData(0, copId); TargetArrow.Add(copId, closest.PlayerId); - Logger.Info($"Add radar for {copId}, closest: {closest.PlayerId}", "Arrow Change"); + Logger.Info($"Add k9 for {copId}, closest: {closest.PlayerId}", "Arrow Change"); } } } diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 7413ed298d..2f263b0181 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3628,25 +3628,21 @@ "C&R_FlashBangChance": "Flash Bang activation chance", "C&R_FlashBangRadius": "Flash Bang radius", "C&R_FlashBangDuration": "Flash Bang duration", - "C&R_RadarChance": "Radar chance", + "C&R_K9Chance": "K9 chance", "C&R_ScopeChance": "Scope chance", "C&R_ScopeIncrease": "Scope Increase", "C&R_CopAbilityActivated": "{Ability.Name} activated", "CopAbility.HotPursuit": "Hot Pursuit", "CopAbility.SpikeStrip": "Spike Strip", "CopAbility.FlashBang": "Flash Bang", - "CopAbility.Radar": "Radar", + "CopAbility.K9": "K9", "CopAbility.Scope": "Scope", "CopAbilityText": "Ability", "CopKillButtonText": "Capture", - "C&R_NotifyRobbersWhenCaptured": "Notify Robbers when captured", - "C&R_InitialPoints": "Initial points", - "C&R_PointsForCapture": "Points for capture", - "C&R_PointsForRescue": "Points for rescue", "Cop": "Cop", "CopInfo": "Capture all Robbers to win", - "CopInfoLong": "As a cop, use your kill button to capture robbers before they finish their tasks. When caught, robbers freeze in place. Depending on the settings, repeated captures may send them to jail.\n\n Each capture earns you points, which you can spend to activate powerful abilities for a limited duration.\nYou can trigger a random ability with the shapeshift button or use chat commands for activating specific ability\u2014just enter \"/ability cop\" to view all your options.\n\nTrack down the robbers and secure your win!", + "CopInfoLong": "As a cop, use your kill button to capture robbers before they finish their tasks. When caught, robbers freeze in place.\n\nYou can trigger a random ability with the shapeshift button.\n Use/ability cop to check all the abilities.\n\nTrack down the robbers and secure your win!", "FFA": "Free For All", From 2d2ad86478e79954b4833bd82e75ed49dd692777 Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Wed, 2 Oct 2024 00:12:21 +0530 Subject: [PATCH 05/12] robbers with abilities --- GameModes/CopsAndRobbersManager.cs | 491 +++++++++++++++++++++++------ Modules/CustomRolesHelper.cs | 6 +- Modules/ExtendedPlayerControl.cs | 7 +- Patches/ChatCommandPatch.cs | 31 +- Patches/PlayerControlPatch.cs | 4 + Patches/onGameStartedPatch.cs | 17 +- Resources/Lang/en_US.json | 35 +- 7 files changed, 457 insertions(+), 134 deletions(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index 2457d798ee..a8d484f88f 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -20,7 +20,8 @@ internal static class CopsAndRobbersManager private static readonly Dictionary defaultSpeed = []; private static readonly Dictionary copAbilityChances = []; - private static readonly Dictionary RemoveAbility = []; + private static readonly Dictionary robberAbilityChances = []; + private static readonly Dictionary RemoveCopAbility = []; private static readonly Dictionary trapLocation = []; private static readonly HashSet removeTrap = []; private static readonly Dictionary spikeTrigger = []; @@ -28,15 +29,22 @@ internal static class CopsAndRobbersManager private static readonly Dictionary k9 = []; private static readonly Dictionary scopeAltered = []; private static int killDistance; + public static Dictionary> roleSettings = []; + private static readonly Dictionary RemoveRobberAbility = []; + private static readonly HashSet energyShieldActive = []; + private static readonly HashSet smokeBombActive = []; + private static readonly Dictionary smokeBombTriggered = []; + private static readonly HashSet adrenalineRushActive = []; + private static readonly Dictionary radar = []; + private static readonly HashSet disguise = []; private static int numCops; public static OptionItem CandR_NumCops; - private static OptionItem CandR_NotifyRobbersWhenCaptured; private static OptionItem CandR_CaptureCooldown; private static OptionItem CandR_CopAbilityTriggerChance; - private static OptionItem CandR_AbilityCooldown; + private static OptionItem CandR_CopAbilityCooldown; private static OptionItem CandR_CopAbilityDuration; private static OptionItem CandR_HotPursuitChance; private static OptionItem CandR_HotPursuitSpeed; @@ -49,14 +57,27 @@ internal static class CopsAndRobbersManager private static OptionItem CandR_K9Chance; private static OptionItem CandR_ScopeChance; private static OptionItem CandR_ScopeIncrease; - public static OptionItem CopHeader; - //public static OptionItem CopActiveHidden; - public static OptionItem RobberHeader; + + + + private static OptionItem CandR_NotifyRobbersWhenCaptured; + private static OptionItem CandR_RobberVentDuration; + private static OptionItem CandR_RobberVentCooldown; + private static OptionItem CandR_RobberAbilityDuration; + private static OptionItem CandR_RobberAbilityTriggerChance; + private static OptionItem CandR_AdrenalineRushChance; + private static OptionItem CandR_AdrenalineRushSpeed; + private static OptionItem CandR_EnergyShieldChance; + private static OptionItem CandR_SmokeBombChance; + private static OptionItem CandR_SmokeBombDuration; + private static OptionItem CandR_DisguiseChance; + private static OptionItem CandR_RadarChance; public static void SetupCustomOption() { - CopHeader = TextOptionItem.Create(Id-1, "Cop", TabGroup.ModSettings) + /*********** Cops ***********/ + TextOptionItem.Create(Id, "Cop", TabGroup.ModSettings) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)); @@ -79,7 +100,7 @@ public static void SetupCustomOption() .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_CopAbilityTriggerChance); - CandR_AbilityCooldown = FloatOptionItem.Create(Id + 5, "C&R_AbilityCooldown", new(10f, 60f, 2.5f), 20f, TabGroup.ModSettings, false) + CandR_CopAbilityCooldown = FloatOptionItem.Create(Id + 5, "C&R_CopAbilityCooldown", new(10f, 60f, 2.5f), 20f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) @@ -130,27 +151,95 @@ public static void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_FlashBangChance); - CandR_K9Chance = IntegerOptionItem.Create(Id + 14, "C&R_K9Chance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + CandR_ScopeChance = IntegerOptionItem.Create(Id + 14, "C&R_ScopeChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); + CandR_ScopeIncrease = IntegerOptionItem.Create(Id + 15, "C&R_ScopeIncrease", new(1, 5, 1), 1, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier) + .SetParent(CandR_ScopeChance); - CandR_ScopeChance = IntegerOptionItem.Create(Id + 15, "C&R_ScopeChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) + CandR_K9Chance = IntegerOptionItem.Create(Id + 16, "C&R_K9Chance", new(0, 100, 5), 20, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_ScopeIncrease = IntegerOptionItem.Create(Id + 16, "C&R_ScopeIncrease", new(1, 5, 1), 1, TabGroup.ModSettings, false) + + roleSettings[CustomRoles.Cop] = [CandR_NumCops, CandR_CaptureCooldown, CandR_CopAbilityTriggerChance]; + + /*********** Robbers ***********/ + + TextOptionItem.Create(Id + 20, "Robber", TabGroup.ModSettings) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)); + + CandR_NotifyRobbersWhenCaptured = BooleanOptionItem.Create(Id + 21, "C&R_NotifyRobbersWhenCaptured", true, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)); + + CandR_RobberVentDuration = FloatOptionItem.Create(Id + 22, "C&R_RobberVentDuration", new(1f, 20f, 0.5f), 10f, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + CandR_RobberVentCooldown = FloatOptionItem.Create(Id + 23, "C&R_RobberVentCooldown", new(10f, 60f, 2.5f), 20f, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + + CandR_RobberAbilityTriggerChance = IntegerOptionItem.Create(Id + 24, "C&R_RobberAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent); + CandR_RobberAbilityDuration = IntegerOptionItem.Create(Id + 25, "C&R_RobberAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetParent(CandR_RobberAbilityTriggerChance); + + CandR_AdrenalineRushChance = IntegerOptionItem.Create(Id + 26, "C&R_AdrenalineRushChance", new(0, 100, 5), 30, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_RobberAbilityTriggerChance); + CandR_AdrenalineRushSpeed = FloatOptionItem.Create(Id + 27, "C&R_AdrenalineRushSpeed", new(0f, 2f, 0.25f), 1f, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) - .SetParent(CandR_ScopeChance); + .SetParent(CandR_AdrenalineRushChance); + + CandR_EnergyShieldChance = IntegerOptionItem.Create(Id + 28, "C&R_EnergyShieldChance", new(0, 100, 5), 25, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_RobberAbilityTriggerChance); + + CandR_SmokeBombChance = IntegerOptionItem.Create(Id + 29, "C&R_SmokeBombChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_RobberAbilityTriggerChance); + CandR_SmokeBombDuration = IntegerOptionItem.Create(Id + 30, "C&R_SmokeBombDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetParent(CandR_SmokeBombChance); + CandR_DisguiseChance = IntegerOptionItem.Create(Id + 31, "C&R_DisguiseChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_RobberAbilityTriggerChance); - CandR_NotifyRobbersWhenCaptured = BooleanOptionItem.Create(Id + 17, "C&R_NotifyRobbersWhenCaptured", true, TabGroup.ModSettings, false) + CandR_RadarChance = IntegerOptionItem.Create(Id + 32, "C&R_RadarChance", new(0, 100, 5), 15, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 123, 255, byte.MaxValue)); + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Percent) + .SetParent(CandR_RobberAbilityTriggerChance); + + roleSettings[CustomRoles.Robber] = [CandR_NotifyRobbersWhenCaptured, CandR_RobberVentDuration, CandR_RobberVentCooldown, CandR_RobberAbilityTriggerChance]; } public enum RoleType @@ -165,7 +254,7 @@ private enum RobberAbility EnergyShield, // Protects from the next capture attempt. SmokeBomb, // Blinds the Cop when captured. Disguise, // Wear the same costume as a Cop for confusion. - InvisibilityCloak // Become invisible for a set amount of duration. + Radar // Points towards closest captured player, if no captured player, it points towards a cop (colored arrows indicating cops or captured) } private enum CopAbility { @@ -176,15 +265,6 @@ private enum CopAbility Scope // Increases the capture range. } - public static RoleTypes RoleBase(CustomRoles role) - { - return role switch - { - CustomRoles.Cop => RoleTypes.Shapeshifter, - CustomRoles.Robber => RoleTypes.Engineer, - _ => RoleTypes.Engineer - }; - } public static bool HasTasks(CustomRoles role) { return role switch @@ -195,7 +275,7 @@ public static bool HasTasks(CustomRoles role) }; } - private static void CopCumulativeChances() + private static void CumulativeAbilityChances() { Dictionary copOptionItems = new() { @@ -213,6 +293,23 @@ private static void CopCumulativeChances() cumulativeChance += optionItem.Value.GetInt(); copAbilityChances.Add(optionItem.Key, cumulativeChance); } + + Dictionary robberOptionItems = new() + { + {RobberAbility.AdrenalineRush, CandR_AdrenalineRushChance }, + {RobberAbility.EnergyShield, CandR_EnergyShieldChance }, + {RobberAbility.SmokeBomb, CandR_SmokeBombChance }, + {RobberAbility.Radar, CandR_RadarChance }, + {RobberAbility.Disguise, CandR_DisguiseChance }, + }; + robberAbilityChances.Clear(); + cumulativeChance = 0; + foreach (var optionItem in robberOptionItems) + { + if (optionItem.Value.GetInt() == 0) continue; + cumulativeChance += optionItem.Value.GetInt(); + robberAbilityChances.Add(optionItem.Key, cumulativeChance); + } } public static void Init() @@ -227,15 +324,22 @@ public static void Init() saved.Clear(); numCops = CandR_NumCops.GetInt(); defaultSpeed.Clear(); - RemoveAbility.Clear(); + RemoveCopAbility.Clear(); + RemoveRobberAbility.Clear(); trapLocation.Clear(); removeTrap.Clear(); spikeTrigger.Clear(); flashTrigger.Clear(); k9.Clear(); scopeAltered.Clear(); + energyShieldActive.Clear(); + smokeBombActive.Clear(); + smokeBombTriggered.Clear(); + adrenalineRushActive.Clear(); + radar.Clear(); + disguise.Clear(); killDistance = Main.RealOptionsData.GetInt(Int32OptionNames.KillDistance); - CopCumulativeChances(); + CumulativeAbilityChances(); } public static Dictionary SetRoles() { @@ -294,14 +398,18 @@ private static void AddCaptured(this PlayerControl robber, Vector2 capturedLocat captured[robber.PlayerId] = capturedLocation; RoleType.Captured.SetCostume(playerId: robber.PlayerId); Main.AllPlayerSpeed[robber.PlayerId] = Main.MinSpeed; + robber.RpcSetVentInteraction(); robber?.MarkDirtySettings(); } private static void RemoveCaptured(this PlayerControl rescued) { if (rescued == null) return; captured.Remove(rescued.PlayerId); - RoleType.Robber.SetCostume(playerId: rescued.PlayerId); //for robber + if (disguise.Contains(rescued.PlayerId)) RoleType.Cop.SetCostume(playerId: rescued.PlayerId); + else RoleType.Robber.SetCostume(playerId: rescued.PlayerId); //for robber Main.AllPlayerSpeed[rescued.PlayerId] = defaultSpeed[rescued.PlayerId]; + if (adrenalineRushActive.Contains(rescued.PlayerId)) Main.AllPlayerSpeed[rescued.PlayerId] += CandR_AdrenalineRushSpeed.GetFloat(); + rescued.RpcSetVentInteraction(); rescued?.MarkDirtySettings(); } @@ -322,12 +430,6 @@ public static void SetCostume(this RoleType opMode, byte playerId) "visor_pk01_Security1Visor", //visor player.CurrentOutfit.PetId, player.CurrentOutfit.NamePlateId); - - //player.RpcSetColor(1); //blue - //player.RpcSetHat("hat_police"); - //player.RpcSetSkin("skin_Police"); - //player.RpcSetVisor("visor_pk01_Security1Visor"); - break; case RoleType.Robber: playerOutfit.Set(player.GetRealName(isMeeting: true), @@ -337,9 +439,6 @@ public static void SetCostume(this RoleType opMode, byte playerId) "visor_None", //visor player.CurrentOutfit.PetId, player.CurrentOutfit.NamePlateId); - //player.RpcSetColor(6); //black - //player.RpcSetHat("hat_pk04_Vagabond"); - //player.RpcSetSkin("skin_None"); break; case RoleType.Captured: playerOutfit.Set(player.GetRealName(isMeeting: true), @@ -349,10 +448,6 @@ public static void SetCostume(this RoleType opMode, byte playerId) "visor_pk01_DumStickerVisor", //visor player.CurrentOutfit.PetId, player.CurrentOutfit.NamePlateId); - //player.RpcSetColor(5); //yellow - //player.RpcSetHat("hat_tombstone"); - //player.RpcSetSkin("skin_prisoner"); - //player.RpcSetVisor("visor_pk01_DumStickerVisor"); break; } player.SetNewOutfit(newOutfit: playerOutfit, setName: false, setNamePlate: false); @@ -361,18 +456,25 @@ public static void SetCostume(this RoleType opMode, byte playerId) public static void CaptureCooldown(PlayerControl cop) => Main.AllPlayerKillCooldown[cop.PlayerId] = CandR_CaptureCooldown.GetFloat(); - private static void SendCandRData(byte op, byte copId) + private static void SendCandRData(byte op, byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncCandRData, SendOption.Reliable, -1); writer.Write(op); switch (op) { case 0: - writer.Write(copId); - writer.Write(k9[copId]); + writer.Write(playerId); + writer.Write(k9[playerId]); break; case 1: - writer.Write(copId); + writer.Write(playerId); + break; + case 2: + writer.Write(playerId); + writer.Write(radar[playerId]); + break; + case 3: + writer.Write(playerId); break; } @@ -392,6 +494,15 @@ public static void ReceiveCandRData(MessageReader reader) byte removeCopId = reader.ReadByte(); k9.Remove(removeCopId); break; + case 2: + byte robberId = reader.ReadByte(); + byte radarId = reader.ReadByte(); + radar[robberId] = radarId; + break; + case 3: + byte removeRobberId = reader.ReadByte(); + radar.Remove(removeRobberId); + break; } } @@ -440,7 +551,7 @@ private static void DeactivateCopAbility(this PlayerControl cop, CopAbility? abi default: return; } - RemoveAbility.Remove(cop.PlayerId); + RemoveCopAbility.Remove(cop.PlayerId); } private static void ActivateCopAbility(this PlayerControl cop, CopAbility? ability) { @@ -477,12 +588,12 @@ private static void ActivateCopAbility(this PlayerControl cop, CopAbility? abili default: return; } - RemoveAbility[cop.PlayerId] = ability; - var notifyMsg = GetString("C&R_CopAbilityActivated"); + RemoveCopAbility[cop.PlayerId] = ability; + var notifyMsg = GetString("C&R_AbilityActivated"); cop.Notify(string.Format(notifyMsg.Replace("{Ability.Name}", "{0}"), GetString($"CopAbility.{ability}")), CandR_CopAbilityDuration.GetFloat()); _ = new LateTask(() => { - if (!GameStates.IsInGame || !RemoveAbility.ContainsKey(cop.PlayerId)) return; + if (!GameStates.IsInGame || !RemoveCopAbility.ContainsKey(cop.PlayerId)) return; cop.DeactivateCopAbility(ability: ability, loc: loc); }, CandR_CopAbilityDuration.GetInt(), "Remove cop ability"); } @@ -501,8 +612,15 @@ public static void UnShapeShiftButton(PlayerControl shapeshifter) } public static string GetClosestArrow(PlayerControl seer, PlayerControl target, bool isForMeeting = false) { - if (isForMeeting || !k9.ContainsKey(seer.PlayerId) || seer.PlayerId != target.PlayerId) return string.Empty; - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cop), TargetArrow.GetArrows(seer)); + if (isForMeeting || seer.PlayerId != target.PlayerId) return string.Empty; + if (k9.ContainsKey(seer.PlayerId)) + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cop), TargetArrow.GetArrows(seer)); + else if (radar.ContainsKey(seer.PlayerId)) + { + bool isCaptured = captured.ContainsKey(radar[seer.PlayerId]); + return Utils.ColorString(isCaptured? Utils.GetRoleColor(CustomRoles.Robber) : Utils.GetRoleColor(CustomRoles.Cop), TargetArrow.GetArrows(seer)); + } + return string.Empty; } public static void OnCopAttack(PlayerControl cop, PlayerControl robber) { @@ -520,37 +638,177 @@ public static void OnCopAttack(PlayerControl cop, PlayerControl robber) return; } - Vector2 robberLocation = robber.GetCustomPosition(); - robber.AddCaptured(robberLocation); + if (!energyShieldActive.Contains(robber.PlayerId)) + { + Vector2 robberLocation = robber.GetCustomPosition(); + robber.AddCaptured(robberLocation); - if (CandR_NotifyRobbersWhenCaptured.GetBool()) - { - foreach (byte pid in robbers) + if (CandR_NotifyRobbersWhenCaptured.GetBool()) { - if (pid == byte.MaxValue) continue; - PlayerControl pc = Utils.GetPlayerById(pid); - pc?.KillFlash(); + foreach (byte pid in robbers) + { + if (pid == byte.MaxValue) continue; + PlayerControl pc = Utils.GetPlayerById(pid); + pc?.KillFlash(); + } } - } - if (!capturedScore.ContainsKey(cop.PlayerId)) capturedScore[cop.PlayerId] = 0; - capturedScore[cop.PlayerId]++; + if (!capturedScore.ContainsKey(cop.PlayerId)) capturedScore[cop.PlayerId] = 0; + capturedScore[cop.PlayerId]++; - if (!timesCaptured.ContainsKey(robber.PlayerId)) timesCaptured[robber.PlayerId] = 0; - timesCaptured[robber.PlayerId]++; + if (!timesCaptured.ContainsKey(robber.PlayerId)) timesCaptured[robber.PlayerId] = 0; + timesCaptured[robber.PlayerId]++; + } + else + { + Logger.Info($"capture canceled, Energy shield active {robber.PlayerId}", "Capture canceled"); + cop.Notify($"Could not capture, {GetString("RobberAbility.EnergyShield")} active"); + } + if (smokeBombActive.Contains(robber.PlayerId)) + { + smokeBombTriggered[cop.PlayerId] = Utils.GetTimeStamp(); + cop.MarkDirtySettings(); + smokeBombActive.Remove(robber.PlayerId); + Logger.Info($"smoke bomb triggered for {cop.PlayerId}", "smoke trigger"); + } CaptureCooldown(cop); - cop.ResetKillCooldown(); cop.SetKillCooldown(); } + private static RobberAbility? RandomRobberAbility() + { + var random = IRandom.Instance; + var shouldTrigger = random.Next(100); + if (!robberAbilityChances.Any() || shouldTrigger >= CandR_RobberAbilityTriggerChance.GetInt()) return null; + + int randomChance = random.Next(copAbilityChances.Values.Last()); + foreach (var ability in robberAbilityChances) + { + if (randomChance < ability.Value) + { + return ability.Key; + } + } + return null; // shouldn't happen + } + private static void DeactivateRobberAbility(this PlayerControl robber, RobberAbility? ability) + { + if (ability == null || robber == null) return; + switch (ability) + { + case RobberAbility.AdrenalineRush: + adrenalineRushActive.Remove(robber.PlayerId); + if (!spikeTrigger.ContainsKey(robber.PlayerId)) + { + Main.AllPlayerSpeed[robber.PlayerId] -= CandR_AdrenalineRushSpeed.GetFloat(); + robber.MarkDirtySettings(); + } + break; + case RobberAbility.EnergyShield: + energyShieldActive.Remove(robber.PlayerId); + break; + case RobberAbility.SmokeBomb: + smokeBombActive.Remove(robber.PlayerId); + break; + case RobberAbility.Disguise: + if (captured.ContainsKey(robber.PlayerId)) + { + Logger.Info($"disguise finished for captured player {robber.PlayerId}", "disguise finish"); + break; + } + disguise.Remove(robber.PlayerId); + RoleType.Robber.SetCostume(robber.PlayerId); + Logger.Info($"reverting costume because disguise finished for player: {robber.PlayerId}", "disguise finish"); + break; + case RobberAbility.Radar: + byte targetId = radar[robber.PlayerId]; + Logger.Info($"Removed k9 for {robber.PlayerId}", "Remove k9"); + radar.Remove(robber.PlayerId); + SendCandRData(3, robber.PlayerId); + TargetArrow.Remove(robber.PlayerId, targetId); + break; + default: + return; + } + RemoveRobberAbility.Remove(robber.PlayerId); + } + private static void ActivateRobberAbility(this PlayerControl robber, RobberAbility? ability) + { + if (ability == null || robber == null) return; + switch (ability) + { + case RobberAbility.AdrenalineRush: + adrenalineRushActive.Add(robber.PlayerId); + Main.AllPlayerSpeed[robber.PlayerId] += CandR_AdrenalineRushSpeed.GetFloat(); + robber.MarkDirtySettings(); + break; + case RobberAbility.EnergyShield: + energyShieldActive.Add(robber.PlayerId); + break; + case RobberAbility.SmokeBomb: + smokeBombActive.Add(robber.PlayerId); + break; + case RobberAbility.Disguise: + RoleType.Cop.SetCostume(robber.PlayerId); + disguise.Add(robber.PlayerId); + break; + case RobberAbility.Radar: + if (radar.ContainsKey(robber.PlayerId)) + return; + radar.Add(robber.PlayerId, byte.MaxValue); + SendCandRData(2, robber.PlayerId); + Logger.Info($"Added {robber.PlayerId} for radar", "radar activated"); + break; + default: + return; + } + RemoveRobberAbility[robber.PlayerId] = ability; + var notifyMsg = GetString("C&R_AbilityActivated"); + robber.Notify(string.Format(notifyMsg.Replace("{Ability.Name}", "{0}"), GetString($"RobberAbility.{ability}")), CandR_RobberAbilityDuration.GetFloat()); + _ = new LateTask(() => + { + if (!GameStates.IsInGame || !RemoveRobberAbility.ContainsKey(robber.PlayerId)) return; + robber.DeactivateRobberAbility(ability: ability); + }, CandR_RobberAbilityDuration.GetInt(), "Remove cop ability"); + } + public static void OnRobberExitVent(PlayerControl pc) + { + if (pc == null) return; + if (!pc.Is(CustomRoles.Robber)) return; + if (captured.ContainsKey(pc.PlayerId)) + { + Logger.Info($"Player {pc.PlayerId} was captured", "robber activity cancel"); + return; + } + if (spikeTrigger.ContainsKey(pc.PlayerId)) + { + Logger.Info($"Ability canceled for {pc.PlayerId}, robber triggered spike strip", "robber ability cancel"); + return; + } + var ability = RandomRobberAbility(); + if (ability == null) return; + + float delay = Utils.GetActiveMapId() != 5 ? 0.1f : 0.4f; + _ = new LateTask(() => + { + ActivateRobberAbility(pc, ability); + }, delay, "Robber On Exit Vent"); + } public static void ApplyGameOptions(ref IGameOptions opt, PlayerControl player) { if (player.Is(CustomRoles.Cop) && CandR_CopAbilityTriggerChance.GetFloat() > 0f) { - AURoleOptions.ShapeshifterCooldown = CandR_AbilityCooldown.GetFloat(); + AURoleOptions.ShapeshifterCooldown = CandR_CopAbilityCooldown.GetFloat(); AURoleOptions.ShapeshifterDuration = CandR_CopAbilityDuration.GetFloat(); } + + if (player.Is(CustomRoles.Robber)) + { + AURoleOptions.EngineerCooldown = CandR_RobberVentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = CandR_RobberVentDuration.GetFloat(); + } + if (scopeAltered.TryGetValue(player.PlayerId, out bool isAltered)) { if (!isAltered) @@ -560,12 +818,12 @@ public static void ApplyGameOptions(ref IGameOptions opt, PlayerControl player) } opt.SetInt(Int32OptionNames.KillDistance, killDistance); } - if (flashTrigger.ContainsKey(player.PlayerId)) + if (flashTrigger.ContainsKey(player.PlayerId) || smokeBombTriggered.ContainsKey(player.PlayerId)) { opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, 0.25f); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0.25f); - Logger.Warn($"vision for {player.PlayerId} set to 0.25f", "flash vision"); + opt.SetFloat(FloatOptionNames.CrewLightMod, 0.05f); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0.05f); + Logger.Warn($"vision for {player.PlayerId} set to 0.05f", "blind vision"); } else { @@ -592,7 +850,8 @@ public static void SetAbilityButtonText(HudManager hud, byte playerId) [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] class FixedUpdateInGameModeCandRPatch { - private static long LastChecked; + private static long LastCheckedCop; + private static long LastCheckedRobber; public static void Postfix(PlayerControl __instance) { if (!GameStates.IsInTask || Options.CurrentGameMode != CustomGameMode.CandR) return; @@ -619,7 +878,7 @@ public static void Postfix(PlayerControl __instance) UnShapeshifter.RpcRejectShapeshift(); RoleType.Cop.SetCostume(UnShapeshifter.PlayerId); Utils.NotifyRoles(SpecifyTarget: UnShapeshifter); - Logger.Info($"Revert to shapeshifting state for: {__instance.GetRealName()}", "UnShapeShifer_FixedUpdate"); + Logger.Info($"Revert to shapeshifting state for: {__instance.GetRealName()}", "UnShapeShifter_FixedUpdate"); } } } @@ -639,13 +898,15 @@ public static void Postfix(PlayerControl __instance) Vector2 currentRobberLocation = robber.GetCustomPosition(); // check if duration of trap is completed every second - if (now != LastChecked) + if (now != LastCheckedRobber) { - LastChecked = now; + LastCheckedRobber = now; // If Spike duration finished, reset the speed of trapped player if (spikeTrigger.ContainsKey(robberId) && now - spikeTrigger[robberId] > CandR_SpikeStripDuration.GetFloat()) { Main.AllPlayerSpeed[robberId] = defaultSpeed[robberId]; + if (adrenalineRushActive.Contains(robberId)) + Main.AllPlayerSpeed[robberId] += CandR_AdrenalineRushSpeed.GetFloat(); robber?.MarkDirtySettings(); spikeTrigger.Remove(robberId); } @@ -654,7 +915,7 @@ public static void Postfix(PlayerControl __instance) { flashTrigger.Remove(robberId); robber.MarkDirtySettings(); - Logger.Warn($"Removed {robberId} from Flash trigger", "Flash remove"); + Logger.Info($"Removed {robberId} from Flash trigger", "Flash remove"); } } @@ -689,6 +950,43 @@ public static void Postfix(PlayerControl __instance) } } + if (radar.ContainsKey(robberId)) + { + PlayerControl closest = null; + if (captured.Any()) + { + closest = Main.AllAlivePlayerControls.Where(pc => captured.ContainsKey(pc.PlayerId) && pc != null && pc.PlayerId != robberId) + .MinBy(capturedPC => Utils.GetDistance(robber.GetCustomPosition(), capturedPC.GetCustomPosition())); + } + if (closest == null) + { + closest = Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoles.Cop) && pc != null) + .MinBy(closestCop => Utils.GetDistance(robber.GetCustomPosition(), closestCop.GetCustomPosition())); + } + + if (closest != null) + { + if (radar.TryGetValue(robberId, out byte targetId) && targetId != byte.MaxValue) + { + if (targetId != closest.PlayerId) + { + radar[robberId] = closest.PlayerId; + SendCandRData(2, robberId); + Logger.Info($"Set radar for {robberId}, closest: {closest.PlayerId}", "radar Change"); + TargetArrow.Remove(robberId, targetId); + TargetArrow.Add(robberId, closest.PlayerId); + } + } + else + { + radar[robberId] = closest.PlayerId; + SendCandRData(2, robberId); + TargetArrow.Add(robberId, closest.PlayerId); + Logger.Info($"Add radar for {robberId}, closest: {closest.PlayerId}", "radar add"); + } + } + } + // Check if trap triggered if (trapLocation.Any()) { @@ -716,7 +1014,7 @@ public static void Postfix(PlayerControl __instance) { flashTrigger[robberId] = now; robber.MarkDirtySettings(); - Logger.Warn($"added {robberId} to flashtrigger", "Flash trigger"); + Logger.Info($"added {robberId} to flashTrigger", "Flash trigger"); removeTrap.Add(trap.Key); break; } @@ -735,6 +1033,20 @@ public static void Postfix(PlayerControl __instance) if (copId == byte.MaxValue) continue; PlayerControl copPC = Utils.GetPlayerById(copId); if (copPC == null) continue; + + if (smokeBombTriggered.Any()) + { + if (now != LastCheckedCop) + { + LastCheckedCop = now; + if (smokeBombTriggered.ContainsKey(copId) && now - smokeBombTriggered[copId] > CandR_SmokeBombDuration.GetFloat()) + { + smokeBombTriggered.Remove(copId); + copPC.MarkDirtySettings(); + Logger.Info($"Removed smoke bomb effect from {copId}", "remove smoke bomb"); + } + } + } //check for k9 if (k9.ContainsKey(copId)) { @@ -761,31 +1073,6 @@ public static void Postfix(PlayerControl __instance) } } } - - //// below this only captured - //if (!captured.Any()) return; - - - - - //foreach (byte capturedId in captured) - //{ - // PlayerControl capturedPC = Utils.GetPlayerById(capturedId); - // if (capturedPC == null) continue; - - // var capturedPos = capturedPC.GetCustomPosition(); - - // foreach (byte robberId in robbers) - // { - // if (captured.Contains(robberId)) continue; - // PlayerControl robberPC = Utils.GetPlayerById(robberId); - // if (robberPC == null) continue; - - // float dis = Utils.GetDistance(capturedPos, robberPC.GetCustomPosition()); - - // } - //} - } } diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 8952316888..342df6c42c 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -18,6 +18,9 @@ public static class CustomRolesHelper public static readonly Custom_Team[] AllRoleTypes = EnumHelper.GetAllValues(); public static CustomRoles GetVNRole(this CustomRoles role) // RoleBase: Impostor, Shapeshifter, Crewmate, Engineer, Scientist { + //C&R + if (Options.CurrentGameMode is CustomGameMode.CandR && role is CustomRoles.Robber) return CustomRoles.Engineer; + // Vanilla roles if (role.IsVanilla()) return role; @@ -45,7 +48,8 @@ public static RoleTypes GetDYRole(this CustomRoles role) // Role has a kill butt if (role is CustomRoles.Killer) return RoleTypes.Impostor; break; case CustomGameMode.CandR: //C&R - return CopsAndRobbersManager.RoleBase(role); + if (role is CustomRoles.Cop) return RoleTypes.Shapeshifter; + break; } return (role.GetStaticRoleClass().ThisRoleBase is CustomRoles.Impostor or CustomRoles.Shapeshifter) && !role.IsImpostor() diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 2e69554362..db2ad54e0e 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1037,7 +1037,12 @@ public static bool HasKillButton(this PlayerControl pc) _ => false }; } - public static bool CanUseVents(this PlayerControl player) => player != null && (player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer); + public static bool CanUseVents(this PlayerControl player) => Options.CurrentGameMode switch + { + CustomGameMode.CandR => player.Is(CustomRoles.Robber) && !CopsAndRobbersManager.captured.ContainsKey(player.PlayerId), + _ => player != null && (player.CanUseImpostorVentButton() || player.GetCustomRole().GetVNRole() == CustomRoles.Engineer) + }; + public static bool CantUseVent(this PlayerControl player, int ventId) => player == null || !player.CanUseVents() || (CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId)); public static bool HasAnyBlockedVent(this PlayerControl player) => player != null && CustomRoleManager.BlockedVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Any(); public static bool NotUnlockVent(this PlayerControl player, int ventId) => player != null && CustomRoleManager.DoNotUnlockVentsList.TryGetValue(player.PlayerId, out var blockedVents) && blockedVents.Contains(ventId); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index c4e20bac90..5756d6f82e 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1803,17 +1803,8 @@ public static void SendRolesInfo(string role, byte playerId, bool isDev = false, var Conf1 = new StringBuilder(); CustomRoles rl1; - OptionItem option; - if (role == copName) - { - rl1 = CustomRoles.Cop; - option = CopsAndRobbersManager.CopHeader; - } - else if (role == robberName) - { - rl1 = CustomRoles.Robber; - option = CopsAndRobbersManager.RobberHeader; - } + if (role == copName) rl1 = CustomRoles.Cop; + else if (role == robberName) rl1 = CustomRoles.Robber; else { Utils.SendMessage(GetString("ModeDescribe.C&R"), playerId); @@ -1823,11 +1814,19 @@ public static void SendRolesInfo(string role, byte playerId, bool isDev = false, var description = rl1.GetInfoLong(); var title1 = Utils.ColorString(Utils.GetRoleColor(rl1), GetString($"{rl1}")); string rlHex1 = Utils.GetRoleColorCode(rl1); - //Conf1.Append($"{option.GetName(true)}: {option.GetString()}\n"); - Utils.ShowChildrenSettings(option, ref Conf1); - var cleared1 = Conf1.ToString(); - var Setting1 = $"{GetString(rl1.ToString())} {GetString("Settings:")}\n"; - Conf1.Clear().Append($"{Setting1}{cleared1}"); + var Setting = $"{GetString(rl1.ToString())} {GetString("Settings:")}\n"; + Conf1.Clear().Append(Setting); + + foreach (OptionItem opt in CopsAndRobbersManager.roleSettings[rl1]) + { + Conf1.Append($"{opt.GetName(true)}: {opt.GetString()}\n"); + int deep = 0; + if (opt.Children.Any()) deep = 1; + Utils.ShowChildrenSettings(opt, ref Conf1, deep: deep); + var cleared = Conf1.ToString(); + Conf1.Clear().Append($"{cleared}"); + } + Conf1.Append(""); // Show role info Utils.SendMessage(description, playerId, title1, noReplay: true); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 860fd8da28..452f109867 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1565,6 +1565,10 @@ public static void Postfix(PlayerPhysics __instance, [HarmonyArgument(0)] int id } if (!AmongUsClient.Instance.AmHost) return; + if (Options.CurrentGameMode == CustomGameMode.CandR) + { + CopsAndRobbersManager.OnRobberExitVent(player); + } player.GetRoleClass()?.OnExitVent(player, id); diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 59a948bb0a..7bd9bd29d9 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -423,13 +423,18 @@ public static System.Collections.IEnumerator AssignRoles() switch (Options.CurrentGameMode) { case CustomGameMode.FFA: - case CustomGameMode.CandR: - foreach (var pair in Main.PlayerStates) - { - ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); - } - goto EndOfSelectRolePatch; + { + ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); + } + goto EndOfSelectRolePatch; + case CustomGameMode.CandR: + foreach (var pair in RoleAssign.RoleResult) + { + if (pair.Value is CustomRoles.Robber) AssignCustomRole(pair.Value, Utils.GetPlayerById(pair.Key)); + ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value); + } + goto EndOfSelectRolePatch; } foreach (var kv in RoleAssign.RoleResult) diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 4b5fa4736a..d260bf93cc 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1021,7 +1021,7 @@ "SlothInfoLong": "(Add-ons):\nThe Sloth's default movement speed is slower than others.\n(Speed depends on the setting of the Host)", "ProhibitedInfoLong": "(Add-ons):\nAs the Prohibited, you have specific vents that you can't use.\nHow many vents are disabled depends on the Host's settings.", "EavesdropperInfoLong": "(Add-ons):\nAs the Eavesdropper, you have a chance to read other role/addon information-based messages like Mortician or Sleuth.", - + "ShowTextOverlay": "Text Overlay", "Overlay.GuesserMode": "Guesser Mode", "Overlay.NoGameEnd": "No Game End", @@ -3617,11 +3617,14 @@ "C&R": "Cops and Robbers", "C&RShortInfo": "Robbers complete tasks, while Cops race to capture them!", "ModeC&R": "Gamemode: Cops And Robbers", + "Cop": "Cop", + "CopInfo": "Capture all Robbers to win", + "CopInfoLong": "As a cop, use your kill button to capture robbers before they finish their tasks. When caught, robbers freeze in place.\n\nYou can trigger a random ability with the shapeshift button.\nUse/ability cop to check all the abilities.\n\nTrack down the robbers and secure your win!", "C&R_NumCops": "Number of Cops", - "C&R_CopAbilityTriggerChance": "Ability trigger chance", + "C&R_CopAbilityTriggerChance": "Cop ability trigger chance", "C&R_CaptureCooldown": "Capture Cooldown", - "C&R_CopAbilityDuration": "Ability Duration", - "C&R_AbilityCooldown": "Ability Cooldown", + "C&R_CopAbilityDuration": "Cop ability duration", + "C&R_CopAbilityCooldown": "Cop ability cooldown", "C&R_HotPursuitChance": "Hot Pursuit activation chance", "C&R_HotPursuitSpeed": "Increase speed by", "C&R_SpikeStripChance": "Spike Strip activation chance", @@ -3633,7 +3636,7 @@ "C&R_K9Chance": "K9 chance", "C&R_ScopeChance": "Scope chance", "C&R_ScopeIncrease": "Scope Increase", - "C&R_CopAbilityActivated": "{Ability.Name} activated", + "C&R_AbilityActivated": "{Ability.Name} activated", "CopAbility.HotPursuit": "Hot Pursuit", "CopAbility.SpikeStrip": "Spike Strip", "CopAbility.FlashBang": "Flash Bang", @@ -3642,9 +3645,25 @@ "CopAbilityText": "Ability", "CopKillButtonText": "Capture", "C&R_NotifyRobbersWhenCaptured": "Notify Robbers when captured", - "Cop": "Cop", - "CopInfo": "Capture all Robbers to win", - "CopInfoLong": "As a cop, use your kill button to capture robbers before they finish their tasks. When caught, robbers freeze in place.\n\nYou can trigger a random ability with the shapeshift button.\n Use/ability cop to check all the abilities.\n\nTrack down the robbers and secure your win!", + "Robber": "Robber", + "RobberInfo": "Complete all tasks to win", + "RobberInfoLong": "As robber, your goal is to complete all tasks and escape the watchful eyes of the cops! If you encounter a captured player, you can touch them to set them free!\n\nWhen you emerge from a vent, you’ll activate a random ability.\nUse/ability robber to check all the abilities. Work together, stay stealthy, and outsmart the cops to pull of a great Heist!", + "C&R_RobberVentDuration": "Robber vent duration", + "C&R_RobberVentCooldown": "Robber vent cooldown", + "C&R_RobberAbilityTriggerChance": "Robber ability trigger chance", + "C&R_RobberAbilityDuration": "Robber ability duration", + "C&R_AdrenalineRushChance": "Adrenaline Rush activation chance", + "C&R_AdrenalineRushSpeed": "Increase speed by", + "C&R_EnergyShieldChance": "Energy Shield activation chance", + "C&R_SmokeBombChance": "Smoke Bomb activation chance", + "C&R_SmokeBombDuration": "Smoke Bomb duration", + "C&R_DisguiseChance": "Disguise activation chance", + "C&R_RadarChance": "Radar activation chance", + "RobberAbility.AdrenalineRush": "Adrenaline Rush", + "RobberAbility.EnergyShield": "Energy Shield", + "RobberAbility.SmokeBomb": "Smoke Bomb", + "RobberAbility.Disguise": "Disguise", + "RobberAbility.Radar": "Radar", "FFA": "Free For All", From 82c81ab1fd80133f9a6d4e546655d3ce3efdd256 Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Fri, 4 Oct 2024 04:59:39 +0530 Subject: [PATCH 06/12] hide opts, summary and ability descriptions --- GameModes/CopsAndRobbersManager.cs | 199 ++++++++++++++++++++++++++++- Modules/OptionHolder.cs | 16 ++- Modules/OptionItem/OptionItem.cs | 2 +- Modules/Utils.cs | 74 +++++++---- Patches/ChatCommandPatch.cs | 11 +- Patches/PlayerControlPatch.cs | 2 +- Patches/PlayerJoinAndLeftPatch.cs | 4 + Patches/RandomSpawnPatch.cs | 1 + Resources/Lang/en_US.json | 21 ++- Resources/roleColor.json | 3 +- 10 files changed, 287 insertions(+), 46 deletions(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index a8d484f88f..d9b97f14fa 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -3,6 +3,9 @@ using UnityEngine; using Hazel; using TOHE.Modules; +using AmongUs.Data; +using System.Text; +using System; namespace TOHE; @@ -40,6 +43,8 @@ internal static class CopsAndRobbersManager private static readonly HashSet disguise = []; private static int numCops; + private static int numCaptures; + private static int numRobbers; public static OptionItem CandR_NumCops; private static OptionItem CandR_CaptureCooldown; @@ -93,7 +98,6 @@ public static void SetupCustomOption() .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent); - //.SetParent(CopActiveHidden); CandR_CopAbilityDuration = IntegerOptionItem.Create(Id + 4, "C&R_CopAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) @@ -319,6 +323,8 @@ public static void Init() cops.Clear(); robbers.Clear(); captured.Clear(); + numCaptures = 0; + numRobbers = 0; capturedScore.Clear(); timesCaptured.Clear(); saved.Clear(); @@ -370,7 +376,8 @@ public static Dictionary SetRoles() } Logger.Msg($"set role for {pc.PlayerId}: {finalRoles[pc.PlayerId]}", "SetRoles"); } - + SendCandRData(5); + SendCandRData(6); return finalRoles; } private static void Add(this RoleType role, byte playerId) @@ -390,6 +397,7 @@ private static void Add(this RoleType role, byte playerId) robbers.Add(playerId); timesCaptured[playerId] = 0; saved[playerId] = 0; + numRobbers++; return; } } @@ -456,7 +464,7 @@ public static void SetCostume(this RoleType opMode, byte playerId) public static void CaptureCooldown(PlayerControl cop) => Main.AllPlayerKillCooldown[cop.PlayerId] = CandR_CaptureCooldown.GetFloat(); - private static void SendCandRData(byte op, byte playerId) + private static void SendCandRData(byte op, byte playerId = byte.MaxValue, int capturedCount = 0) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncCandRData, SendOption.Reliable, -1); writer.Write(op); @@ -476,6 +484,26 @@ private static void SendCandRData(byte op, byte playerId) case 3: writer.Write(playerId); break; + case 4: + writer.Write(capturedCount); + break; + case 5: + writer.Write(numRobbers); + break; + case 6: + writer.Write(cops.Count); + foreach (var pid in cops) writer.Write(pid); + writer.Write(robbers.Count); + foreach (var pid in robbers) writer.Write(pid); + break; + case 7: + writer.Write(playerId); + writer.Write(saved[playerId]); + break; + case 8: + writer.Write(playerId); + writer.Write(capturedScore[playerId]); + break; } AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -503,8 +531,29 @@ public static void ReceiveCandRData(MessageReader reader) byte removeRobberId = reader.ReadByte(); radar.Remove(removeRobberId); break; - } - + case 4: + numCaptures = reader.ReadInt32(); + break; + case 5: + numRobbers = reader.ReadInt32(); + break; + case 6: + cops.Clear(); + int copLength = reader.ReadInt32(); + for (int i = 0; i < copLength; i++) cops.Add(reader.ReadByte()); + robbers.Clear(); + int robberLength = reader.ReadInt32(); + for (int i = 0; i < robberLength; i++) robbers.Add(reader.ReadByte()); + break; + case 7: + byte robber = reader.ReadByte(); + saved[robber] = reader.ReadInt32(); + break; + case 8: + byte cop = reader.ReadByte(); + capturedScore[cop] = reader.ReadInt32(); + break; + } } private static CopAbility? RandomCopAbility() @@ -622,6 +671,7 @@ public static string GetClosestArrow(PlayerControl seer, PlayerControl target, b } return string.Empty; } + public static void OnCopAttack(PlayerControl cop, PlayerControl robber) { if (cop == null || robber == null || Options.CurrentGameMode != CustomGameMode.CandR) return; @@ -642,6 +692,15 @@ public static void OnCopAttack(PlayerControl cop, PlayerControl robber) { Vector2 robberLocation = robber.GetCustomPosition(); robber.AddCaptured(robberLocation); + numCaptures = captured.Count; + foreach (var pid in cops) + { + var player = Utils.GetPlayerById(pid); + if (player != null) + { + Utils.NotifyRoles(SpecifySeer: player); + } + } if (CandR_NotifyRobbersWhenCaptured.GetBool()) { @@ -658,6 +717,8 @@ public static void OnCopAttack(PlayerControl cop, PlayerControl robber) if (!timesCaptured.ContainsKey(robber.PlayerId)) timesCaptured[robber.PlayerId] = 0; timesCaptured[robber.PlayerId]++; + SendCandRData(4, capturedCount: numCaptures); + SendCandRData(8, playerId: cop.PlayerId); } else { @@ -834,6 +895,41 @@ public static void ApplyGameOptions(ref IGameOptions opt, PlayerControl player) return; } + public static void AbilityDescription(string ability, byte playerId = byte.MaxValue) + { + ability = ability.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); + StringBuilder copAbilities = new(); + int i = 0; + foreach (var ab in Enum.GetValues(typeof(CopAbility))) + { + i++; + copAbilities.Append($"{i}. {GetString($"CopAbility.{ab}")}:\n"); + copAbilities.Append($"{GetString($"Description.{ab}")}\n\n"); + } + StringBuilder robberAbilities = new(); + i = 0; + foreach (var ab in Enum.GetValues(typeof(RobberAbility))) + { + i++; + robberAbilities.Append($"{i}. {GetString($"RobberAbility.{ab}")}:\n"); + robberAbilities.Append($"{GetString($"Description.{ab}")}\n\n"); + } + + if (ability == GetString(CustomRoles.Cop.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty) || ability == "cop") + { + Utils.SendMessage(copAbilities.ToString(), sendTo:playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cop), Utils.GetRoleName(CustomRoles.Cop))); + } + else if (ability == GetString(CustomRoles.Cop.ToString()).ToLower().Trim().TrimStart('*').Replace(" ", string.Empty) || ability == "robber") + { + Utils.SendMessage(robberAbilities.ToString(), sendTo: playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Robber), Utils.GetRoleName(CustomRoles.Robber))); + } + else + { + Utils.SendMessage(copAbilities.ToString(), sendTo: playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cop), Utils.GetRoleName(CustomRoles.Cop))); + Utils.SendMessage(robberAbilities.ToString(), sendTo: playerId, title: Utils.ColorString(Utils.GetRoleColor(CustomRoles.Robber), Utils.GetRoleName(CustomRoles.Robber))); + } + } + public static void SetAbilityButtonText(HudManager hud, byte playerId) { if (playerId == byte.MaxValue) return; @@ -845,7 +941,88 @@ public static void SetAbilityButtonText(HudManager hud, byte playerId) hud.KillButton?.OverrideText(GetString("CopKillButtonText")); } } + public static string SummaryTexts(byte id, bool disableColor = true, bool check = false) + { + var name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty); + if (id == PlayerControl.LocalPlayer.PlayerId) name = DataManager.player.Customization.Name; + else name = Utils.GetPlayerById(id)?.Data.PlayerName ?? name; + + string TaskCount = string.Empty; + Color nameColor = Color.white; + string roleName = Utils.ColorString(Utils.GetRoleColor(CustomRoles.GM), Utils.GetRoleName(CustomRoles.GM)); + string capturedCountText = string.Empty; + if (robbers.Contains(id)) + { + var taskState = Main.PlayerStates?[id].TaskState; + Color CurrentСolor; + CurrentСolor = taskState.IsTaskFinished ? Color.green : Utils.GetRoleColor(CustomRoles.Robber); + TaskCount = Utils.ColorString(CurrentСolor, $" ({taskState.CompletedTasksCount}/{taskState.AllTasksCount})"); + nameColor = Utils.GetRoleColor(CustomRoles.Robber); + roleName = Utils.ColorString(nameColor, Utils.GetRoleName(CustomRoles.Robber)); + if (!saved.ContainsKey(id)) saved[id] = 0; + capturedCountText = Utils.ColorString(new Color32(255, 69, 0, byte.MaxValue), $"{GetString("Saved")}: {saved[id]}"); + } + else if (cops.Contains(id)) + { + nameColor = Utils.GetRoleColor(CustomRoles.Cop); + roleName = Utils.ColorString(nameColor, Utils.GetRoleName(CustomRoles.Cop)); + if (!capturedScore.ContainsKey(id)) capturedScore[id] = 0; + capturedCountText = Utils.ColorString(new Color32(255, 69, 0, byte.MaxValue), $"{GetString("Captured")}: {capturedScore[id]}"); + + } + + Main.PlayerStates.TryGetValue(id, out var playerState); + var disconnectedText = playerState.deathReason != PlayerState.DeathReason.etc && playerState.Disconnected ? $"({GetString("Disconnected")})" : string.Empty; + + string summary = $"{Utils.ColorString(nameColor, name)} - {roleName}{TaskCount} {capturedCountText} 『{Utils.GetVitalText(id, true)}』{disconnectedText}"; + + return check && Utils.GetDisplayRoleAndSubName(id, id, true).RemoveHtmlTags().Contains("INVALID:NotAssigned") + ? "INVALID" + : disableColor ? summary.RemoveHtmlTags() : summary; + } + + public static string GetProgressText(byte playerId) + { + string progressText = string.Empty; + if (playerId == byte.MaxValue) return progressText; + Color32 textColor = Color.white; + if (cops.Contains(playerId)) + { + progressText = $" ({numCaptures}/{numRobbers})"; + textColor = Utils.GetRoleColor(CustomRoles.Cop); + } + else if (robbers.Contains(playerId)) + { + var taskState = Main.PlayerStates?[playerId].TaskState; + string Completed = $"{taskState.CompletedTasksCount}"; + progressText= $" ({Completed}/{taskState.AllTasksCount})"; + textColor = taskState.IsTaskFinished? Color.green : Utils.GetRoleColor(CustomRoles.Robber); + } + return Utils.ColorString(textColor, progressText); + } + public static void OnPlayerDisconnect(byte playerId) + { + if (robbers.Contains(playerId)) + { + if (captured.ContainsKey(playerId)) + { + captured.Remove(playerId); + numCaptures = captured.Count; + SendCandRData(4, capturedCount: numCaptures); + } + numRobbers--; + SendCandRData(5); + foreach (var pid in cops) + { + var player = Utils.GetPlayerById(pid); + if (player != null) + { + Utils.NotifyRoles(SpecifySeer: player); + } + } + } + } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] class FixedUpdateInGameModeCandRPatch @@ -945,8 +1122,19 @@ public static void Postfix(PlayerControl __instance) if (!saved.ContainsKey(saviour)) saved[saviour] = 0; saved[saviour]++; Utils.GetPlayerById(rescued).RemoveCaptured(); + SendCandRData(7, playerId: saviour); } removeCaptured.Clear(); + numCaptures = captured.Count; + SendCandRData(4, capturedCount: numCaptures); + foreach (var pid in cops) + { + var player = Utils.GetPlayerById(pid); + if (player != null) + { + Utils.NotifyRoles(SpecifySeer: player); + } + } } } @@ -1075,5 +1263,4 @@ public static void Postfix(PlayerControl __instance) } } } - } \ No newline at end of file diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index c354a1c6c9..1f017655d9 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -1000,13 +1000,15 @@ private static System.Collections.IEnumerator CoLoadOptions() GradientTagsOpt = BooleanOptionItem.Create(60031, "EnableGadientTags", false, TabGroup.SystemSettings, false) .SetHeader(true); EnableKillerLeftCommand = BooleanOptionItem.Create(60040, "EnableKillerLeftCommand", true, TabGroup.SystemSettings, false) - .HideInHnS(); + .HideInHnS() + .HideInCandR(); ShowMadmatesInLeftCommand = BooleanOptionItem.Create(60042, "ShowMadmatesInLeftCommand", true, TabGroup.SystemSettings, false) .SetParent(EnableKillerLeftCommand); ShowApocalypseInLeftCommand = BooleanOptionItem.Create(60043, "ShowApocalypseInLeftCommand", true, TabGroup.SystemSettings, false) .SetParent(EnableKillerLeftCommand); SeeEjectedRolesInMeeting = BooleanOptionItem.Create(60041, "SeeEjectedRolesInMeeting", true, TabGroup.SystemSettings, false) - .HideInHnS(); + .HideInHnS() + .HideInCandR(); KickLowLevelPlayer = IntegerOptionItem.Create(60050, "KickLowLevelPlayer", new(0, 100, 1), 0, TabGroup.SystemSettings, false) .SetValueFormat(OptionFormat.Level) @@ -1087,9 +1089,11 @@ private static System.Collections.IEnumerator CoLoadOptions() .SetColor(Color.blue); HideExileChat = BooleanOptionItem.Create(60292, "HideExileChat", true, TabGroup.SystemSettings, false) .SetColor(Color.blue) - .HideInHnS(); + .HideInHnS() + .HideInCandR(); RemovePetsAtDeadPlayers = BooleanOptionItem.Create(60294, "RemovePetsAtDeadPlayers", false, TabGroup.SystemSettings, false) - .SetColor(Color.magenta); + .SetColor(Color.magenta) + .HideInCandR(); CheatResponses = StringOptionItem.Create(60250, "CheatResponses", CheatResponsesName, 0, TabGroup.SystemSettings, false) .SetHeader(true); @@ -1772,11 +1776,13 @@ private static System.Collections.IEnumerator CoLoadOptions() // 其它设定 TextOptionItem.Create(10000031, "MenuTitle.Other", TabGroup.ModSettings) .HideInFFA() + .HideInCandR() .SetColor(new Color32(193, 255, 209, byte.MaxValue)); // 梯子摔死 LadderDeath = BooleanOptionItem.Create(60760, "LadderDeath", false, TabGroup.ModSettings, false) .SetColor(new Color32(193, 255, 209, byte.MaxValue)) - .HideInFFA(); + .HideInFFA() + .HideInCandR(); LadderDeathChance = StringOptionItem.Create(60761, "LadderDeathChance", EnumHelper.GetAllNames()[1..], 0, TabGroup.ModSettings, false) .SetParent(LadderDeath); diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index 7f122038a4..a94ab3c14f 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -184,7 +184,7 @@ public virtual string GetString() // Deprecated IsHidden function public virtual bool IsHiddenOn(CustomGameMode mode) { - return IsHidden || this.Parent?.IsHiddenOn(Options.CurrentGameMode) == true || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); + return IsHidden || this.Parent?.IsHiddenOn(Options.CurrentGameMode) == true || (HideOptionInCandR != CustomGameMode.All && HideOptionInCandR == mode) || (HideOptionInFFA != CustomGameMode.All && HideOptionInFFA == mode) || (HideOptionInHnS != CustomGameMode.All && HideOptionInHnS == mode) || (GameMode != CustomGameMode.All && GameMode != mode); } public string ApplyFormat(string value) { diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 16f549aa7c..d1c47273e9 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -578,9 +578,19 @@ public static string GetKillCountText(byte playerId, bool ffa = false) return ColorString(new Color32(255, 69, 0, byte.MaxValue), string.Format(GetString("KillCount"), count)); } public static string GetVitalText(byte playerId, bool RealKillerColor = false) - { + { var state = Main.PlayerStates[playerId]; string deathReason = state.IsDead ? state.deathReason == PlayerState.DeathReason.etc && state.Disconnected ? GetString("Disconnected") : GetString("DeathReason." + state.deathReason) : GetString("Alive"); + if (Options.CurrentGameMode is CustomGameMode.CandR) + { + if (deathReason == GetString("Alive")) + { + if (CopsAndRobbersManager.captured.ContainsKey(playerId)) + deathReason = ColorString(GetRoleColor(CustomRoles.Robber), GetString("Captured")); + else deathReason = ColorString(Color.cyan, deathReason); + } + return deathReason; + } if (RealKillerColor) { var KillerId = state.GetRealKiller(); @@ -694,40 +704,45 @@ public static string GetProgressText(byte playerId, bool comms = false) var ProgressText = new StringBuilder(); var role = Main.PlayerStates[playerId].MainRole; - if (Options.CurrentGameMode == CustomGameMode.FFA && role == CustomRoles.Killer) + switch (Options.CurrentGameMode) { - ProgressText.Append(FFAManager.GetDisplayScore(playerId)); - } - else - { - ProgressText.Append(playerId.GetRoleClassById()?.GetProgressText(playerId, comms)); + case CustomGameMode.FFA: + if (role is CustomRoles.Killer) + ProgressText.Append(FFAManager.GetDisplayScore(playerId)); + break; + case CustomGameMode.CandR: + ProgressText.Append(CopsAndRobbersManager.GetProgressText(playerId)); + break; + default: + ProgressText.Append(playerId.GetRoleClassById()?.GetProgressText(playerId, comms)); - if (ProgressText.Length == 0) - { - var taskState = Main.PlayerStates?[playerId].TaskState; - if (taskState.hasTasks) + if (ProgressText.Length == 0) { - Color TextColor; - var info = GetPlayerInfoById(playerId); - var TaskCompleteColor = HasTasks(info) ? Color.green : GetRoleColor(role).ShadeColor(0.5f); - var NonCompleteColor = HasTasks(info) ? Color.yellow : Color.white; + var taskState = Main.PlayerStates?[playerId].TaskState; + if (taskState.hasTasks) + { + Color TextColor; + var info = GetPlayerInfoById(playerId); + var TaskCompleteColor = HasTasks(info) ? Color.green : GetRoleColor(role).ShadeColor(0.5f); + var NonCompleteColor = HasTasks(info) ? Color.yellow : Color.white; - if (Workhorse.IsThisRole(playerId)) - NonCompleteColor = Workhorse.RoleColor; + if (Workhorse.IsThisRole(playerId)) + NonCompleteColor = Workhorse.RoleColor; - var NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor; - if (Main.PlayerStates.TryGetValue(playerId, out var ps) && ps.MainRole == CustomRoles.Crewpostor) - NormalColor = Color.red; + var NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor; + if (Main.PlayerStates.TryGetValue(playerId, out var ps) && ps.MainRole == CustomRoles.Crewpostor) + NormalColor = Color.red; - TextColor = comms ? Color.gray : NormalColor; - string Completed = comms ? "?" : $"{taskState.CompletedTasksCount}"; - ProgressText.Append(ColorString(TextColor, $" ({Completed}/{taskState.AllTasksCount})")); + TextColor = comms ? Color.gray : NormalColor; + string Completed = comms ? "?" : $"{taskState.CompletedTasksCount}"; + ProgressText.Append(ColorString(TextColor, $" ({Completed}/{taskState.AllTasksCount})")); + } } - } - else - { - ProgressText.Insert(0, " "); - } + else + { + ProgressText.Insert(0, " "); + } + break; } return ProgressText.ToString(); } @@ -1628,6 +1643,8 @@ void SetRealName() } if (Options.CurrentGameMode == CustomGameMode.FFA) name = $"{GetString("ModeFFA")}\r\n" + name; + else if (Options.CurrentGameMode == CustomGameMode.CandR) + name = $"{GetString("ModeC&R")}\r\n" + name; } var modtag = ""; @@ -2493,6 +2510,7 @@ public static void DumpLog() public static string SummaryTexts(byte id, bool disableColor = true, bool check = false) { + if (Options.CurrentGameMode is CustomGameMode.CandR) return CopsAndRobbersManager.SummaryTexts(id, disableColor, check); var name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty); if (id == PlayerControl.LocalPlayer.PlayerId) name = DataManager.player.Customization.Name; else name = GetPlayerById(id)?.Data.PlayerName ?? name; diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 5756d6f82e..ac96806e1f 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -1399,6 +1399,11 @@ static Color32 RndCLR() Utils.SendMessage("" + str + "", PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medium), GetString("8BallTitle"))); break; + case "/ability": + subArgs = args.Length < 2 ? "" : args[1]; + CopsAndRobbersManager.AbilityDescription(subArgs); + break; + default: Main.isChatCommand = false; break; @@ -1807,7 +1812,7 @@ public static void SendRolesInfo(string role, byte playerId, bool isDev = false, else if (role == robberName) rl1 = CustomRoles.Robber; else { - Utils.SendMessage(GetString("ModeDescribe.C&R"), playerId); + Utils.SendMessage(GetString("ModeDescribe.C&R"), playerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cop), GetString("ModeC&R"))); return; } @@ -3088,6 +3093,10 @@ public static void OnReceiveChat(PlayerControl player, string text, out bool can } break; + case "/ability": + subArgs = args.Length < 2 ? "" : args[1]; + CopsAndRobbersManager.AbilityDescription(subArgs, player.PlayerId); + break; default: if (SpamManager.CheckSpam(player, text)) return; diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 452f109867..d51f5facd8 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -735,7 +735,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] Network return false; } if (Options.DisableMeeting.GetBool()) return false; - if (Options.CurrentGameMode == CustomGameMode.FFA) return false; + if (Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.CandR) return false; if (!CanReport[__instance.PlayerId]) { diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index eea8825540..017a39532f 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -359,6 +359,10 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] Client { if (GameStates.IsNormalGame && GameStates.IsInGame) { + if (Options.CurrentGameMode is CustomGameMode.CandR) + { + CopsAndRobbersManager.OnPlayerDisconnect(data.Character.PlayerId); + } if (data.Character.Is(CustomRoles.Lovers) && !data.Character.Data.IsDead) { foreach (var lovers in Main.LoversPlayers.ToArray()) diff --git a/Patches/RandomSpawnPatch.cs b/Patches/RandomSpawnPatch.cs index 442b5d70e7..d098564a10 100644 --- a/Patches/RandomSpawnPatch.cs +++ b/Patches/RandomSpawnPatch.cs @@ -167,6 +167,7 @@ public static void SetupCustomOption() { RandomSpawnMode = BooleanOptionItem.Create(60470, RandomSpawnOpt.RandomSpawnMode, false, TabGroup.ModSettings, false) .HideInFFA() + .HideInCandR() .SetColor(new Color32(19, 188, 233, byte.MaxValue)); SpawnInFirstRound = BooleanOptionItem.Create(60476, RandomSpawnOpt.RandomSpawn_SpawnInFirstRound, true, TabGroup.ModSettings, false) .SetParent(RandomSpawnMode); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 6a1e231a13..35521c56fa 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3616,11 +3616,14 @@ "InfluencedChangeVote": "Oops! You are so influenced by others!\nYou can not contain your fear that you change voted {0}!", "C&R": "Cops and Robbers", + "CandR": "Cops and Robbers", "C&RShortInfo": "Robbers complete tasks, while Cops race to capture them!", + "ModeDescribe.C&R": "Welcome to the ultimate heist showdown! In this thrilling Among Us mode, players are split into two teams: Cops and Robbers.\n\nAs a Robber, your mission is to stealthily complete all tasks and escape the vigilant eyes of the Cops. If you come across a captured player, simply touch them to set them free! Utilize vents to activate random abilities that can help you outsmart your pursuers. Teamwork and cunning are key—can you pull off the perfect heist?\n\nAs a Cop, your job is to track down and capture the robbers before they can finish their tasks. Use your kill button to freeze them in place, and trigger powerful random abilities with your shapeshift button to turn the tide in your favor. It’s a game of cat and mouse—can you outsmart the robbers and secure a win for the team?\n\nJoin the chase, strategize with your crew, and may the best team emerge victorious!", "ModeC&R": "Gamemode: Cops And Robbers", "Cop": "Cop", "CopInfo": "Capture all Robbers to win", - "CopInfoLong": "As a cop, use your kill button to capture robbers before they finish their tasks. When caught, robbers freeze in place.\n\nYou can trigger a random ability with the shapeshift button.\nUse/ability cop to check all the abilities.\n\nTrack down the robbers and secure your win!", + "CopInfoLong": "As a cop, use your kill button to capture robbers before they finish their tasks. When caught, robbers freeze in place.\n\nYou can trigger a random ability with the shapeshift button.\nUse /ability cop to check all the abilities.\n\nTrack down the robbers and secure your win!", + "WinnerRoleText.Cop": "Cops Win!", "C&R_NumCops": "Number of Cops", "C&R_CopAbilityTriggerChance": "Cop ability trigger chance", "C&R_CaptureCooldown": "Capture Cooldown", @@ -3643,12 +3646,18 @@ "CopAbility.FlashBang": "Flash Bang", "CopAbility.K9": "K9", "CopAbility.Scope": "Scope", + "Description.HotPursuit": "Increase speed of the Cop.", + "Description.SpikeStrip": "Cop sets a trap, when triggered freezes the robber.", + "Description.FlashBang": "Cop Sets a trap, when triggered blinds the robber.", + "Description.K9": "Cop gets an arrow pointing to the closest robber.", + "Description.Scope": "Increases the capture range of cop.", "CopAbilityText": "Ability", "CopKillButtonText": "Capture", "C&R_NotifyRobbersWhenCaptured": "Notify Robbers when captured", "Robber": "Robber", "RobberInfo": "Complete all tasks to win", - "RobberInfoLong": "As robber, your goal is to complete all tasks and escape the watchful eyes of the cops! If you encounter a captured player, you can touch them to set them free!\n\nWhen you emerge from a vent, you’ll activate a random ability.\nUse/ability robber to check all the abilities. Work together, stay stealthy, and outsmart the cops to pull of a great Heist!", + "RobberInfoLong": "As robber, your goal is to complete all tasks and escape the watchful eyes of the cops! If you encounter a captured player, you can touch them to set them free!\n\nWhen you emerge from a vent, you’ll activate a random ability.\nUse /ability robber to check all the abilities. Work together, stay stealthy, and outsmart the cops to pull of a great Heist!", + "WinnerRoleText.Robber": "Robber Win!", "C&R_RobberVentDuration": "Robber vent duration", "C&R_RobberVentCooldown": "Robber vent cooldown", "C&R_RobberAbilityTriggerChance": "Robber ability trigger chance", @@ -3665,7 +3674,13 @@ "RobberAbility.SmokeBomb": "Smoke Bomb", "RobberAbility.Disguise": "Disguise", "RobberAbility.Radar": "Radar", - + "Description.AdrenalineRush": "Increases the speed of Robber.", + "Description.EnergyShield": "Protects from the capture attempt.", + "Description.SmokeBomb": "Blinds the Cop when robber is captured.", + "Description.Disguise": "Wear the same costume as Cop.", + "Description.Radar": "Points towards closest captured player, if no captured player, it points towards a cop (colored arrows indicating cops or captured).", + "Captured": "Captured", + "Saved": "Saved", "FFA": "Free For All", "ModeFFA": "Gamemode: FFA", diff --git a/Resources/roleColor.json b/Resources/roleColor.json index e4a20597cf..85f66d0833 100644 --- a/Resources/roleColor.json +++ b/Resources/roleColor.json @@ -248,5 +248,6 @@ "Rebirth": "#f08c22", "Sloth": "#376db8", "Eavesdropper": "#ffe6bf", - "Cop": "#007BFF" + "Cop": "#007BFF", + "Robber": "#FF8C00" } From c289b31ff7f74fa49bd4f1f567839b88aabfa783 Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Fri, 4 Oct 2024 18:35:51 +0530 Subject: [PATCH 07/12] add release cd for captured and robber --- GameModes/CopsAndRobbersManager.cs | 51 ++++++++++++++++++++++++++++-- Resources/Lang/en_US.json | 4 +++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index d9b97f14fa..0e76c6c339 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -18,6 +18,8 @@ internal static class CopsAndRobbersManager public static readonly Dictionary captured = []; private static readonly Dictionary capturedScore = []; + private static readonly Dictionary capturedTime = []; + private static readonly Dictionary releaseTime = []; private static readonly Dictionary timesCaptured = []; private static readonly Dictionary saved = []; private static readonly Dictionary defaultSpeed = []; @@ -77,6 +79,8 @@ internal static class CopsAndRobbersManager private static OptionItem CandR_SmokeBombDuration; private static OptionItem CandR_DisguiseChance; private static OptionItem CandR_RadarChance; + private static OptionItem CandR_ReleaseCooldownForCaptured; + private static OptionItem CandR_ReleaseCooldownForRobber; public static void SetupCustomOption() @@ -89,7 +93,7 @@ public static void SetupCustomOption() CandR_NumCops = IntegerOptionItem.Create(Id + 1, "C&R_NumCops", new(1, 5, 1), 2, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)); - CandR_CaptureCooldown = FloatOptionItem.Create(Id + 2, "C&R_CaptureCooldown", new(10f, 60f, 2.5f), 25f, TabGroup.ModSettings, false) + CandR_CaptureCooldown = FloatOptionItem.Create(Id + 2, "C&R_CaptureCooldown", new(5f, 60f, 2.5f), 15f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); @@ -192,7 +196,14 @@ public static void SetupCustomOption() .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); - + CandR_ReleaseCooldownForCaptured = IntegerOptionItem.Create(Id + 33, "C&R_ReleaseCooldownForCaptured", new(5, 20, 1), 5, TabGroup.ModSettings, false) + .SetGameMode (CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + CandR_ReleaseCooldownForRobber = IntegerOptionItem.Create(Id + 34, "C&R_ReleaseCooldownForRobber", new(5, 20, 1), 15, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); CandR_RobberAbilityTriggerChance = IntegerOptionItem.Create(Id + 24, "C&R_RobberAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) @@ -346,6 +357,8 @@ public static void Init() disguise.Clear(); killDistance = Main.RealOptionsData.GetInt(Int32OptionNames.KillDistance); CumulativeAbilityChances(); + capturedTime.Clear(); + releaseTime.Clear(); } public static Dictionary SetRoles() { @@ -398,6 +411,7 @@ private static void Add(this RoleType role, byte playerId) timesCaptured[playerId] = 0; saved[playerId] = 0; numRobbers++; + releaseTime[playerId] = Utils.GetTimeStamp(); return; } } @@ -715,6 +729,8 @@ public static void OnCopAttack(PlayerControl cop, PlayerControl robber) if (!capturedScore.ContainsKey(cop.PlayerId)) capturedScore[cop.PlayerId] = 0; capturedScore[cop.PlayerId]++; + capturedTime[robber.PlayerId] = Utils.GetTimeStamp(); + if (!timesCaptured.ContainsKey(robber.PlayerId)) timesCaptured[robber.PlayerId] = 0; timesCaptured[robber.PlayerId]++; SendCandRData(4, capturedCount: numCaptures); @@ -1029,6 +1045,8 @@ class FixedUpdateInGameModeCandRPatch { private static long LastCheckedCop; private static long LastCheckedRobber; + private static Dictionary LastCheckedReleaseCooldownCaptured = []; + private static Dictionary LastCheckedReleaseCooldownRobber = []; public static void Postfix(PlayerControl __instance) { if (!GameStates.IsInTask || Options.CurrentGameMode != CustomGameMode.CandR) return; @@ -1098,7 +1116,7 @@ public static void Postfix(PlayerControl __instance) // Check if captured release if (captured.Any()) - { + { foreach ((var captureId, Vector2 capturedLocation) in captured) { if (captureId == byte.MaxValue) continue; @@ -1110,6 +1128,32 @@ public static void Postfix(PlayerControl __instance) float dis = Utils.GetDistance(capturedLocation, currentRobberLocation); if (dis < 0.3f) { + var releaseCDforCapture = now - capturedTime[captureId]; + if (releaseCDforCapture < CandR_ReleaseCooldownForCaptured.GetInt()) + { + if (!LastCheckedReleaseCooldownCaptured.ContainsKey(captureId)) LastCheckedReleaseCooldownCaptured[captureId] = now - 1; + if (now != LastCheckedReleaseCooldownCaptured[captureId]) + { + LastCheckedReleaseCooldownCaptured[captureId] = now; + robber.Notify(GetString("C&R_CapturedInReleaseCooldown"), time: CandR_ReleaseCooldownForCaptured.GetInt() - releaseCDforCapture); + Logger.Info($"Time left in release cooldown for captured player, {captureId}: {now - capturedTime[captureId]}", "release canceled"); + } + continue; + } + if (!releaseTime.ContainsKey(robberId)) releaseTime[robberId] = now; + var releaseCDforRobber = now - releaseTime[robberId]; + if (releaseCDforRobber < CandR_ReleaseCooldownForRobber.GetInt()) + { + if (!LastCheckedReleaseCooldownRobber.ContainsKey(robberId)) LastCheckedReleaseCooldownRobber[robberId] = now - 1; + if (now != LastCheckedReleaseCooldownRobber[robberId]) + { + LastCheckedReleaseCooldownRobber[robberId] = now; + robber.Notify(GetString("C&R_RobberInReleaseCooldown"), time: CandR_ReleaseCooldownForRobber.GetInt() - releaseCDforRobber); + Logger.Info($"Time left in release cooldown for robber, {robberId}: {now - releaseTime[robberId]}", "release canceled"); + } + continue; + } + removeCaptured[captureId] = robberId; Logger.Info($"to remove captured {captureId}, rob: {robberId}", "C&R FixedUpdate"); } @@ -1123,6 +1167,7 @@ public static void Postfix(PlayerControl __instance) saved[saviour]++; Utils.GetPlayerById(rescued).RemoveCaptured(); SendCandRData(7, playerId: saviour); + releaseTime[saviour] = now; } removeCaptured.Clear(); numCaptures = captured.Count; diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 88099f8755..70c63267c5 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3682,6 +3682,10 @@ "Description.Radar": "Points towards closest captured player, if no captured player, it points towards a cop (colored arrows indicating cops or captured).", "Captured": "Captured", "Saved": "Saved", + "C&R_CapturedInReleaseCooldown": "Release cooldown for captured player is not finished", + "C&R_RobberInReleaseCooldown": "Your release cooldown is not finished", + "C&R_ReleaseCooldownForCaptured": "Release cooldown for captured player", + "C&R_ReleaseCooldownForRobber": "Release cooldown for robber", "FFA": "Free For All", "ModeFFA": "Gamemode: FFA", From bf2ebb471abc4a52f858150712803df9e261b5fa Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:19:39 +0530 Subject: [PATCH 08/12] add round time --- GameModes/CopsAndRobbersManager.cs | 94 +++++++++++++++++++----------- Patches/CheckGameEndPatch.cs | 10 ++++ Patches/HudPatch.cs | 21 +++++++ Patches/IntroPatch.cs | 1 + Resources/Lang/en_US.json | 1 + 5 files changed, 94 insertions(+), 33 deletions(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index 0e76c6c339..53d5ccbcb3 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -47,6 +47,7 @@ internal static class CopsAndRobbersManager private static int numCops; private static int numCaptures; private static int numRobbers; + public static int RoundTime; public static OptionItem CandR_NumCops; private static OptionItem CandR_CaptureCooldown; @@ -81,96 +82,103 @@ internal static class CopsAndRobbersManager private static OptionItem CandR_RadarChance; private static OptionItem CandR_ReleaseCooldownForCaptured; private static OptionItem CandR_ReleaseCooldownForRobber; + private static OptionItem CandR_GameTime; public static void SetupCustomOption() { + CandR_GameTime = IntegerOptionItem.Create(Id, "CandR_GameTime", new(30, 600, 10), 30, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR) + .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetHeader(true); + /*********** Cops ***********/ - TextOptionItem.Create(Id, "Cop", TabGroup.ModSettings) + TextOptionItem.Create(Id + 1, "Cop", TabGroup.ModSettings) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)); - CandR_NumCops = IntegerOptionItem.Create(Id + 1, "C&R_NumCops", new(1, 5, 1), 2, TabGroup.ModSettings, false) + CandR_NumCops = IntegerOptionItem.Create(Id + 2, "C&R_NumCops", new(1, 5, 1), 2, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)); - CandR_CaptureCooldown = FloatOptionItem.Create(Id + 2, "C&R_CaptureCooldown", new(5f, 60f, 2.5f), 15f, TabGroup.ModSettings, false) + CandR_CaptureCooldown = FloatOptionItem.Create(Id + 3, "C&R_CaptureCooldown", new(5f, 60f, 2.5f), 15f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); - CandR_CopAbilityTriggerChance = IntegerOptionItem.Create(Id + 3, "C&R_CopAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) + CandR_CopAbilityTriggerChance = IntegerOptionItem.Create(Id + 4, "C&R_CopAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent); - CandR_CopAbilityDuration = IntegerOptionItem.Create(Id + 4, "C&R_CopAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) + CandR_CopAbilityDuration = IntegerOptionItem.Create(Id + 5, "C&R_CopAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_CopAbilityTriggerChance); - CandR_CopAbilityCooldown = FloatOptionItem.Create(Id + 5, "C&R_CopAbilityCooldown", new(10f, 60f, 2.5f), 20f, TabGroup.ModSettings, false) + CandR_CopAbilityCooldown = FloatOptionItem.Create(Id + 6, "C&R_CopAbilityCooldown", new(10f, 60f, 2.5f), 20f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_CopAbilityTriggerChance); - CandR_HotPursuitChance = IntegerOptionItem.Create(Id + 6, "C&R_HotPursuitChance", new(0, 100, 5), 35, TabGroup.ModSettings, false) + CandR_HotPursuitChance = IntegerOptionItem.Create(Id + 7, "C&R_HotPursuitChance", new(0, 100, 5), 35, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_HotPursuitSpeed = FloatOptionItem.Create(Id + 7, "C&R_HotPursuitSpeed", new(0f, 2f, 0.25f), 1f, TabGroup.ModSettings, false) + CandR_HotPursuitSpeed = FloatOptionItem.Create(Id + 8, "C&R_HotPursuitSpeed", new(0f, 2f, 0.25f), 1f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_HotPursuitChance); - CandR_SpikeStripChance = IntegerOptionItem.Create(Id + 8, "C&R_SpikeStripChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + CandR_SpikeStripChance = IntegerOptionItem.Create(Id + 9, "C&R_SpikeStripChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_SpikeStripRadius = FloatOptionItem.Create(Id + 9, "C&R_SpikeStripRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) + CandR_SpikeStripRadius = FloatOptionItem.Create(Id + 10, "C&R_SpikeStripRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_SpikeStripChance); - CandR_SpikeStripDuration = IntegerOptionItem.Create(Id + 10, "C&R_SpikeStripDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) + CandR_SpikeStripDuration = IntegerOptionItem.Create(Id + 11, "C&R_SpikeStripDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_SpikeStripChance); - CandR_FlashBangChance = IntegerOptionItem.Create(Id + 11, "C&R_FlashBangChance", new(0, 100, 5), 15, TabGroup.ModSettings, false) + CandR_FlashBangChance = IntegerOptionItem.Create(Id + 12, "C&R_FlashBangChance", new(0, 100, 5), 15, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_FlashBangRadius = FloatOptionItem.Create(Id + 12, "C&R_FlashBangRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) + CandR_FlashBangRadius = FloatOptionItem.Create(Id + 13, "C&R_FlashBangRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_FlashBangChance); - CandR_FlashBangDuration = IntegerOptionItem.Create(Id + 13, "C&R_FlashBangDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) + CandR_FlashBangDuration = IntegerOptionItem.Create(Id + 14, "C&R_FlashBangDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_FlashBangChance); - CandR_ScopeChance = IntegerOptionItem.Create(Id + 14, "C&R_ScopeChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) + CandR_ScopeChance = IntegerOptionItem.Create(Id + 15, "C&R_ScopeChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_ScopeIncrease = IntegerOptionItem.Create(Id + 15, "C&R_ScopeIncrease", new(1, 5, 1), 1, TabGroup.ModSettings, false) + CandR_ScopeIncrease = IntegerOptionItem.Create(Id + 16, "C&R_ScopeIncrease", new(1, 5, 1), 1, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_ScopeChance); - CandR_K9Chance = IntegerOptionItem.Create(Id + 16, "C&R_K9Chance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + CandR_K9Chance = IntegerOptionItem.Create(Id + 17, "C&R_K9Chance", new(0, 100, 5), 20, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) @@ -196,65 +204,65 @@ public static void SetupCustomOption() .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); - CandR_ReleaseCooldownForCaptured = IntegerOptionItem.Create(Id + 33, "C&R_ReleaseCooldownForCaptured", new(5, 20, 1), 5, TabGroup.ModSettings, false) + CandR_ReleaseCooldownForCaptured = IntegerOptionItem.Create(Id + 24, "C&R_ReleaseCooldownForCaptured", new(5, 20, 1), 5, TabGroup.ModSettings, false) .SetGameMode (CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); - CandR_ReleaseCooldownForRobber = IntegerOptionItem.Create(Id + 34, "C&R_ReleaseCooldownForRobber", new(5, 20, 1), 15, TabGroup.ModSettings, false) + CandR_ReleaseCooldownForRobber = IntegerOptionItem.Create(Id + 25, "C&R_ReleaseCooldownForRobber", new(5, 20, 1), 15, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); - CandR_RobberAbilityTriggerChance = IntegerOptionItem.Create(Id + 24, "C&R_RobberAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) + CandR_RobberAbilityTriggerChance = IntegerOptionItem.Create(Id + 26, "C&R_RobberAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent); - CandR_RobberAbilityDuration = IntegerOptionItem.Create(Id + 25, "C&R_RobberAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) + CandR_RobberAbilityDuration = IntegerOptionItem.Create(Id + 27, "C&R_RobberAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_RobberAbilityTriggerChance); - CandR_AdrenalineRushChance = IntegerOptionItem.Create(Id + 26, "C&R_AdrenalineRushChance", new(0, 100, 5), 30, TabGroup.ModSettings, false) + CandR_AdrenalineRushChance = IntegerOptionItem.Create(Id + 28, "C&R_AdrenalineRushChance", new(0, 100, 5), 30, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_RobberAbilityTriggerChance); - CandR_AdrenalineRushSpeed = FloatOptionItem.Create(Id + 27, "C&R_AdrenalineRushSpeed", new(0f, 2f, 0.25f), 1f, TabGroup.ModSettings, false) + CandR_AdrenalineRushSpeed = FloatOptionItem.Create(Id + 29, "C&R_AdrenalineRushSpeed", new(0f, 2f, 0.25f), 1f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_AdrenalineRushChance); - CandR_EnergyShieldChance = IntegerOptionItem.Create(Id + 28, "C&R_EnergyShieldChance", new(0, 100, 5), 25, TabGroup.ModSettings, false) + CandR_EnergyShieldChance = IntegerOptionItem.Create(Id + 30, "C&R_EnergyShieldChance", new(0, 100, 5), 25, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_RobberAbilityTriggerChance); - CandR_SmokeBombChance = IntegerOptionItem.Create(Id + 29, "C&R_SmokeBombChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + CandR_SmokeBombChance = IntegerOptionItem.Create(Id + 31, "C&R_SmokeBombChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_RobberAbilityTriggerChance); - CandR_SmokeBombDuration = IntegerOptionItem.Create(Id + 30, "C&R_SmokeBombDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) + CandR_SmokeBombDuration = IntegerOptionItem.Create(Id + 32, "C&R_SmokeBombDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_SmokeBombChance); - CandR_DisguiseChance = IntegerOptionItem.Create(Id + 31, "C&R_DisguiseChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) + CandR_DisguiseChance = IntegerOptionItem.Create(Id + 33, "C&R_DisguiseChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_RobberAbilityTriggerChance); - CandR_RadarChance = IntegerOptionItem.Create(Id + 32, "C&R_RadarChance", new(0, 100, 5), 15, TabGroup.ModSettings, false) + CandR_RadarChance = IntegerOptionItem.Create(Id + 34, "C&R_RadarChance", new(0, 100, 5), 15, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_RobberAbilityTriggerChance); - roleSettings[CustomRoles.Robber] = [CandR_NotifyRobbersWhenCaptured, CandR_RobberVentDuration, CandR_RobberVentCooldown, CandR_RobberAbilityTriggerChance]; + roleSettings[CustomRoles.Robber] = [CandR_NotifyRobbersWhenCaptured, CandR_RobberVentDuration, CandR_RobberVentCooldown, CandR_ReleaseCooldownForCaptured, CandR_ReleaseCooldownForRobber, CandR_RobberAbilityTriggerChance]; } public enum RoleType @@ -360,6 +368,17 @@ public static void Init() capturedTime.Clear(); releaseTime.Clear(); } + public static void SetData() + { + if (Options.CurrentGameMode != CustomGameMode.CandR) return; + + RoundTime = CandR_GameTime.GetInt() + 8; + var now = Utils.GetTimeStamp() + 8; + foreach (byte robber in robbers) + { + releaseTime[robber] = now; + } + } public static Dictionary SetRoles() { Dictionary finalRoles = []; @@ -411,7 +430,6 @@ private static void Add(this RoleType role, byte playerId) timesCaptured[playerId] = 0; saved[playerId] = 0; numRobbers++; - releaseTime[playerId] = Utils.GetTimeStamp(); return; } } @@ -1039,18 +1057,29 @@ public static void OnPlayerDisconnect(byte playerId) } } } + public static string GetHudText() + { + return string.Format(GetString("FFATimeRemain"), RoundTime.ToString()); + } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] class FixedUpdateInGameModeCandRPatch { private static long LastCheckedCop; private static long LastCheckedRobber; - private static Dictionary LastCheckedReleaseCooldownCaptured = []; - private static Dictionary LastCheckedReleaseCooldownRobber = []; + private static long lastRoundTime; + private static readonly Dictionary LastCheckedReleaseCooldownCaptured = []; + private static readonly Dictionary LastCheckedReleaseCooldownRobber = []; public static void Postfix(PlayerControl __instance) { if (!GameStates.IsInTask || Options.CurrentGameMode != CustomGameMode.CandR) return; + var now = Utils.GetTimeStamp(); + if (lastRoundTime != now) + { + lastRoundTime = now; + RoundTime--; + } if (!AmongUsClient.Instance.AmHost) return; if (__instance.AmOwner) @@ -1082,7 +1111,6 @@ public static void Postfix(PlayerControl __instance) robbers.Remove(byte.MaxValue); captured.Remove(byte.MaxValue); - var now = Utils.GetTimeStamp(); Dictionary removeCaptured = []; foreach (byte robberId in robbers) diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index c83cb2fc9d..4ba8d7ca84 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -676,6 +676,16 @@ public static bool CheckGameEndByLivingPlayers(out GameOverReason reason) // everyone died reason = GameOverReason.ImpostorByKill; + + if (CopsAndRobbersManager.RoundTime <= 0) + { + reason = GameOverReason.HideAndSeek_ByTimer; + ResetAndSetWinner(CustomWinner.Cops); + WinnerIds = [.. CopsAndRobbersManager.cops]; + Logger.Warn("Game end because round time finished", "C&R"); + return true; + } + if (!Main.AllAlivePlayerControls.Any()) { reason = GameOverReason.ImpostorByKill; diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index c792beff97..bcb1f3653b 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -71,6 +71,27 @@ public static void Postfix(HudManager __instance) } LowerInfoText.text = FFAManager.GetHudText(); } + else if (Options.CurrentGameMode == CustomGameMode.CandR) + { + if (LowerInfoText == null) + { + TempLowerInfoText = new GameObject("CountdownText"); + TempLowerInfoText.transform.position = new Vector3(0f, -2f, 1f); + LowerInfoText = TempLowerInfoText.AddComponent(); + //LowerInfoText.text = string.Format(GetString("CountdownText")); + LowerInfoText.alignment = TextAlignmentOptions.Center; + //LowerInfoText = Object.Instantiate(__instance.KillButton.buttonLabelText); + LowerInfoText.transform.parent = __instance.transform; + LowerInfoText.transform.localPosition = new Vector3(0, -2f, 0); + LowerInfoText.overflowMode = TextOverflowModes.Overflow; + LowerInfoText.enableWordWrapping = false; + LowerInfoText.color = Color.white; + LowerInfoText.outlineColor = Color.black; + LowerInfoText.outlineWidth = 20000000f; + LowerInfoText.fontSize = 2f; + } + LowerInfoText.text = CopsAndRobbersManager.GetHudText(); + } if (player.IsAlive()) { // Set default diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 79eace49d6..8a8731027c 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -68,6 +68,7 @@ public static void Prefix() RPC.RpcVersionCheck(); FFAManager.SetData(); + CopsAndRobbersManager.SetData(); } } [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 70c63267c5..0b0c512d31 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3686,6 +3686,7 @@ "C&R_RobberInReleaseCooldown": "Your release cooldown is not finished", "C&R_ReleaseCooldownForCaptured": "Release cooldown for captured player", "C&R_ReleaseCooldownForRobber": "Release cooldown for robber", + "CandR_GameTime": "Maximum Game Length", "FFA": "Free For All", "ModeFFA": "Gamemode: FFA", From ea043632ca48f0a9c84a48e6ea8936509744a227 Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:21:10 +0530 Subject: [PATCH 09/12] oop --- GameModes/CopsAndRobbersManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index 53d5ccbcb3..4c6bc7ebb9 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -87,7 +87,7 @@ internal static class CopsAndRobbersManager public static void SetupCustomOption() { - CandR_GameTime = IntegerOptionItem.Create(Id, "CandR_GameTime", new(30, 600, 10), 30, TabGroup.ModSettings, false) + CandR_GameTime = IntegerOptionItem.Create(Id, "CandR_GameTime", new(30, 600, 10), 300, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) From 9bb00538ed80f7d4eca2717bcc5ba686cbb5858f Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Fri, 4 Oct 2024 23:31:53 +0530 Subject: [PATCH 10/12] setting show chat in game --- GameModes/CopsAndRobbersManager.cs | 41 ++++++++++++++++-------------- Patches/IntroPatch.cs | 14 +++++++++- Resources/Lang/en_US.json | 1 + 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index 4c6bc7ebb9..9e7593fe9f 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -83,102 +83,105 @@ internal static class CopsAndRobbersManager private static OptionItem CandR_ReleaseCooldownForCaptured; private static OptionItem CandR_ReleaseCooldownForRobber; private static OptionItem CandR_GameTime; + public static OptionItem CandR_ShowChatInGame; public static void SetupCustomOption() { CandR_GameTime = IntegerOptionItem.Create(Id, "CandR_GameTime", new(30, 600, 10), 300, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetHeader(true); + CandR_ShowChatInGame = CandR_NotifyRobbersWhenCaptured = BooleanOptionItem.Create(Id + 1, "C&R_ShowChatInGame", true, TabGroup.ModSettings, false) + .SetGameMode(CustomGameMode.CandR); + /*********** Cops ***********/ - TextOptionItem.Create(Id + 1, "Cop", TabGroup.ModSettings) + TextOptionItem.Create(Id + 2, "Cop", TabGroup.ModSettings) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)); - CandR_NumCops = IntegerOptionItem.Create(Id + 2, "C&R_NumCops", new(1, 5, 1), 2, TabGroup.ModSettings, false) + CandR_NumCops = IntegerOptionItem.Create(Id + 3, "C&R_NumCops", new(1, 5, 1), 2, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)); - CandR_CaptureCooldown = FloatOptionItem.Create(Id + 3, "C&R_CaptureCooldown", new(5f, 60f, 2.5f), 15f, TabGroup.ModSettings, false) + CandR_CaptureCooldown = FloatOptionItem.Create(Id + 4, "C&R_CaptureCooldown", new(5f, 60f, 2.5f), 15f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds); - CandR_CopAbilityTriggerChance = IntegerOptionItem.Create(Id + 4, "C&R_CopAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) + CandR_CopAbilityTriggerChance = IntegerOptionItem.Create(Id + 5, "C&R_CopAbilityTriggerChance", new(0, 100, 5), 50, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent); - CandR_CopAbilityDuration = IntegerOptionItem.Create(Id + 5, "C&R_CopAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) + CandR_CopAbilityDuration = IntegerOptionItem.Create(Id + 6, "C&R_CopAbilityDuration", new(1, 10, 1), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_CopAbilityTriggerChance); - CandR_CopAbilityCooldown = FloatOptionItem.Create(Id + 6, "C&R_CopAbilityCooldown", new(10f, 60f, 2.5f), 20f, TabGroup.ModSettings, false) + CandR_CopAbilityCooldown = FloatOptionItem.Create(Id + 7, "C&R_CopAbilityCooldown", new(10f, 60f, 2.5f), 20f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_CopAbilityTriggerChance); - CandR_HotPursuitChance = IntegerOptionItem.Create(Id + 7, "C&R_HotPursuitChance", new(0, 100, 5), 35, TabGroup.ModSettings, false) + CandR_HotPursuitChance = IntegerOptionItem.Create(Id + 8, "C&R_HotPursuitChance", new(0, 100, 5), 35, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_HotPursuitSpeed = FloatOptionItem.Create(Id + 8, "C&R_HotPursuitSpeed", new(0f, 2f, 0.25f), 1f, TabGroup.ModSettings, false) + CandR_HotPursuitSpeed = FloatOptionItem.Create(Id + 9, "C&R_HotPursuitSpeed", new(0f, 2f, 0.25f), 1f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_HotPursuitChance); - CandR_SpikeStripChance = IntegerOptionItem.Create(Id + 9, "C&R_SpikeStripChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + CandR_SpikeStripChance = IntegerOptionItem.Create(Id + 10, "C&R_SpikeStripChance", new(0, 100, 5), 20, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_SpikeStripRadius = FloatOptionItem.Create(Id + 10, "C&R_SpikeStripRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) + CandR_SpikeStripRadius = FloatOptionItem.Create(Id + 11, "C&R_SpikeStripRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_SpikeStripChance); - CandR_SpikeStripDuration = IntegerOptionItem.Create(Id + 11, "C&R_SpikeStripDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) + CandR_SpikeStripDuration = IntegerOptionItem.Create(Id + 12, "C&R_SpikeStripDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_SpikeStripChance); - CandR_FlashBangChance = IntegerOptionItem.Create(Id + 12, "C&R_FlashBangChance", new(0, 100, 5), 15, TabGroup.ModSettings, false) + CandR_FlashBangChance = IntegerOptionItem.Create(Id + 13, "C&R_FlashBangChance", new(0, 100, 5), 15, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_FlashBangRadius = FloatOptionItem.Create(Id + 13, "C&R_FlashBangRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) + CandR_FlashBangRadius = FloatOptionItem.Create(Id + 14, "C&R_FlashBangRadius", new(0.5f, 2f, 0.5f), 1f, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_FlashBangChance); - CandR_FlashBangDuration = IntegerOptionItem.Create(Id + 14, "C&R_FlashBangDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) + CandR_FlashBangDuration = IntegerOptionItem.Create(Id + 15, "C&R_FlashBangDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_FlashBangChance); - CandR_ScopeChance = IntegerOptionItem.Create(Id + 15, "C&R_ScopeChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) + CandR_ScopeChance = IntegerOptionItem.Create(Id + 16, "C&R_ScopeChance", new(0, 100, 5), 10, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) .SetParent(CandR_CopAbilityTriggerChance); - CandR_ScopeIncrease = IntegerOptionItem.Create(Id + 16, "C&R_ScopeIncrease", new(1, 5, 1), 1, TabGroup.ModSettings, false) + CandR_ScopeIncrease = IntegerOptionItem.Create(Id + 17, "C&R_ScopeIncrease", new(1, 5, 1), 1, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Multiplier) .SetParent(CandR_ScopeChance); - CandR_K9Chance = IntegerOptionItem.Create(Id + 17, "C&R_K9Chance", new(0, 100, 5), 20, TabGroup.ModSettings, false) + CandR_K9Chance = IntegerOptionItem.Create(Id + 18, "C&R_K9Chance", new(0, 100, 5), 20, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) .SetColor(new Color32(0, 123, 255, byte.MaxValue)) .SetValueFormat(OptionFormat.Percent) @@ -246,7 +249,7 @@ public static void SetupCustomOption() .SetParent(CandR_RobberAbilityTriggerChance); CandR_SmokeBombDuration = IntegerOptionItem.Create(Id + 32, "C&R_SmokeBombDuration", new(1, 10, 1), 5, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR) - .SetColor(new Color32(0, 123, 255, byte.MaxValue)) + .SetColor(new Color32(255, 140, 0, byte.MaxValue)) .SetValueFormat(OptionFormat.Seconds) .SetParent(CandR_SmokeBombChance); diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 8a8731027c..9c867b7ccc 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -725,12 +725,24 @@ public static void Postfix() bool chatVisible = Options.CurrentGameMode switch { CustomGameMode.FFA => true, - CustomGameMode.CandR => true, + CustomGameMode.CandR => CopsAndRobbersManager.CandR_ShowChatInGame.GetBool(), + _ => false + }; + bool shouldAntiBlackOut = Options.CurrentGameMode switch + { + CustomGameMode.CandR => CopsAndRobbersManager.CandR_ShowChatInGame.GetBool(), _ => false }; try { if (chatVisible) Utils.SetChatVisibleForEveryone(); + if (shouldAntiBlackOut) + { + _ = new LateTask(() => + { + AntiBlackout.SetIsDead(); + }, 4f, "anti blackout"); + } } catch (Exception error) { diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 0b0c512d31..2a68f169cd 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3687,6 +3687,7 @@ "C&R_ReleaseCooldownForCaptured": "Release cooldown for captured player", "C&R_ReleaseCooldownForRobber": "Release cooldown for robber", "CandR_GameTime": "Maximum Game Length", + "C&R_ShowChatInGame": "Show chat in game (may cause black screens)", "FFA": "Free For All", "ModeFFA": "Gamemode: FFA", From 216d307c5e8cfb08468f876a4863ed2458c9c00b Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Fri, 4 Oct 2024 23:34:20 +0530 Subject: [PATCH 11/12] change default --- GameModes/CopsAndRobbersManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GameModes/CopsAndRobbersManager.cs b/GameModes/CopsAndRobbersManager.cs index 9e7593fe9f..87ba0d2cb6 100644 --- a/GameModes/CopsAndRobbersManager.cs +++ b/GameModes/CopsAndRobbersManager.cs @@ -93,7 +93,7 @@ public static void SetupCustomOption() .SetValueFormat(OptionFormat.Seconds) .SetHeader(true); - CandR_ShowChatInGame = CandR_NotifyRobbersWhenCaptured = BooleanOptionItem.Create(Id + 1, "C&R_ShowChatInGame", true, TabGroup.ModSettings, false) + CandR_ShowChatInGame = CandR_NotifyRobbersWhenCaptured = BooleanOptionItem.Create(Id + 1, "C&R_ShowChatInGame", false, TabGroup.ModSettings, false) .SetGameMode(CustomGameMode.CandR); /*********** Cops ***********/ From b50f8dbfced3cc681972ebee4ac90a8714a1d1ef Mon Sep 17 00:00:00 2001 From: ryuk201198 <138619164+ryuk201198@users.noreply.github.com> Date: Sat, 5 Oct 2024 16:05:41 +0530 Subject: [PATCH 12/12] unhide random spawn mode --- Patches/RandomSpawnPatch.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Patches/RandomSpawnPatch.cs b/Patches/RandomSpawnPatch.cs index d098564a10..442b5d70e7 100644 --- a/Patches/RandomSpawnPatch.cs +++ b/Patches/RandomSpawnPatch.cs @@ -167,7 +167,6 @@ public static void SetupCustomOption() { RandomSpawnMode = BooleanOptionItem.Create(60470, RandomSpawnOpt.RandomSpawnMode, false, TabGroup.ModSettings, false) .HideInFFA() - .HideInCandR() .SetColor(new Color32(19, 188, 233, byte.MaxValue)); SpawnInFirstRound = BooleanOptionItem.Create(60476, RandomSpawnOpt.RandomSpawn_SpawnInFirstRound, true, TabGroup.ModSettings, false) .SetParent(RandomSpawnMode);