From ca5b6bf3522cbb2de2ebad9a3f31e76bdfebcd01 Mon Sep 17 00:00:00 2001 From: mxyx0412 Date: Fri, 3 Jan 2025 07:33:28 +0800 Subject: [PATCH] Update v1.1.1.1 --- CHANGELOG.md | 26 +- Strings.xlsx | Bin 84946 -> 85431 bytes TheOtherRoles/Objects/Trap.cs | 3 +- TheOtherRoles/Options/CustomOptionHolder.cs | 14 + TheOtherRoles/Patches/EndGamePatch.cs | 9 +- TheOtherRoles/Patches/ExileControllerPatch.cs | 63 +- TheOtherRoles/Patches/MeetingHudPatch.cs | 17 +- TheOtherRoles/Patches/PlayerControlPatch.cs | 1491 ++++++++--------- TheOtherRoles/Patches/PlayerPhysicsPatch.cs | 2 +- TheOtherRoles/Patches/RoleAssignmentPatch.cs | 13 + TheOtherRoles/Patches/UpdatePatch.cs | 30 +- TheOtherRoles/RPC.cs | 175 +- TheOtherRoles/Resources/stringData.json | 25 +- TheOtherRoles/Roles/Ghost/Specter.cs | 5 + TheOtherRoles/Roles/Guesser.cs | 162 +- TheOtherRoles/Roles/Impostor/WolfLord.cs | 167 ++ TheOtherRoles/Roles/Modifier/Chameleon.cs | 9 +- TheOtherRoles/Roles/Modifier/Giant.cs | 2 +- TheOtherRoles/Roles/Modifier/Radar.cs | 3 - TheOtherRoles/Roles/Modifier/Vortox.cs | 25 + TheOtherRoles/Roles/Neutral/Amnisiac.cs | 6 + TheOtherRoles/Roles/Neutral/Lawyer.cs | 13 +- TheOtherRoles/Roles/Neutral/Thief.cs | 1 + TheOtherRoles/Roles/Neutral/Witness.cs | 19 +- TheOtherRoles/Roles/RoleHelpers.cs | 3 + TheOtherRoles/Roles/RoleInfo.cs | 16 +- TheOtherRoles/TheOtherRoles.csproj | 2 +- TheOtherRoles/Utilities/MapData.cs | 45 +- 28 files changed, 1249 insertions(+), 1097 deletions(-) create mode 100644 TheOtherRoles/Roles/Impostor/WolfLord.cs create mode 100644 TheOtherRoles/Roles/Modifier/Vortox.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cbf59f9..82c91968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,8 @@ -v1.1.0.8更新日志 +v1.1.1.1更新日志 -* 新增选项:"赌怪猜测时返还所有投票"。 -* 新增选项:"赌怪猜测时延长会议时间"。 -* 优化随机出生,游戏开始时会缓存出生点,使其不会因新增的管道出生点而出现玩家卡墙问题。 -* 赌怪猜测完毕时,如果其他玩家的赌怪界面没有关闭会强制关闭,避免多个玩家同时出赌的问题。 -* 交换师:交换失败时,将会延迟到会议结束的驱逐动画结束后判定。 -* 抹除者:抹除玩家后,将会延迟到会议结束的驱逐动画结束后判定。 -* 女巫:咒杀玩家时,目标玩家将会延迟到会议结束的驱逐动画结束后死亡。 -* 告密者:告密者已死亡时,即使告密者任务完成,其他玩家也不会看到告密者名称颜色。 -* 设陷师:优化陷阱日志,在陷阱触发时发送该踩到该陷阱的玩家职业信息。 -* 情报师:查验消息发送时间提前,当轮会议查验将会在会议结束后发送查验消息。 -* 忍者:修复隐身期间,其他玩家按钮可以看到忍者的玩家名称问题。 -* 纵火狂:修复点火按钮的冷却问题 -* 纵火狂:在仅剩纵火狂一个杀手时,点火按钮最大冷却更改为2.5s,基础冷却0s (但需要按照房间对应设置才可生效)。 -* 处刑者:修复处刑目标在会议中死亡时,处刑者不会变为起诉人的问题。 -* 入殓师:优化选项,独立附加能力的选项,且附加能力的入殓师启用后,强制禁用主职业入殓师。 -* 分散者:修复分散时,其他玩家如果在管道内,不会被强制跳出管道的问题。 -* 专业刺客:新增选项:"更改为公共附加能力",启用后,在赌怪模式中,所有赌怪都有可能获得该附加能力。 -* 巨人:不再修改碰撞体积,以优化卡墙的问题。 -* 怨灵:修复在会议中死亡的玩家获得怨灵时可以看到其他玩家职业信息的问题。 -* 怨灵:删除怨灵在会议外的聊天按钮,并无法接收到赌怪猜测信息和查验信息等。 +* 新增伪装者职业:狼之主 +* 新增附加能力:迷乱旋涡 +* 修复巨人的体型在其他玩家不可见的问题 +* 修复分散者的坐标问题 注:本次仅更新Lite分支 \ No newline at end of file diff --git a/Strings.xlsx b/Strings.xlsx index 4c937fbea870fa0d4fecf817cdc124e4cd1df80b..3857c3a07489c4d6ea11b30ad5b6ca0bc87d3668 100644 GIT binary patch delta 43512 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 UNdzxNXqg(28D&lR)g17D0O)sFzW@LL delta 43296 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 diff --git a/TheOtherRoles/Objects/Trap.cs b/TheOtherRoles/Objects/Trap.cs index 5a55d944..6526c5fd 100644 --- a/TheOtherRoles/Objects/Trap.cs +++ b/TheOtherRoles/Objects/Trap.cs @@ -124,7 +124,7 @@ public static void triggerTrap(byte playerId, byte trapId) message = trap.trappedPlayer.Aggregate(message, (current, p) => current + Trapper.infoType switch { 0 => RoleInfo.GetRolesString(p, false, false, false) + "\n", - 1 when isEvilNeutral(p) || p.Data.Role.IsImpostor => "邪恶职业 \n", + 1 when (isEvilNeutral(p) || p.isImpostor()) ^ Vortox.Reversal => "邪恶职业 \n", 1 => "善良职业 \n", _ => p.Data.PlayerName + "\n" }); @@ -132,7 +132,6 @@ 1 when isEvilNeutral(p) || p.Data.Role.IsImpostor => "邪恶职业 \n", FastDestroyableSingleton.Instance.Chat.AddChat(Trapper.trapper, $"{message}"); } } - Trapper.playersOnMap = new List(); } diff --git a/TheOtherRoles/Options/CustomOptionHolder.cs b/TheOtherRoles/Options/CustomOptionHolder.cs index cbe6f365..053c383d 100644 --- a/TheOtherRoles/Options/CustomOptionHolder.cs +++ b/TheOtherRoles/Options/CustomOptionHolder.cs @@ -40,6 +40,8 @@ public class CustomOptionHolder public static CustomOption yoyoAdminTableCooldown; public static CustomOption yoyoSilhouetteVisibility; + public static CustomOption wolfLordSpawnRate; + public static CustomOption morphlingSpawnRate; public static CustomOption morphlingCooldown; public static CustomOption morphlingDuration; @@ -70,6 +72,10 @@ public class CustomOptionHolder public static CustomOption poucherSpawnRate; public static CustomOption modifierPoucher; + public static CustomOption modifierVortox; + public static CustomOption modifierVortoxSkipMeeting; + public static CustomOption modifierVortoxSkipNum; + public static CustomOption butcherSpawnRate; public static CustomOption butcherDissectionCooldown; public static CustomOption butcherDissectionDuration; @@ -390,6 +396,7 @@ public class CustomOptionHolder public static CustomOption witnessMarkTimer; public static CustomOption witnessWinCount; public static CustomOption witnessMeetingDie; + public static CustomOption witnessSkipMeeting; public static CustomOption balancerSpawnRate; public static CustomOption balancerCount; @@ -787,6 +794,8 @@ public static void Load() //-------------------------- Impostor Options 10000-19999 -------------------------- // + wolfLordSpawnRate = Create(10100, Types.Impostor, cs(WolfLord.color, "WolfLord"), rates, null, true); + morphlingSpawnRate = Create(10110, Types.Impostor, cs(Morphling.color, "Morphling"), rates, null, true); morphlingCooldown = Create(10111, Types.Impostor, "morphlingCooldown", 15f, 10f, 60f, 2.5f, morphlingSpawnRate); morphlingDuration = Create(10112, Types.Impostor, "morphlingDuration", 15f, 1f, 20f, 0.5f, morphlingSpawnRate); @@ -947,6 +956,7 @@ public static void Load() witnessMarkTimer = Create(20302, Types.Neutral, "witnessMarkTimer", 30, 20, 90, 5, witnessSpawnRate); witnessWinCount = Create(20303, Types.Neutral, "witnessWinCount", 2, 1, 6, 1, witnessSpawnRate); witnessMeetingDie = Create(20304, Types.Neutral, "witnessMeetingDie", true, witnessSpawnRate); + witnessSkipMeeting = Create(20305, Types.Neutral, "witnessSkipMeeting", true, witnessSpawnRate); jackalSpawnRate = Create(20130, Types.Neutral, cs(Jackal.color, "Jackal"), rates, null, true); jackalChanceSwoop = Create(20142, Types.Neutral, cs(Swooper.color, "jackalChanceSwoop"), rates, jackalSpawnRate); @@ -1267,6 +1277,10 @@ public static void Load() poucherSpawnRate.selection = 0; }); + modifierVortox = Create(40380, Types.Modifier, cs(Vortox.color, "Vortox"), rates, null, true); + modifierVortoxSkipMeeting = Create(40381, Types.Modifier, "modifierVortoxSkipMeeting", true, modifierVortox); + modifierVortoxSkipNum = Create(40382, Types.Modifier, "modifierVortoxSkipNum", 4, 1, 10, 1, modifierVortoxSkipMeeting); + modifierLastImpostor = Create(40110, Types.Modifier, cs(Palette.ImpostorRed, "LastImpostor"), false, null, true); modifierLastImpostorDeduce = Create(40111, Types.Modifier, "modifierLastImpostorDeduce", 5f, 2.5f, 15f, 2.5f, modifierLastImpostor); diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs index 1e7cf135..c6ffdc88 100644 --- a/TheOtherRoles/Patches/EndGamePatch.cs +++ b/TheOtherRoles/Patches/EndGamePatch.cs @@ -183,7 +183,8 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En var miniLose = Mini.mini != null && gameOverReason == (GameOverReason)CustomGameOverReason.MiniLose; var jesterWin = Jester.jester != null && gameOverReason == (GameOverReason)CustomGameOverReason.JesterWin; var witnessWin = Witness.Player != null && gameOverReason == (GameOverReason)CustomGameOverReason.WitnessWin; - var impostorWin = gameOverReason is GameOverReason.ImpostorByKill or GameOverReason.ImpostorBySabotage or GameOverReason.ImpostorByVote; + var impostorWin = (gameOverReason is GameOverReason.ImpostorByKill or GameOverReason.ImpostorBySabotage or GameOverReason.ImpostorByVote) + || Vortox.triggerImpWin; var werewolfWin = gameOverReason == (GameOverReason)CustomGameOverReason.WerewolfWin && Werewolf.werewolf.IsAlive(); var juggernautWin = gameOverReason == (GameOverReason)CustomGameOverReason.JuggernautWin && Juggernaut.juggernaut.IsAlive(); var swooperWin = gameOverReason == (GameOverReason)CustomGameOverReason.SwooperWin && Swooper.swooper.IsAlive(); @@ -485,7 +486,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En TempData.winners.Add(new WinningPlayerData(PartTimer.partTimer.Data)); AdditionalTempData.additionalWinConditions.Add(WinCondition.AdditionalPartTimerWin); } - Message($"游戏结束 {AdditionalTempData.winCondition}"); + Message($"游戏结束 {AdditionalTempData.winCondition}", "OnGameEnd"); // Reset Settings RPCProcedure.resetVariables(); } @@ -1038,7 +1039,7 @@ private static bool CheckAndEndGameForJuggernautWin(ShipStatus __instance, Playe private static bool CheckAndEndGameForImpostorWin(ShipStatus __instance, PlayerStatistics statistics) { - if (statistics.TeamImpostorsAlive >= statistics.TotalAlive - statistics.TeamImpostorsAlive && + if (Vortox.triggerImpWin || (statistics.TeamImpostorsAlive >= statistics.TotalAlive - statistics.TeamImpostorsAlive && statistics.TeamJackalAlive == 0 && statistics.TeamPavlovsAlive == 0 && statistics.TeamWerewolfAlive == 0 && @@ -1046,7 +1047,7 @@ private static bool CheckAndEndGameForImpostorWin(ShipStatus __instance, PlayerS statistics.TeamArsonistAlive == 0 && statistics.TeamAkujoAlive == 0 && statistics.TeamJuggernautAlive == 0 && - !(statistics.TeamImpostorHasAliveLover && statistics.TeamLoversAlive == 2) && !killingCrewAlive()) + !(statistics.TeamImpostorHasAliveLover && statistics.TeamLoversAlive == 2) && !killingCrewAlive())) { //__instance.enabled = false; GameOverReason endReason; diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index 92d7057c..2215e4d7 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -64,9 +64,25 @@ void createlate(int index) Helpers.SetActiveAllObject(controller.gameObject.GetChildren(), "RaftAnimation", false); controller.transform.localPosition = new(-3.75f, -0.2f, -60f); } + if (Lawyer.lawyer != null && exiled?.Object.PlayerId == Lawyer.target.PlayerId && Lawyer.target != Jester.jester) + { + var writer = StartRPC(PlayerControl.LocalPlayer, CustomRPC.LawyerPromotesToPursuer); + writer.Write(true); + writer.EndRPC(); + Lawyer.PromotesToPursuer(true); + } + if (!IsSec) return true; } + if (Lawyer.lawyer != null && exiled?.Object.PlayerId == Lawyer.target.PlayerId && Lawyer.target != Jester.jester) + { + var writer = StartRPC(PlayerControl.LocalPlayer, CustomRPC.LawyerPromotesToPursuer); + writer.Write(true); + writer.EndRPC(); + Lawyer.PromotesToPursuer(true); + } + // Medic shield if (Medic.medic != null && AmongUsClient.Instance.AmHost && Medic.futureShielded != null && !Medic.medic.Data.IsDead) { @@ -176,7 +192,7 @@ internal class ExileControllerWrapUpPatch public static void Prefix(GameObject obj) { // Nightvision: - if (obj?.name != null && obj.name.Contains("FungleSecurity")) + if (obj != null && obj.name != null && obj.name.Contains("FungleSecurity")) { SurveillanceMinigamePatch.resetNightVision(); return; @@ -227,27 +243,31 @@ private static void WrapUpPostfix(GameData.PlayerInfo exiled) AmongUsClient.Instance.FinishRpcImmediately(writer); Executioner.PromotesRole(); } - else if (Witness.Player == CachedPlayer.LocalPlayer.PlayerControl && Witness.target != null && Witness.killerTarget != null) + if (Witness.target != null && Witness.killerTarget != null) { - bool skip = exiled == null; + bool skip = exiled == null && Witness.skipMeeting; bool targetIsKillerAndNotExiled = Witness.target == Witness.killerTarget && (exiled?.Object == null || Witness.target != exiled?.Object); - bool targetIsExiledAndNotKiller = (Witness.target == exiled?.Object || (Witness.meetingDie && Witness.target.IsDead())) - && Witness.target != Witness.killerTarget; + bool targetIsExiledAndNotKiller = Witness.target != Witness.killerTarget && (Witness.target == exiled?.Object || + (Witness.meetingDie && Witness.target.IsDead())); - if (targetIsKillerAndNotExiled || targetIsExiledAndNotKiller) + if ((!skip && targetIsKillerAndNotExiled) || targetIsExiledAndNotKiller) { Witness.exiledCount++; } if (Witness.exiledCount == Witness.exileToWin) { - var writer = StartRPC(CachedPlayer.LocalPlayer.PlayerControl, CustomRPC.WitnessWin); - writer.EndRPC(); Witness.triggerWitnessWin = true; } } Witness.target = Witness.killerTarget = null; + if (Vortox.Player.IsAlive()) + { + Vortox.skipCount++; + if (Vortox.skipCount == Vortox.skipMeetingNum) Vortox.triggerImpWin = true; + } + // Reset custom button timers where necessary CustomButton.MeetingEndedUpdate(); @@ -292,7 +312,6 @@ private static void WrapUpPostfix(GameData.PlayerInfo exiled) }))); } } - Seer.deadBodyPositions = new List(); } @@ -436,18 +455,16 @@ private static void WrapUpPostfix(GameData.PlayerInfo exiled) if (InfoSleuth.infoSleuth != null && InfoSleuth.target != null && InfoSleuth.infoSleuth == PlayerControl.LocalPlayer) { - string msg; - var random = rnd.Next(2); - var isNotCrew = isNeutral(InfoSleuth.target) || InfoSleuth.target.isImpostor(); - var team = "的阵营是 " + teamString(InfoSleuth.target); + var isNotCrew = (isNeutral(InfoSleuth.target) || InfoSleuth.target.isImpostor()) ^ Vortox.Reversal; + var team = "的阵营是 " + getTeam(InfoSleuth.target); var info = InfoSleuth.infoType switch { 0 => isNotCrew ? "不是船员" : "是船员", 1 => team, - _ => random == 0 ? isNotCrew ? "不是船员" : "是船员" : team, + _ => rnd.Next(2) == 0 ? isNotCrew ? "不是船员" : "是船员" : team, }; - msg = $"{InfoSleuth.target.Data.PlayerName} {info}"; + string msg = $"{InfoSleuth.target.Data.PlayerName} {info}"; FastDestroyableSingleton.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}"); var writer = StartRPC(PlayerControl.LocalPlayer, CustomRPC.ShareGhostInfo); @@ -459,6 +476,19 @@ private static void WrapUpPostfix(GameData.PlayerInfo exiled) var writer1 = StartRPC(PlayerControl.LocalPlayer, CustomRPC.InfoSleuthNoTarget); writer1.EndRPC(); RPCProcedure.infoSleuthNoTarget(); + + static string getTeam(PlayerControl player) + { + if (Vortox.Player.IsAlive()) + { + if (player.isCrew()) return rnd.Next(2) == 0 ? "NeutralRolesText".Translate() : "ImpostorRolesText".Translate(); + if (isNeutral(player) || player.isImpostor()) return "CrewmateRolesText".Translate(); + } + + return isNeutral(player) ? "NeutralRolesText".Translate() + : player.isImpostor() ? "ImpostorRolesText".Translate() + : "CrewmateRolesText".Translate(); + } } // Invert add meeting @@ -471,8 +501,7 @@ private static void WrapUpPostfix(GameData.PlayerInfo exiled) (GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown / 2) + 2, new Action(p => { if (p == 1f) foreach (var trap in Trap.traps) trap.triggerable = true; }))); - if (!Yoyo.markStaysOverMeeting) - Silhouette.clearSilhouettes(); + if (!Yoyo.markStaysOverMeeting) Silhouette.clearSilhouettes(); if (AmongUsClient.Instance.AmHost) { diff --git a/TheOtherRoles/Patches/MeetingHudPatch.cs b/TheOtherRoles/Patches/MeetingHudPatch.cs index 7481aed5..c8e21168 100644 --- a/TheOtherRoles/Patches/MeetingHudPatch.cs +++ b/TheOtherRoles/Patches/MeetingHudPatch.cs @@ -136,14 +136,7 @@ private static void mayorToggleVoteTwice(MeetingHud __instance) __instance.playerStates[0].Cancel(); // This will stop the underlying buttons of the template from showing up if (__instance.state == VoteStates.Results || Mayor.mayor.Data.IsDead) return; - // Only accept changes until the mayor voted - var mayorPVA = __instance.playerStates.FirstOrDefault(x => x.TargetPlayerId == Mayor.mayor.PlayerId); - if (Mayor.Revealed && mayorPVA != null && mayorPVA.DidVote) - { - SoundEffectsManager.play("fail"); - return; - } - Mayor.Revealed = !Mayor.Revealed; + Mayor.Revealed = true; var writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.MayorRevealed, SendOption.Reliable); @@ -261,7 +254,8 @@ private static void populateButtonsPostfix(MeetingHud __instance) // Add Guesser Buttons var GuesserRemainingShots = HandleGuesser.remainingShots(CachedPlayer.LocalPlayer.PlayerId); - if (!isGuesser || CachedPlayer.LocalPlayer.Data.IsDead || GuesserRemainingShots <= 0) return; + if (!isGuesser || CachedPlayer.LocalPlayer.IsDead || GuesserRemainingShots <= 0 || + (PlayerControl.LocalPlayer == WolfLord.Player && WolfLord.Revealed)) return; { Doomsayer.CanShoot = true; for (var i = 0; i < __instance.playerStates.Length; i++) @@ -515,7 +509,7 @@ private class MeetingHudBloopAVoteIconPatch public static bool Prefix(MeetingHud __instance, GameData.PlayerInfo voterPlayer, int index, Transform parent) { var spriteRenderer = Object.Instantiate(__instance.PlayerVotePrefab); - var showVoteColors = !GameManager.Instance.LogicOptions.GetAnonymousVotes() || CachedPlayer.LocalPlayer.Data.IsDead || + var showVoteColors = !GameManager.Instance.LogicOptions.GetAnonymousVotes() || shouldShowGhostInfo() || (Prosecutor.prosecutor != null && Prosecutor.prosecutor == CachedPlayer.LocalPlayer.PlayerControl && Prosecutor.canSeeVoteColors && TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data).Item1 >= Prosecutor.tasksNeededToSeeVoteColors) || @@ -734,8 +728,7 @@ private static void Postfix(MeetingHud __instance) [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Deserialize))] private class MeetingDeserializePatch { - private static void Postfix(MeetingHud __instance, [HarmonyArgument(0)] MessageReader reader, - [HarmonyArgument(1)] bool initialState) + private static void Postfix(MeetingHud __instance, [HarmonyArgument(0)] MessageReader reader, [HarmonyArgument(1)] bool initialState) { // Add swapper buttons if (initialState) populateButtonsPostfix(__instance); diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index 7d5b6a5b..131cf4d7 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -26,10 +26,11 @@ private static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPl List untargetablePlayers = null, PlayerControl targetingPlayer = null) { PlayerControl result = null; - var num = GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; + var num = GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 3)]; if (!MapUtilities.CachedShipStatus) return result; if (targetingPlayer == null) targetingPlayer = CachedPlayer.LocalPlayer.PlayerControl; if (targetingPlayer.Data.IsDead) return result; + if (PlayerControl.LocalPlayer == Arsonist.arsonist) num += 0.5f; var truePosition = targetingPlayer.GetTruePosition(); foreach (var playerInfo in GameData.Instance.AllPlayers.GetFastEnumerator()) @@ -120,6 +121,127 @@ private static void setBasePlayerOutlines() } } + public static void updatePlayerInfo() + { + var local = CachedPlayer.LocalPlayer.PlayerControl; + foreach (PlayerControl p in CachedPlayer.AllPlayers) + { + var playerVoteArea = MeetingHud.Instance?.playerStates?.FirstOrDefault(x => x.TargetPlayerId == p.PlayerId); + if (playerVoteArea != null && playerVoteArea.ColorBlindName.gameObject.active) + { + playerVoteArea.ColorBlindName.transform.localPosition = new Vector3(-0.93f, -0.2f, -0.1f); + playerVoteArea.ColorBlindName.fontSize *= 1.75f; + } + p.cosmetics.nameText.transform.parent.SetLocalZ(-0.0001f); + + bool canSeeRole = (Lawyer.lawyerKnowsRole && local == Lawyer.lawyer && p == Lawyer.target) || + (PartTimer.knowsRole && local == PartTimer.partTimer && p == PartTimer.target) || + (local == PartTimer.target && p == PartTimer.partTimer) || + (Akujo.knowsRoles && local == Akujo.akujo && + (p == Akujo.honmei || Akujo.keeps.Any(x => x.PlayerId == p.PlayerId))) || + (ModOption.impostorSeeRoles && Spy.spy == null && PlayerControl.LocalPlayer.isImpostor() && + PlayerControl.LocalPlayer.IsAlive() && p.isImpostor() && p.IsAlive()); + + bool reported = ((local == Slueth.slueth && Slueth.reported.Any(x => x.PlayerId == p.PlayerId)) || + (local == Poucher.poucher && Poucher.killed.Any(x => x.PlayerId == p.PlayerId))) && p.IsDead(); + + bool revealed = (Mayor.mayor == p && Mayor.Revealed) || (WolfLord.Player == p && WolfLord.Revealed); + + if (p == local || local.Data.IsDead || canSeeRole || reported || revealed) + { + var roleNames = RoleInfo.GetRolesString(p, true, false, false, true); + var mainRole = RoleInfo.GetRolesString(p, true, false, false, false); + var allRoleText = RoleInfo.GetRolesString(p, true, true, true, true); + + var playerInfoTransform = p.cosmetics.nameText.transform.parent.FindChild("Info"); + var playerInfo = playerInfoTransform != null ? playerInfoTransform.GetComponent() : null; + if (playerInfo == null) + { + playerInfo = Object.Instantiate(p.cosmetics.nameText, p.cosmetics.nameText.transform.parent); + playerInfo.transform.localPosition += Vector3.up * 0.225f; + playerInfo.fontSize *= 0.8f; + playerInfo.gameObject.name = "Info"; + playerInfo.color = playerInfo.color.SetAlpha(1f); + } + + var meetingInfoTransform = playerVoteArea != null ? playerVoteArea.NameText.transform.parent.FindChild("Info") : null; + var meetingInfo = meetingInfoTransform != null ? meetingInfoTransform.GetComponent() : null; + + if (meetingInfo == null && playerVoteArea != null) + { + meetingInfo = Object.Instantiate(playerVoteArea.NameText, playerVoteArea.NameText.transform.parent); + meetingInfo.transform.localPosition += Vector3.down * 0.2f; + meetingInfo.fontSize *= 0.64f; + meetingInfo.gameObject.name = "Info"; + } + + // Set player name higher to align in middle + if (meetingInfo != null && playerVoteArea != null) + { + var playerName = playerVoteArea.NameText; + playerName.transform.localPosition = new Vector3(0.3384f, 0.0311f, -0.1f); + } + + var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data); + var taskInfo = tasksTotal > 0 ? $"({tasksCompleted}/{tasksTotal})" : ""; + + var playerInfoText = ""; + var meetingInfoText = ""; + if (p == local) + { + if (p.Data.IsDead) roleNames = allRoleText; + playerInfoText = $"{roleNames}"; + if (HudManager.Instance.TaskPanel != null) + { + var tabText = HudManager.Instance.TaskPanel.tab.transform.FindChild("TabText_TMP").GetComponent(); + tabText.SetText(string.Format("tasksNum".Translate(), taskInfo)); + } + meetingInfoText = $"{allRoleText} {taskInfo}".Trim(); + } + else if (reported) + { + meetingInfoText = playerInfoText = mainRole; + } + else if (local.IsAlive() && Mayor.mayor == p && Mayor.Revealed) + { + meetingInfoText = cs(Mayor.color, "Mayor".Translate()); + } + else if (local.IsAlive() && WolfLord.Player == p && WolfLord.Revealed) + { + meetingInfoText = cs(WolfLord.color, "WolfLord".Translate()); + } + else if (canSeeRole) + { + meetingInfoText = playerInfoText = roleNames; + } + else + { + if (CanSeeRoleInfo) + { + playerInfoText = $"{allRoleText} {taskInfo}".Trim(); + meetingInfoText = playerInfoText; + } + } + + playerInfo.text = playerInfoText; + playerInfo.gameObject.SetActive(p.Visible); + if (meetingInfo != null) + { + meetingInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : meetingInfoText; + } + } + else + { + if (local != p && p != null) + { + var playerInfoTransform = p.cosmetics.nameText.transform.parent.FindChild("Info"); + var playerInfo = playerInfoTransform?.GetComponent(); + if (playerInfo != null) playerInfo.text = ""; + } + } + } + } + private static void setPetVisibility() { var localalive = !CachedPlayer.LocalPlayer.Data.IsDead; @@ -184,96 +306,6 @@ public static void bendTimeUpdate() } } - private static void medicSetTarget() - { - if (Medic.medic == null || Medic.medic != CachedPlayer.LocalPlayer.PlayerControl) return; - Medic.currentTarget = setTarget(); - if (!Medic.usedShield) setPlayerOutline(Medic.currentTarget, Medic.shieldedColor); - } - - private static void partTimerSetTarget() - { - if (PartTimer.partTimer == null || PartTimer.partTimer != CachedPlayer.LocalPlayer.PlayerControl) return; - PartTimer.currentTarget = setTarget(); - if (PartTimer.target != null) setPlayerOutline(PartTimer.currentTarget, PartTimer.color); - } - - private static void bomberSetTarget() - { - setBomberBombTarget(); - if (Bomber.bomber == null || Bomber.bomber != CachedPlayer.LocalPlayer.PlayerControl) return; - Bomber.currentTarget = setTarget(); - if (Bomber.hasBombPlayer == null) setPlayerOutline(Bomber.currentTarget, Bomber.color); - } - - private static void setBomberBombTarget() - { - if (Bomber.bomber == null || Bomber.hasBombPlayer != CachedPlayer.LocalPlayer.PlayerControl) return; - Bomber.currentBombTarget = setTarget(); - //if (Bomber.hasBomb != null) setPlayerOutline(Bomber.currentBombTarget, Bomber.color); - } - - private static void bodyGuardSetTarget() - { - if (BodyGuard.bodyguard == null || BodyGuard.bodyguard != CachedPlayer.LocalPlayer.PlayerControl) return; - BodyGuard.currentTarget = setTarget(); - if (!BodyGuard.usedGuard) setPlayerOutline(Medic.currentTarget, Medic.shieldedColor); - } - - private static void werewolfSetTarget() - { - if (Werewolf.werewolf == null || Werewolf.werewolf != CachedPlayer.LocalPlayer.PlayerControl) return; - Werewolf.currentTarget = setTarget(); - } - - private static void juggernautSetTarget() - { - if (Juggernaut.juggernaut == null || Juggernaut.juggernaut != CachedPlayer.LocalPlayer.PlayerControl) return; - Juggernaut.currentTarget = setTarget(); - } - - - private static void doomsayerSetTarget() - { - if (Doomsayer.doomsayer == null || Doomsayer.doomsayer != CachedPlayer.LocalPlayer.PlayerControl) return; - Doomsayer.currentTarget = setTarget(); - } - - private static void blackMailerSetTarget() - { - if (Blackmailer.blackmailer == null || - Blackmailer.blackmailer != CachedPlayer.LocalPlayer.PlayerControl) return; - Blackmailer.currentTarget = setTarget(); - setPlayerOutline(Medic.currentTarget, Blackmailer.blackmailedColor); - } - - private static void shifterSetTarget() - { - if (Shifter.shifter == null || Shifter.shifter != CachedPlayer.LocalPlayer.PlayerControl) return; - Shifter.currentTarget = setTarget(); - if (Shifter.futureShift == null) setPlayerOutline(Shifter.currentTarget, Color.yellow); - } - - private static void morphlingSetTarget() - { - if (Morphling.morphling == null || Morphling.morphling != CachedPlayer.LocalPlayer.PlayerControl) return; - Morphling.currentTarget = setTarget(); - setPlayerOutline(Morphling.currentTarget, Morphling.color); - } - - private static void sheriffSetTarget() - { - if (Sheriff.sheriff == null || Sheriff.sheriff != CachedPlayer.LocalPlayer.PlayerControl) return; - Sheriff.currentTarget = setTarget(); - setPlayerOutline(Sheriff.currentTarget, Sheriff.color); - } - - private static void deputySetTarget() - { - if (Deputy.deputy == null || Deputy.deputy != CachedPlayer.LocalPlayer.PlayerControl) return; - Deputy.currentTarget = setTarget(); - setPlayerOutline(Deputy.currentTarget, Deputy.color); - } public static void deputyCheckPromotion(bool isMeeting = false) { @@ -290,13 +322,6 @@ public static void deputyCheckPromotion(bool isMeeting = false) } } - private static void trackerSetTarget() - { - if (Tracker.tracker == null || Tracker.tracker != CachedPlayer.LocalPlayer.PlayerControl) return; - Tracker.currentTarget = setTarget(); - if (!Tracker.usedTracker) setPlayerOutline(Tracker.currentTarget, Tracker.color); - } - private static void detectiveUpdateFootPrints() { if (Detective.detective == null @@ -314,119 +339,36 @@ private static void detectiveUpdateFootPrints() } } - private static void vampireSetTarget() + private static void sidekickCheckPromotion() { - if (Vampire.vampire == null || Vampire.vampire != CachedPlayer.LocalPlayer.PlayerControl) return; - - PlayerControl target = null; + // If LocalPlayer is Sidekick, the Jackal is disconnected and Sidekick promotion is enabled, then trigger promotion + if (Jackal.sidekick.IsDead() || !Jackal.promotesToJackal || Jackal.sidekick != CachedPlayer.LocalPlayer.PlayerControl) return; + var writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, + (byte)CustomRPC.SidekickPromotes, SendOption.Reliable); + writer.Write(Jackal.sidekick.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.sidekickPromotes(Jackal.sidekick.PlayerId); + } - var untargetablePlayers = new List(); + private static void deputyUpdate() + { + if (CachedPlayer.LocalPlayer.PlayerControl == null || + !Deputy.handcuffedKnows.ContainsKey(CachedPlayer.LocalPlayer.PlayerId)) return; - if (Spy.spy != null) - { - if (Spy.impostorsCanKillAnyone) - { - target = setTarget(false, true); - } - else - { - target = setTarget(true, true); - } - } - else + if (Deputy.handcuffedKnows[CachedPlayer.LocalPlayer.PlayerId] <= 0) { - target = setTarget(true, true); + Deputy.handcuffedKnows.Remove(CachedPlayer.LocalPlayer.PlayerId); + // Resets the buttons + Deputy.setHandcuffedKnows(false); + + // Ghost info + var writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, + (byte)CustomRPC.ShareGhostInfo, SendOption.Reliable); + writer.Write(CachedPlayer.LocalPlayer.PlayerId); + writer.Write((byte)RPCProcedure.GhostInfoTypes.HandcuffOver); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - - bool targetNearGarlic = false; - if (target != null) - foreach (var garlic in Garlic.garlics) - if (Vector2.Distance(garlic.garlic.transform.position, target.transform.position) <= 1.91f) - targetNearGarlic = true; - Vampire.targetNearGarlic = targetNearGarlic; - Vampire.currentTarget = target; - setPlayerOutline(Vampire.currentTarget, Vampire.color); - } - - private static void jackalSetTarget() - { - if (Jackal.jackal.Any(x => x.IsAlive() && x.PlayerId == CachedPlayer.LocalId)) - { - var untargetablePlayers = new List(); - foreach (var p in Jackal.jackal) - { - untargetablePlayers.Add(p); - } - if (Jackal.sidekick != null) untargetablePlayers.Add(Jackal.sidekick); - if (Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); - Jackal.currentTarget = setTarget(untargetablePlayers: untargetablePlayers); - setPlayerOutline(Jackal.currentTarget, Palette.ImpostorRed); - } - } - - private static void sidekickSetTarget() - { - if (Jackal.sidekick == null || Jackal.sidekick != CachedPlayer.LocalPlayer.PlayerControl) return; - var untargetablePlayers = new List(); - foreach (var p in Jackal.jackal) - { - untargetablePlayers.Add(p); - } - if (Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); - Jackal.currentTarget2 = setTarget(untargetablePlayers: untargetablePlayers); - setPlayerOutline(Jackal.currentTarget2, Palette.ImpostorRed); - } - - private static void sidekickCheckPromotion() - { - // If LocalPlayer is Sidekick, the Jackal is disconnected and Sidekick promotion is enabled, then trigger promotion - if (Jackal.sidekick.IsDead() || !Jackal.promotesToJackal || Jackal.sidekick != CachedPlayer.LocalPlayer.PlayerControl) return; - var writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, - (byte)CustomRPC.SidekickPromotes, SendOption.Reliable); - writer.Write(Jackal.sidekick.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.sidekickPromotes(Jackal.sidekick.PlayerId); - } - - private static void swooperSetTarget() - { - if (Swooper.swooper == null || Swooper.swooper != CachedPlayer.LocalPlayer.PlayerControl) return; - var untargetablePlayers = new List(); - if (Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); // Exclude Jackal from targeting the Mini unless it has grown up - Swooper.currentTarget = setTarget(untargetablePlayers: untargetablePlayers); - setPlayerOutline(Swooper.currentTarget, Palette.ImpostorRed); - } - - private static void eraserSetTarget() - { - if (Eraser.eraser == null || Eraser.eraser != CachedPlayer.LocalPlayer.PlayerControl) return; - - var untargetables = new List(); - if (Spy.spy != null) untargetables.Add(Spy.spy); - Eraser.currentTarget = setTarget(!Eraser.canEraseAnyone, - untargetablePlayers: Eraser.canEraseAnyone ? [] : untargetables); - setPlayerOutline(Eraser.currentTarget, Eraser.color); - } - - private static void deputyUpdate() - { - if (CachedPlayer.LocalPlayer.PlayerControl == null || - !Deputy.handcuffedKnows.ContainsKey(CachedPlayer.LocalPlayer.PlayerId)) return; - - if (Deputy.handcuffedKnows[CachedPlayer.LocalPlayer.PlayerId] <= 0) - { - Deputy.handcuffedKnows.Remove(CachedPlayer.LocalPlayer.PlayerId); - // Resets the buttons - Deputy.setHandcuffedKnows(false); - - // Ghost info - var writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, - (byte)CustomRPC.ShareGhostInfo, SendOption.Reliable); - writer.Write(CachedPlayer.LocalPlayer.PlayerId); - writer.Write((byte)RPCProcedure.GhostInfoTypes.HandcuffOver); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - } + } private static void engineerUpdate() { @@ -456,54 +398,6 @@ private static void engineerUpdate() } } - private static void impostorSetTarget() - { - if (!CachedPlayer.LocalPlayer.Data.Role.IsImpostor || !CachedPlayer.LocalPlayer.PlayerControl.CanMove || - CachedPlayer.LocalPlayer.Data.IsDead) - { - // !isImpostor || !canMove || isDead - FastDestroyableSingleton.Instance.KillButton.SetTarget(null); - return; - } - - PlayerControl target; - if (Spy.spy != null) - { - if (Spy.impostorsCanKillAnyone) - { - target = setTarget(false, true); - } - else - { - target = setTarget(true, true, [Spy.spy]); - } - } - else - { - target = setTarget(true, true); - } - - FastDestroyableSingleton.Instance.KillButton.SetTarget(target); // Includes setPlayerOutline(target, Palette.ImpstorRed); - } - - private static void warlockSetTarget() - { - if (Warlock.warlock == null || Warlock.warlock != CachedPlayer.LocalPlayer.PlayerControl) return; - if (Warlock.curseVictim != null && (Warlock.curseVictim.Data.Disconnected || Warlock.curseVictim.Data.IsDead)) - // If the cursed victim is disconnected or dead reset the curse so a new curse can be applied - Warlock.resetCurse(); - if (Warlock.curseVictim == null) - { - Warlock.currentTarget = setTarget(); - setPlayerOutline(Warlock.currentTarget, Warlock.color); - } - else - { - Warlock.curseVictimTarget = setTarget(targetingPlayer: Warlock.curseVictim); - setPlayerOutline(Warlock.curseVictimTarget, Warlock.color); - } - } - private static void swooperUpdate() { if (Swooper.isInvisable && Swooper.swoopTimer <= 0 && Swooper.swooper == CachedPlayer.LocalPlayer.PlayerControl) @@ -573,13 +467,6 @@ private static void ninjaUpdate() } } - private static void prophetSetTarget() - { - if (Prophet.prophet == null || CachedPlayer.LocalPlayer.PlayerControl != Prophet.prophet) return; - Prophet.currentTarget = setTarget(); - if (Prophet.examinesLeft > 0) setPlayerOutline(Prophet.currentTarget, Prophet.color); - } - private static void prophetUpdate() { if (Prophet.arrows == null) return; @@ -683,7 +570,7 @@ private static void trackerUpdate() } } - public static void MiniSizeUpdate(PlayerControl p) + private static void MiniSizeUpdate(PlayerControl p) { if (Mini.mini == null) return; // Set default player size @@ -718,131 +605,18 @@ public static void MiniSizeUpdate(PlayerControl p) public static void GiantSizeUpdate(PlayerControl p) { - if (Giant.giant == null || InMeeting) return; + if (Giant.giant == null) return; - if (Giant.giant == null || isCamoComms || Camouflager.camouflageTimer > 0f || - MushroomSabotageActive || (Giant.giant == Morphling.morphling && Morphling.morphTimer > 0)) return; - var size = Giant.size; - // Giant - if (p == Giant.giant || (Morphling.morphling != null && p == Morphling.morphling && - Morphling.morphTarget == Giant.giant && Morphling.morphTimer > 0f)) + if (!isCamoComms && Camouflager.camouflageTimer == 0f && !MushroomSabotageActive && + ((Giant.giant == Morphling.morphling && Morphling.morphTimer == 0f) || + (p == Morphling.morphling && Giant.giant == Morphling.morphTarget && Morphling.morphTimer > 0f) || + Giant.giant == p)) { - p.transform.localScale = new Vector3(size, size, 1f); + p.transform.localScale = new Vector3(Giant.size, Giant.size, 1f); } - } - - public static void updatePlayerInfo() - { - var local = CachedPlayer.LocalPlayer.PlayerControl; - foreach (PlayerControl p in CachedPlayer.AllPlayers) + else if (p != Mini.mini) { - var playerVoteArea = MeetingHud.Instance?.playerStates?.FirstOrDefault(x => x.TargetPlayerId == p.PlayerId); - if (playerVoteArea != null && playerVoteArea.ColorBlindName.gameObject.active) - { - playerVoteArea.ColorBlindName.transform.localPosition = new Vector3(-0.93f, -0.2f, -0.1f); - playerVoteArea.ColorBlindName.fontSize *= 1.75f; - } - p.cosmetics.nameText.transform.parent.SetLocalZ(-0.0001f); - - bool canSeeRole = (Lawyer.lawyerKnowsRole && local == Lawyer.lawyer && p == Lawyer.target) || - (PartTimer.knowsRole && local == PartTimer.partTimer && p == PartTimer.target) || - (local == PartTimer.target && p == PartTimer.partTimer) || - (Akujo.knowsRoles && local == Akujo.akujo && - (p == Akujo.honmei || Akujo.keeps.Any(x => x.PlayerId == p.PlayerId))) || - (ModOption.impostorSeeRoles && Spy.spy == null && PlayerControl.LocalPlayer.isImpostor() && - PlayerControl.LocalPlayer.IsAlive() && p.isImpostor() && p.IsAlive()); - - bool reported = ((local == Slueth.slueth && Slueth.reported.Any(x => x.PlayerId == p.PlayerId)) - || (local == Poucher.poucher && Poucher.killed.Any(x => x.PlayerId == p.PlayerId))) && p.IsDead(); - - if (p == local || local.Data.IsDead || canSeeRole || reported || (Mayor.mayor == p && Mayor.Revealed)) - { - var roleNames = RoleInfo.GetRolesString(p, true, false, false, true); - var mainRole = RoleInfo.GetRolesString(p, true, false, false, false); - var allRoleText = RoleInfo.GetRolesString(p, true, true, true, true); - - var playerInfoTransform = p.cosmetics.nameText.transform.parent.FindChild("Info"); - var playerInfo = playerInfoTransform != null ? playerInfoTransform.GetComponent() : null; - if (playerInfo == null) - { - playerInfo = Object.Instantiate(p.cosmetics.nameText, p.cosmetics.nameText.transform.parent); - playerInfo.transform.localPosition += Vector3.up * 0.225f; - playerInfo.fontSize *= 0.8f; - playerInfo.gameObject.name = "Info"; - playerInfo.color = playerInfo.color.SetAlpha(1f); - } - - var meetingInfoTransform = playerVoteArea != null ? playerVoteArea.NameText.transform.parent.FindChild("Info") : null; - var meetingInfo = meetingInfoTransform != null ? meetingInfoTransform.GetComponent() : null; - - if (meetingInfo == null && playerVoteArea != null) - { - meetingInfo = Object.Instantiate(playerVoteArea.NameText, playerVoteArea.NameText.transform.parent); - meetingInfo.transform.localPosition += Vector3.down * 0.2f; - meetingInfo.fontSize *= 0.64f; - meetingInfo.gameObject.name = "Info"; - } - - // Set player name higher to align in middle - if (meetingInfo != null && playerVoteArea != null) - { - var playerName = playerVoteArea.NameText; - playerName.transform.localPosition = new Vector3(0.3384f, 0.0311f, -0.1f); - } - - var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data); - var taskInfo = tasksTotal > 0 ? $"({tasksCompleted}/{tasksTotal})" : ""; - - var playerInfoText = ""; - var meetingInfoText = ""; - if (p == local) - { - if (p.Data.IsDead) roleNames = allRoleText; - playerInfoText = $"{roleNames}"; - if (HudManager.Instance.TaskPanel != null) - { - var tabText = HudManager.Instance.TaskPanel.tab.transform.FindChild("TabText_TMP").GetComponent(); - tabText.SetText(string.Format("tasksNum".Translate(), taskInfo)); - } - meetingInfoText = $"{allRoleText} {taskInfo}".Trim(); - } - else if (reported) - { - meetingInfoText = playerInfoText = mainRole; - } - else if (local.IsAlive() && Mayor.mayor == p && Mayor.Revealed) - { - meetingInfoText = cs(Mayor.color, "Mayor".Translate()); - } - else if (canSeeRole) - { - meetingInfoText = playerInfoText = roleNames; - } - else - { - if (CanSeeRoleInfo) - { - playerInfoText = $"{allRoleText} {taskInfo}".Trim(); - meetingInfoText = playerInfoText; - } - } - - playerInfo.text = playerInfoText; - playerInfo.gameObject.SetActive(p.Visible); - if (meetingInfo != null) - { - meetingInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : meetingInfoText; - } - } - else - { - if (local != p && p != null) - { - var playerInfoTransform = p.cosmetics.nameText.transform.parent.FindChild("Info"); - var playerInfo = playerInfoTransform?.GetComponent(); - if (playerInfo != null) playerInfo.text = ""; - } - } + p.transform.localScale = new Vector3(0.7f, 0.7f, 1f); } } @@ -854,11 +628,11 @@ public static void WitnessUpdate() { if (Witness.target != null) { - setInfo(Witness.target.PlayerId, cs(Color.red, "该玩家被标记为凶手")); + setInfo(Witness.target.PlayerId, cs(Color.red, $"{Witness.target?.Data?.PlayerName} 疑似为本案的凶手")); } - else if (PlayerControl.LocalPlayer == Witness.Player && Witness.killerTarget != null) + else if ((PlayerControl.LocalPlayer == Witness.Player || ModOption.DebugMode) && Witness.killerTarget != null) { - setInfo(Witness.killerTarget.PlayerId, cs(Color.red, "该玩家可能为凶手")); + setInfo(Witness.killerTarget.PlayerId, cs(Color.red, $"{Witness.killerTarget?.Data?.PlayerName} 为本案的真凶")); } } @@ -885,34 +659,7 @@ void setInfo(int targetPlayerId, string infoText) } } - public static void securityGuardSetTarget() - { - if (SecurityGuard.securityGuard == null || - SecurityGuard.securityGuard != CachedPlayer.LocalPlayer.PlayerControl || - MapUtilities.CachedShipStatus == null || MapUtilities.CachedShipStatus.AllVents == null) return; - - Vent target = null; - var truePosition = CachedPlayer.LocalPlayer.PlayerControl.GetTruePosition(); - var closestDistance = float.MaxValue; - for (var i = 0; i < MapUtilities.CachedShipStatus.AllVents.Length; i++) - { - var vent = MapUtilities.CachedShipStatus.AllVents[i]; - if (vent.gameObject.name.StartsWith("JackInTheBoxVent_") || - vent.gameObject.name.StartsWith("SealedVent_") || - vent.gameObject.name.StartsWith("FutureSealedVent_")) continue; - if (SubmergedCompatibility.IsSubmerged && vent.Id == 9) continue; // cannot seal submergeds exit only vent! - var distance = Vector2.Distance(vent.transform.position, truePosition); - if (distance <= vent.UsableDistance && distance < closestDistance) - { - closestDistance = distance; - target = vent; - } - } - - SecurityGuard.ventTarget = target; - } - - public static void securityGuardUpdate() + public static void securityGuardUpdate() { if (SecurityGuard.securityGuard == null || CachedPlayer.LocalPlayer.PlayerControl != SecurityGuard.securityGuard || @@ -925,29 +672,6 @@ public static void securityGuardUpdate() } } - public static void arsonistSetTarget() - { - if (Arsonist.arsonist == null || Arsonist.arsonist != CachedPlayer.LocalPlayer.PlayerControl) return; - List untargetables; - if (Arsonist.douseTarget != null) - { - untargetables = new(); - foreach (var cachedPlayer in CachedPlayer.AllPlayers) - if (cachedPlayer.PlayerId != Arsonist.douseTarget.PlayerId) - untargetables.Add(cachedPlayer); - } - else - { - untargetables = Arsonist.dousedPlayers; - } - - Arsonist.currentTarget = setTarget(untargetablePlayers: untargetables); - if (Arsonist.currentTarget != null) setPlayerOutline(Arsonist.currentTarget, Arsonist.color); - - Arsonist.currentTarget2 = setTarget(false, true); - if (Arsonist.currentTarget2 != null) setPlayerOutline(Arsonist.currentTarget2, Arsonist.color); - } - private static void snitchUpdate() { if (Snitch.localArrows == null) return; @@ -1317,8 +1041,7 @@ public static void evilTrapperUpdate() private static void radarUpdate() { - if (Radar.radar == null || CachedPlayer.LocalPlayer.PlayerControl != Radar.radar || Radar.localArrows == null || - !Radar.showArrows) return; + if (Radar.radar == null || CachedPlayer.LocalPlayer.PlayerControl != Radar.radar || Radar.localArrows == null || InMeeting) return; if (Radar.radar.Data.IsDead) { foreach (var arrow in Radar.localArrows) Object.Destroy(arrow.arrow); @@ -1364,9 +1087,8 @@ public static PlayerControl GetClosestPlayer(PlayerControl refPlayer, List targets = null - ) - { - if (!button.isActiveAndEnabled) return; - - button.SetTarget( - SetClosestPlayer(ref closestPlayer, maxDistance, targets) - ); - } - - public static PlayerControl SetClosestPlayer( - ref PlayerControl closestPlayer, - float maxDistance = float.NaN, - List targets = null - ) - { - if (float.IsNaN(maxDistance)) - maxDistance = - GameOptionsData.KillDistances[GameOptionsManager.Instance.currentNormalGameOptions.KillDistance]; - var player = GetClosestPlayer( - PlayerControl.LocalPlayer, - targets ?? PlayerControl.AllPlayerControls.ToArray().ToList() - ); - var closeEnough = player == null || GetDistBetweenPlayers(PlayerControl.LocalPlayer, player) < maxDistance; - return closestPlayer = closeEnough ? player : null; - } - - public static double GetDistBetweenPlayers(PlayerControl player, PlayerControl refplayer) - { - var truePosition = refplayer.GetTruePosition(); - var truePosition2 = player.GetTruePosition(); - return Vector2.Distance(truePosition, truePosition2); - } - - public static void mediumSetTarget() - { - if (Medium.medium == null || Medium.medium != CachedPlayer.LocalPlayer.PlayerControl || - Medium.medium.Data.IsDead || Medium.deadBodies == null || - MapUtilities.CachedShipStatus?.AllVents == null) return; - - DeadPlayer target = null; - var truePosition = CachedPlayer.LocalPlayer.PlayerControl.GetTruePosition(); - var closestDistance = float.MaxValue; - var usableDistance = MapUtilities.CachedShipStatus.AllVents.FirstOrDefault().UsableDistance; - foreach (var (dp, ps) in Medium.deadBodies) - { - var distance = Vector2.Distance(ps, truePosition); - if (distance <= usableDistance && distance < closestDistance) - { - closestDistance = distance; - target = dp; - } - } - - Medium.target = target; - } - private static void morphlingAndCamouflagerUpdate() { var mushRoomSaboIsActive = MushroomSabotageActive; @@ -1554,60 +1210,6 @@ public static void swapperUpdate() } } - private static void pursuerSetTarget() - { - if (Pursuer.Player == null || !Pursuer.Player.Contains(CachedPlayer.LocalPlayer.PlayerControl)) return; - Pursuer.target = setTarget(); - setPlayerOutline(Pursuer.target, Pursuer.color); - } - - private static void survivorSetTarget() - { - if (Survivor.Player == null || !Survivor.Player.Contains(CachedPlayer.LocalPlayer.PlayerControl)) return; - Survivor.target = setTarget(); - setPlayerOutline(Survivor.target, Survivor.color); - } - - private static void witchSetTarget() - { - if (Witch.witch == null || Witch.witch != CachedPlayer.LocalPlayer.PlayerControl) return; - List untargetables; - if (Witch.spellCastingTarget != null) - { - // Don't switch the target from the the one you're currently casting a spell on - untargetables = PlayerControl.AllPlayerControls.ToArray().Where(x => x.PlayerId != Witch.spellCastingTarget.PlayerId).ToList(); - } - else - { - // Also target players that have already been spelled, to hide spells that were blanks/blocked by shields - untargetables = new(); - if (Spy.spy != null && !Witch.canSpellAnyone) untargetables.Add(Spy.spy); - } - - Witch.currentTarget = setTarget(!Witch.canSpellAnyone, untargetablePlayers: untargetables); - setPlayerOutline(Witch.currentTarget, Witch.color); - } - - private static void ninjaSetTarget() - { - if (Ninja.ninja == null || Ninja.ninja != CachedPlayer.LocalPlayer.PlayerControl) return; - var untargetables = new List(); - if (Spy.spy != null && !Spy.impostorsCanKillAnyone) untargetables.Add(Spy.spy); - if (Mini.mini != null && !Mini.isGrownUp()) untargetables.Add(Mini.mini); - Ninja.currentTarget = - setTarget(Spy.spy == null || !Spy.impostorsCanKillAnyone, untargetablePlayers: untargetables); - setPlayerOutline(Ninja.currentTarget, Ninja.color); - } - - private static void thiefSetTarget() - { - if (Thief.thief == null || Thief.thief != CachedPlayer.LocalPlayer.PlayerControl) return; - var untargetables = new List(); - if (Mini.mini != null && !Mini.isGrownUp()) untargetables.Add(Mini.mini); - Thief.currentTarget = setTarget(untargetablePlayers: untargetables); - setPlayerOutline(Thief.currentTarget, Thief.color); - } - private static void baitUpdate() { @@ -1723,6 +1325,292 @@ public static void akujoUpdate() } } + private static void pavlovsownerUpdate() + { + if (Pavlovsdogs.arrow == null) return; + + foreach (var arrow in Pavlovsdogs.arrow) arrow.arrow.SetActive(false); + + if (Pavlovsdogs.pavlovsowner == null || Pavlovsdogs.pavlovsowner.Data.IsDead || CachedPlayer.LocalPlayer.PlayerControl != Pavlovsdogs.pavlovsowner) return; + + var index = 0; + foreach (PlayerControl p in CachedPlayer.AllPlayers) + { + if (!p.Data.IsDead && Pavlovsdogs.pavlovsdogs.Any(x => x == p)) + { + if (index >= Pavlovsdogs.arrow.Count) + { + Pavlovsdogs.arrow.Add(new Arrow(Pavlovsdogs.color)); + } + else if (index < Pavlovsdogs.arrow.Count && Pavlovsdogs.arrow[index] != null) + { + Pavlovsdogs.arrow[index].arrow.SetActive(true); + Pavlovsdogs.arrow[index].Update(p.transform.position, Pavlovsdogs.color); + } + index++; + } + } + + } + + public static void Postfix(PlayerControl __instance) + { + if (AmongUsClient.Instance.GameState != InnerNetClient.GameStates.Started || + GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; + + // Mini and Morphling shrink + MiniSizeUpdate(__instance); + GiantSizeUpdate(__instance); + + if (CachedPlayer.LocalPlayer.PlayerControl == __instance) + { + // Update player outlines + setBasePlayerOutlines(); + + // Update Role Description + refreshRoleDescription(__instance); + + // Update Player Info + updatePlayerInfo(); + + //Update pet visibility + setPetVisibility(); + + if (!InGame) return; + + UpdateSetTarget(); + // EvilTrapper + evilTrapperUpdate(); + // Time Master + bendTimeUpdate(); + // Swooper + swooperUpdate(); + // Prophet + prophetUpdate(); + // Deputy + deputyUpdate(); + // Detective + detectiveUpdateFootPrints(); + // Vampire + Garlic.UpdateAll(); + Trap.Update(); + // Engineer + engineerUpdate(); + // Tracker + trackerUpdate(); + // Pavlovsdogs + pavlovsownerUpdate(); + // Check for deputy promotion on Sheriff disconnect + deputyCheckPromotion(); + // Check for sidekick promotion on Jackal disconnect + sidekickCheckPromotion(); + // Witness + WitnessUpdate(); + // SecurityGuard + securityGuardUpdate(); + // Snitch + snitchUpdate(); + snitchTextUpdate(); + // undertaker + undertakerDragBodyUpdate(); + // Amnisiac + amnisiacUpdate(); + // BountyHunter + bountyHunterUpdate(); + // Vulture + vultureUpdate(); + radarUpdate(); + // Morphling and Camouflager + morphlingAndCamouflagerUpdate(); + // Lawyer + lawyerUpdate(); + // Executioner + executionerUpdate(); + // Ninja + NinjaTrace.UpdateAll(); + ninjaUpdate(); + // yoyo + Silhouette.UpdateAll(); + // PartTimer + partTimerUpdate(); + //Balancer + Balancer.FixedUpdate(); + + hackerUpdate(); + swapperUpdate(); + // Hacker + hackerUpdate(); + // Trapper + trapperUpdate(); + // Akojo + akujoUpdate(); + // Bait + baitUpdate(); + // Bloody + bloodyUpdate(); + // mini (for the cooldowns) + miniCooldownUpdate(); + // Chameleon (invis stuff, timers) + Chameleon.update(); + Bomb.update(); + } + } + + public static void UpdateSetTarget() + { + if (InMeeting) return; + + impostorSetTarget(); + morphlingSetTarget(); + vampireSetTarget(); + eraserSetTarget(); + blackMailerSetTarget(); + ninjaSetTarget(); + witchSetTarget(); + warlockSetTarget(); + bomberSetTarget(); + + jackalSetTarget(); + sidekickSetTarget(); + pavlovsownerSetTarget(); + pavlovsdogsSetTarget(); + arsonistSetTarget(); + werewolfSetTarget(); + juggernautSetTarget(); + doomsayerSetTarget(); + swooperSetTarget(); + pursuerSetTarget(); + survivorSetTarget(); + thiefSetTarget(); + partTimerSetTarget(); + akujoSetTarget(); + + securityGuardSetTarget(); + bodyGuardSetTarget(); + mediumSetTarget(); + trackerSetTarget(); + deputySetTarget(); + sheriffSetTarget(); + prophetSetTarget(); + medicSetTarget(); + + shifterSetTarget(); + } + + + private static void medicSetTarget() + { + if (Medic.medic == null || Medic.medic != CachedPlayer.LocalPlayer.PlayerControl) return; + Medic.currentTarget = setTarget(); + if (!Medic.usedShield) setPlayerOutline(Medic.currentTarget, Medic.shieldedColor); + } + + private static void prophetSetTarget() + { + if (Prophet.prophet == null || CachedPlayer.LocalPlayer.PlayerControl != Prophet.prophet) return; + Prophet.currentTarget = setTarget(); + if (Prophet.examinesLeft > 0) setPlayerOutline(Prophet.currentTarget, Prophet.color); + } + + private static void partTimerSetTarget() + { + if (PartTimer.partTimer == null || PartTimer.partTimer != CachedPlayer.LocalPlayer.PlayerControl) return; + PartTimer.currentTarget = setTarget(); + if (PartTimer.target != null) setPlayerOutline(PartTimer.currentTarget, PartTimer.color); + } + + private static void bomberSetTarget() + { + setBomberBombTarget(); + if (Bomber.bomber == null || Bomber.bomber != CachedPlayer.LocalPlayer.PlayerControl) return; + Bomber.currentTarget = setTarget(); + if (Bomber.hasBombPlayer == null) setPlayerOutline(Bomber.currentTarget, Bomber.color); + } + + private static void trackerSetTarget() + { + if (Tracker.tracker == null || Tracker.tracker != CachedPlayer.LocalPlayer.PlayerControl) return; + Tracker.currentTarget = setTarget(); + if (!Tracker.usedTracker) setPlayerOutline(Tracker.currentTarget, Tracker.color); + } + + private static void vampireSetTarget() + { + if (Vampire.vampire == null || Vampire.vampire != CachedPlayer.LocalPlayer.PlayerControl) return; + + PlayerControl target = null; + + var untargetablePlayers = new List(); + + if (Spy.spy != null) + { + if (Spy.impostorsCanKillAnyone) + { + target = setTarget(false, true); + } + else + { + target = setTarget(true, true); + } + } + else + { + target = setTarget(true, true); + } + + bool targetNearGarlic = false; + if (target != null) + foreach (var garlic in Garlic.garlics) + if (Vector2.Distance(garlic.garlic.transform.position, target.transform.position) <= 1.91f) + targetNearGarlic = true; + Vampire.targetNearGarlic = targetNearGarlic; + Vampire.currentTarget = target; + setPlayerOutline(Vampire.currentTarget, Vampire.color); + } + + private static void jackalSetTarget() + { + if (Jackal.jackal.Any(x => x.IsAlive() && x.PlayerId == CachedPlayer.LocalId)) + { + var untargetablePlayers = new List(); + foreach (var p in Jackal.jackal) + { + untargetablePlayers.Add(p); + } + if (Jackal.sidekick != null) untargetablePlayers.Add(Jackal.sidekick); + if (Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); + Jackal.currentTarget = setTarget(untargetablePlayers: untargetablePlayers); + setPlayerOutline(Jackal.currentTarget, Palette.ImpostorRed); + } + } + + private static void sidekickSetTarget() + { + if (Jackal.sidekick == null || Jackal.sidekick != CachedPlayer.LocalPlayer.PlayerControl) return; + var untargetablePlayers = new List(); + foreach (var p in Jackal.jackal) + { + untargetablePlayers.Add(p); + } + if (Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); + Jackal.currentTarget2 = setTarget(untargetablePlayers: untargetablePlayers); + setPlayerOutline(Jackal.currentTarget2, Palette.ImpostorRed); + } + + private static void setBomberBombTarget() + { + if (Bomber.bomber == null || Bomber.hasBombPlayer != CachedPlayer.LocalPlayer.PlayerControl) return; + Bomber.currentBombTarget = setTarget(); + //if (Bomber.hasBomb != null) setPlayerOutline(Bomber.currentBombTarget, Bomber.color); + } + + private static void bodyGuardSetTarget() + { + if (BodyGuard.bodyguard == null || BodyGuard.bodyguard != CachedPlayer.LocalPlayer.PlayerControl) return; + BodyGuard.currentTarget = setTarget(); + if (!BodyGuard.usedGuard) setPlayerOutline(Medic.currentTarget, Medic.shieldedColor); + } + public static void akujoSetTarget() { if (Akujo.akujo == null || Akujo.akujo.Data.IsDead || CachedPlayer.LocalPlayer.PlayerControl != Akujo.akujo) return; @@ -1733,6 +1621,100 @@ public static void akujoSetTarget() if (Akujo.honmei == null || Akujo.keepsLeft > 0) setPlayerOutline(Akujo.currentTarget, Akujo.color); } + private static void swooperSetTarget() + { + if (Swooper.swooper == null || Swooper.swooper != CachedPlayer.LocalPlayer.PlayerControl) return; + var untargetablePlayers = new List(); + if (Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); // Exclude Jackal from targeting the Mini unless it has grown up + Swooper.currentTarget = setTarget(untargetablePlayers: untargetablePlayers); + setPlayerOutline(Swooper.currentTarget, Palette.ImpostorRed); + } + + private static void eraserSetTarget() + { + if (Eraser.eraser == null || Eraser.eraser != CachedPlayer.LocalPlayer.PlayerControl) return; + + var untargetables = new List(); + if (Spy.spy != null) untargetables.Add(Spy.spy); + Eraser.currentTarget = setTarget(!Eraser.canEraseAnyone, + untargetablePlayers: Eraser.canEraseAnyone ? [] : untargetables); + setPlayerOutline(Eraser.currentTarget, Eraser.color); + } + + private static void impostorSetTarget() + { + if (!CachedPlayer.LocalPlayer.Data.Role.IsImpostor || !CachedPlayer.LocalPlayer.PlayerControl.CanMove || + CachedPlayer.LocalPlayer.Data.IsDead) + { + // !isImpostor || !canMove || isDead + FastDestroyableSingleton.Instance.KillButton.SetTarget(null); + return; + } + + PlayerControl target; + if (Spy.spy != null) + { + if (Spy.impostorsCanKillAnyone) + { + target = setTarget(false, true); + } + else + { + target = setTarget(true, true, [Spy.spy]); + } + } + else + { + target = setTarget(true, true); + } + + FastDestroyableSingleton.Instance.KillButton.SetTarget(target); // Includes setPlayerOutline(target, Palette.ImpstorRed); + } + + private static void warlockSetTarget() + { + if (Warlock.warlock == null || Warlock.warlock != CachedPlayer.LocalPlayer.PlayerControl) return; + if (Warlock.curseVictim != null && (Warlock.curseVictim.Data.Disconnected || Warlock.curseVictim.Data.IsDead)) + // If the cursed victim is disconnected or dead reset the curse so a new curse can be applied + Warlock.resetCurse(); + if (Warlock.curseVictim == null) + { + Warlock.currentTarget = setTarget(); + setPlayerOutline(Warlock.currentTarget, Warlock.color); + } + else + { + Warlock.curseVictimTarget = setTarget(targetingPlayer: Warlock.curseVictim); + setPlayerOutline(Warlock.curseVictimTarget, Warlock.color); + } + } + + public static void securityGuardSetTarget() + { + if (SecurityGuard.securityGuard == null || SecurityGuard.securityGuard != CachedPlayer.LocalPlayer.PlayerControl || + MapUtilities.CachedShipStatus == null || MapUtilities.CachedShipStatus.AllVents == null) return; + + Vent target = null; + var truePosition = CachedPlayer.LocalPlayer.PlayerControl.GetTruePosition(); + var closestDistance = float.MaxValue; + for (var i = 0; i < MapUtilities.CachedShipStatus.AllVents.Length; i++) + { + var vent = MapUtilities.CachedShipStatus.AllVents[i]; + if (vent.gameObject.name.StartsWith("JackInTheBoxVent_") || + vent.gameObject.name.StartsWith("SealedVent_") || + vent.gameObject.name.StartsWith("FutureSealedVent_")) continue; + if (SubmergedCompatibility.IsSubmerged && vent.Id == 9) continue; // cannot seal submergeds exit only vent! + var distance = Vector2.Distance(vent.transform.position, truePosition); + if (distance <= vent.UsableDistance && distance < closestDistance) + { + closestDistance = distance; + target = vent; + } + } + + SecurityGuard.ventTarget = target; + } + private static void pavlovsownerSetTarget() { if (Pavlovsdogs.pavlovsowner == null || Pavlovsdogs.pavlovsowner != CachedPlayer.LocalPlayer.PlayerControl) return; @@ -1742,6 +1724,29 @@ private static void pavlovsownerSetTarget() setPlayerOutline(Pavlovsdogs.currentTarget, Palette.ImpostorRed); } + public static void mediumSetTarget() + { + if (Medium.medium == null || Medium.medium != CachedPlayer.LocalPlayer.PlayerControl || + Medium.medium.Data.IsDead || Medium.deadBodies == null || + MapUtilities.CachedShipStatus?.AllVents == null) return; + + DeadPlayer target = null; + var truePosition = CachedPlayer.LocalPlayer.PlayerControl.GetTruePosition(); + var closestDistance = float.MaxValue; + var usableDistance = MapUtilities.CachedShipStatus.AllVents.FirstOrDefault().UsableDistance; + foreach (var (dp, ps) in Medium.deadBodies) + { + var distance = Vector2.Distance(ps, truePosition); + if (distance <= usableDistance && distance < closestDistance) + { + closestDistance = distance; + target = dp; + } + } + + Medium.target = target; + } + private static void pavlovsdogsSetTarget() { if (Pavlovsdogs.pavlovsdogs == null || !Pavlovsdogs.pavlovsdogs.Any(p => p == CachedPlayer.LocalPlayer.PlayerControl)) return; @@ -1756,195 +1761,138 @@ private static void pavlovsdogsSetTarget() setPlayerOutline(Pavlovsdogs.killTarget, Palette.ImpostorRed); } - private static void pavlovsownerUpdate() + private static void werewolfSetTarget() { - if (Pavlovsdogs.arrow == null) return; + if (Werewolf.werewolf == null || Werewolf.werewolf != CachedPlayer.LocalPlayer.PlayerControl) return; + Werewolf.currentTarget = setTarget(); + } - foreach (var arrow in Pavlovsdogs.arrow) arrow.arrow.SetActive(false); + private static void juggernautSetTarget() + { + if (Juggernaut.juggernaut == null || Juggernaut.juggernaut != CachedPlayer.LocalPlayer.PlayerControl) return; + Juggernaut.currentTarget = setTarget(); + } - if (Pavlovsdogs.pavlovsowner == null || Pavlovsdogs.pavlovsowner.Data.IsDead || CachedPlayer.LocalPlayer.PlayerControl != Pavlovsdogs.pavlovsowner) return; - var index = 0; - foreach (PlayerControl p in CachedPlayer.AllPlayers) - { - if (!p.Data.IsDead && Pavlovsdogs.pavlovsdogs.Any(x => x == p)) - { - if (index >= Pavlovsdogs.arrow.Count) - { - Pavlovsdogs.arrow.Add(new Arrow(Pavlovsdogs.color)); - } - else if (index < Pavlovsdogs.arrow.Count && Pavlovsdogs.arrow[index] != null) - { - Pavlovsdogs.arrow[index].arrow.SetActive(true); - Pavlovsdogs.arrow[index].Update(p.transform.position, Pavlovsdogs.color); - } - index++; - } - } + private static void doomsayerSetTarget() + { + if (Doomsayer.doomsayer == null || Doomsayer.doomsayer != CachedPlayer.LocalPlayer.PlayerControl) return; + Doomsayer.currentTarget = setTarget(); + } + private static void blackMailerSetTarget() + { + if (Blackmailer.blackmailer == null || + Blackmailer.blackmailer != CachedPlayer.LocalPlayer.PlayerControl) return; + Blackmailer.currentTarget = setTarget(); + setPlayerOutline(Medic.currentTarget, Blackmailer.blackmailedColor); } - public static void Postfix(PlayerControl __instance) + private static void pursuerSetTarget() { - if (AmongUsClient.Instance.GameState != InnerNetClient.GameStates.Started || - GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; + if (Pursuer.Player == null || !Pursuer.Player.Contains(CachedPlayer.LocalPlayer.PlayerControl)) return; + Pursuer.target = setTarget(); + setPlayerOutline(Pursuer.target, Pursuer.color); + } - // Mini and Morphling shrink - MiniSizeUpdate(__instance); - GiantSizeUpdate(__instance); + private static void survivorSetTarget() + { + if (Survivor.Player == null || !Survivor.Player.Contains(CachedPlayer.LocalPlayer.PlayerControl)) return; + Survivor.target = setTarget(); + setPlayerOutline(Survivor.target, Survivor.color); + } - // set position of colorblind text - /*foreach (var pc in PlayerControl.AllPlayerControls) + private static void witchSetTarget() + { + if (Witch.witch == null || Witch.witch != CachedPlayer.LocalPlayer.PlayerControl) return; + List untargetables; + if (Witch.spellCastingTarget != null) { - pc.cosmetics.colorBlindText.gameObject.transform.localPosition = new Vector3(0, 0, -0.0001f); - }*/ - - if (CachedPlayer.LocalPlayer.PlayerControl == __instance) + // Don't switch the target from the the one you're currently casting a spell on + untargetables = PlayerControl.AllPlayerControls.ToArray().Where(x => x.PlayerId != Witch.spellCastingTarget.PlayerId).ToList(); + } + else { - // Update player outlines - setBasePlayerOutlines(); + // Also target players that have already been spelled, to hide spells that were blanks/blocked by shields + untargetables = new(); + if (Spy.spy != null && !Witch.canSpellAnyone) untargetables.Add(Spy.spy); + } - // Update Role Description - refreshRoleDescription(__instance); + Witch.currentTarget = setTarget(!Witch.canSpellAnyone, untargetablePlayers: untargetables); + setPlayerOutline(Witch.currentTarget, Witch.color); + } - // Update Player Info - updatePlayerInfo(); + private static void ninjaSetTarget() + { + if (Ninja.ninja == null || Ninja.ninja != CachedPlayer.LocalPlayer.PlayerControl) return; + var untargetables = new List(); + if (Spy.spy != null && !Spy.impostorsCanKillAnyone) untargetables.Add(Spy.spy); + if (Mini.mini != null && !Mini.isGrownUp()) untargetables.Add(Mini.mini); + Ninja.currentTarget = + setTarget(Spy.spy == null || !Spy.impostorsCanKillAnyone, untargetablePlayers: untargetables); + setPlayerOutline(Ninja.currentTarget, Ninja.color); + } - //Update pet visibility - setPetVisibility(); + private static void thiefSetTarget() + { + if (Thief.thief == null || Thief.thief != CachedPlayer.LocalPlayer.PlayerControl) return; + var untargetables = new List(); + if (Mini.mini != null && !Mini.isGrownUp()) untargetables.Add(Mini.mini); + Thief.currentTarget = setTarget(untargetablePlayers: untargetables); + setPlayerOutline(Thief.currentTarget, Thief.color); + } - //RoleClass.FixedUpdate(__instance); - if (!InGame) return; - // EvilTrapper - evilTrapperUpdate(); - // Time Master - bendTimeUpdate(); - // Morphling - morphlingSetTarget(); - // Medic - medicSetTarget(); - // Bomber - bomberSetTarget(); - // Set Werewolf Target - werewolfSetTarget(); - // Juggernaut - juggernautSetTarget(); - // Doomsayer - doomsayerSetTarget(); - // Swooper - swooperSetTarget(); - swooperUpdate(); - // Prophet - prophetSetTarget(); - prophetUpdate(); - // Shifter - shifterSetTarget(); - // Sheriff - sheriffSetTarget(); - // Deputy - deputySetTarget(); - deputyUpdate(); - // Detective - detectiveUpdateFootPrints(); - // Vampire - vampireSetTarget(); - Garlic.UpdateAll(); - Trap.Update(); - // Eraser - eraserSetTarget(); - // Engineer - engineerUpdate(); - // Tracker - trackerSetTarget(); - trackerUpdate(); - // Jackal - jackalSetTarget(); - // Sidekick - sidekickSetTarget(); - // Pavlovsdogs - pavlovsownerSetTarget(); - pavlovsdogsSetTarget(); - pavlovsownerUpdate(); - // Impostor - impostorSetTarget(); - // Warlock - warlockSetTarget(); - // Check for deputy promotion on Sheriff disconnect - deputyCheckPromotion(); - // Check for sidekick promotion on Jackal disconnect - sidekickCheckPromotion(); - // Witness - WitnessUpdate(); - // SecurityGuard - securityGuardSetTarget(); - securityGuardUpdate(); - // Arsonist - arsonistSetTarget(); - // Snitch - snitchUpdate(); - snitchTextUpdate(); - // BodyGuard - bodyGuardSetTarget(); - // undertaker - undertakerDragBodyUpdate(); - // Amnisiac - amnisiacUpdate(); - // BountyHunter - bountyHunterUpdate(); - // Vulture - vultureUpdate(); - radarUpdate(); - // Medium - mediumSetTarget(); - // Morphling and Camouflager - morphlingAndCamouflagerUpdate(); - // Lawyer - lawyerUpdate(); - // Executioner - executionerUpdate(); - // Pursuer - pursuerSetTarget(); - // Survivor - survivorSetTarget(); - // Blackmailer - blackMailerSetTarget(); - // Witch - witchSetTarget(); - // Ninja - ninjaSetTarget(); - NinjaTrace.UpdateAll(); - ninjaUpdate(); - // Thief - thiefSetTarget(); - // yoyo - Silhouette.UpdateAll(); - // PartTimer - partTimerSetTarget(); - partTimerUpdate(); - //Balancer - Balancer.FixedUpdate(); + private static void shifterSetTarget() + { + if (Shifter.shifter == null || Shifter.shifter != CachedPlayer.LocalPlayer.PlayerControl) return; + Shifter.currentTarget = setTarget(); + if (Shifter.futureShift == null) setPlayerOutline(Shifter.currentTarget, Color.yellow); + } - hackerUpdate(); - swapperUpdate(); - // Hacker - hackerUpdate(); - // Trapper - trapperUpdate(); - // Akojo - akujoUpdate(); - akujoSetTarget(); - // -- MODIFIER-- - // Bait - baitUpdate(); - // Bloody - bloodyUpdate(); - // mini (for the cooldowns) - miniCooldownUpdate(); - // Chameleon (invis stuff, timers) - Chameleon.update(); - Bomb.update(); + private static void morphlingSetTarget() + { + if (Morphling.morphling == null || Morphling.morphling != CachedPlayer.LocalPlayer.PlayerControl) return; + Morphling.currentTarget = setTarget(); + setPlayerOutline(Morphling.currentTarget, Morphling.color); + } + + private static void sheriffSetTarget() + { + if (Sheriff.sheriff == null || Sheriff.sheriff != CachedPlayer.LocalPlayer.PlayerControl) return; + Sheriff.currentTarget = setTarget(); + setPlayerOutline(Sheriff.currentTarget, Sheriff.color); + } + + private static void deputySetTarget() + { + if (Deputy.deputy == null || Deputy.deputy != CachedPlayer.LocalPlayer.PlayerControl) return; + Deputy.currentTarget = setTarget(); + setPlayerOutline(Deputy.currentTarget, Deputy.color); + } + + public static void arsonistSetTarget() + { + if (Arsonist.arsonist == null || Arsonist.arsonist != CachedPlayer.LocalPlayer.PlayerControl) return; + List untargetables; + if (Arsonist.douseTarget != null) + { + untargetables = new(); + foreach (var cachedPlayer in CachedPlayer.AllPlayers) + if (cachedPlayer.PlayerId != Arsonist.douseTarget.PlayerId) + untargetables.Add(cachedPlayer); + } + else + { + untargetables = Arsonist.dousedPlayers; } + + Arsonist.currentTarget = setTarget(untargetablePlayers: untargetables); + if (Arsonist.currentTarget != null) setPlayerOutline(Arsonist.currentTarget, Arsonist.color); + + Arsonist.currentTarget2 = setTarget(false, true); + if (Arsonist.currentTarget2 != null) setPlayerOutline(Arsonist.currentTarget2, Arsonist.color); } + } [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.WalkPlayerTo))] @@ -2033,36 +1981,41 @@ private static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] GameD var timeSinceDeath = (float)(DateTime.UtcNow - deadPlayer.TimeOfDeath).TotalMilliseconds; var msg = ""; var killer = deadPlayer.KillerIfExisting; - + float timer = (float)Math.Round(timeSinceDeath / 1000); + if (Vortox.Reversal) + { + timer += rnd.Next(-2, 3); + if (timer < 0) timer = 1; + } if (isMedicReport) { - if (timeSinceDeath < Medic.ReportNameDuration * 1000) + if (timer <= Medic.ReportNameDuration) { - msg = $"尸检报告: 凶手似乎是 {killer.Data.PlayerName}!\n尸体在 {Math.Round(timeSinceDeath / 1000)} 秒前死亡"; + msg = $"尸检报告: 凶手似乎是 {killer.Data.PlayerName}!\n尸体在 {timer} 秒前死亡"; } - else if (timeSinceDeath < Medic.ReportColorDuration * 1000) + else if (timer <= Medic.ReportColorDuration) { var typeOfColor = isLighterColor(killer) ? "浅" : "深"; - msg = $"尸检报告: 凶手的颜色似乎是 {typeOfColor} 色的!\n尸体在{Math.Round(timeSinceDeath / 1000)}秒前死亡"; + msg = $"尸检报告: 凶手的颜色似乎是 {typeOfColor} 色的!\n尸体在{timer}秒前死亡"; } else { - msg = $"尸检报告: 死亡时间太久,无法获取信息! \n尸体在{Math.Round(timeSinceDeath / 1000)}秒前死亡"; + msg = $"尸检报告: 死亡时间太久,无法获取信息! \n尸体在{timer}秒前死亡"; } } else if (isDetectiveReport) { - if (timeSinceDeath < Detective.reportNameDuration * 1000) + if (timer <= Detective.reportNameDuration) { - msg = $"尸检报告: 凶手的职业似乎是 {RoleInfo.getRoleInfoForPlayer(killer, false).First().Name} !\n尸体在 {Math.Round(timeSinceDeath / 1000)} 秒前死亡"; + msg = $"尸检报告: 凶手的职业似乎是 {RoleInfo.getRoleInfoForPlayer(killer, false).First().Name} !\n尸体在 {timer} 秒前死亡"; } - else if (timeSinceDeath < Detective.reportColorDuration * 1000) + else if (timer <= Detective.reportColorDuration) { - msg = $"尸检报告: 凶手的阵营似乎是 {teamString(killer)} !\n尸体在{Math.Round(timeSinceDeath / 1000)}秒前死亡"; + msg = $"尸检报告: 凶手的阵营似乎是 {teamString(killer)} !\n尸体在{timer}秒前死亡"; } else { - msg = $"尸检报告: 死亡时间太久,无法获取信息\n尸体在 {Math.Round(timeSinceDeath / 1000)} 秒前死亡"; + msg = $"尸检报告: 死亡时间太久,无法获取信息\n尸体在 {timer} 秒前死亡"; } } @@ -2238,8 +2191,7 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] Player } else { - BountyHunter.bountyHunter.SetKillTimer( - GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown + BountyHunter.punishmentTime); + BountyHunter.bountyHunter.SetKillTimer(ModOption.KillCooddown + BountyHunter.punishmentTime); } } @@ -2367,10 +2319,8 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] float t if (LastImpostor.lastImpostor != null && CachedPlayer.LocalPlayer.PlayerControl == LastImpostor.lastImpostor) addition -= LastImpostor.deduce; - __instance.killTimer = Mathf.Clamp(time, 0f, - (GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown * multiplier) + addition); - FastDestroyableSingleton.Instance.KillButton.SetCoolDown(__instance.killTimer, - (GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown * multiplier) + addition); + __instance.killTimer = Mathf.Clamp(time, 0f, (ModOption.KillCooddown * multiplier) + addition); + FastDestroyableSingleton.Instance.KillButton.SetCoolDown(__instance.killTimer, (ModOption.KillCooddown * multiplier) + addition); return false; } } @@ -2440,10 +2390,9 @@ public static void Postfix(PlayerControl __instance) __instance.clearAllTasks(); // Lover suicide trigger on exile - if ((Lovers.lover1 != null && __instance == Lovers.lover1) || - (Lovers.lover2 != null && __instance == Lovers.lover2)) + if (__instance.isLover() && Lovers.otherLover(__instance) != null) { - var otherLover = __instance == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1; + var otherLover = Lovers.otherLover(__instance); if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) { otherLover.Exiled(); @@ -2466,11 +2415,8 @@ public static void Postfix(PlayerControl __instance) AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.sidekickPromotes(Jackal.sidekick.PlayerId); } - - // Pursuer promotion trigger on exile & suicide (the host sends the call such that everyone recieves the update before a possible game End) if (Lawyer.lawyer != null && __instance == Lawyer.target) { - var lawyer = Lawyer.lawyer; if (AmongUsClient.Instance.AmHost && ((Lawyer.target != Jester.jester) || Lawyer.targetWasGuessed)) { var writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, @@ -2478,20 +2424,7 @@ public static void Postfix(PlayerControl __instance) AmongUsClient.Instance.FinishRpcImmediately(writer); Lawyer.PromotesToPursuer(); } - - if (!Lawyer.targetWasGuessed) - { - Lawyer.lawyer?.Exiled(); - if (Pursuer.Player != null) - { - foreach (var pursuer in Pursuer.Player.Where(x => x.PlayerId == lawyer.PlayerId)) pursuer?.Exiled(); - } - - RpcOverrideDeathReasonAndKiller(lawyer, CustomDeathReason.LawyerSuicide, lawyer); - // TODO: only executed on host?! - } } - if (Executioner.executioner != null && __instance == Executioner.target) { if (AmongUsClient.Instance.AmHost && Executioner.targetWasGuessed) @@ -2530,25 +2463,3 @@ public static void Postfix(PlayerControl __instance) } } } - -[HarmonyPatch(typeof(GameData), nameof(GameData.HandleDisconnect), typeof(PlayerControl), typeof(DisconnectReasons))] -public static class GameDataHandleDisconnectPatch -{ - public static void Prefix(GameData __instance, PlayerControl player, DisconnectReasons reason) - { - if (MeetingHud.Instance) MeetingHudPatch.swapperCheckAndReturnSwap(MeetingHud.Instance, player.PlayerId); - if (Executioner.executioner.IsAlive() && Executioner.executioner == PlayerControl.LocalPlayer && Executioner.target == player) - { - var writer = StartRPC(PlayerControl.LocalPlayer, CustomRPC.ExecutionerPromotesRole); - writer.EndRPC(); - Executioner.PromotesRole(); - } - - if (Lawyer.lawyer.IsAlive() && Lawyer.lawyer == PlayerControl.LocalPlayer && Lawyer.target == player) - { - var writer = StartRPC(PlayerControl.LocalPlayer, CustomRPC.LawyerPromotesToPursuer); - writer.EndRPC(); - Executioner.PromotesRole(); - } - } -} diff --git a/TheOtherRoles/Patches/PlayerPhysicsPatch.cs b/TheOtherRoles/Patches/PlayerPhysicsPatch.cs index 01b288ce..9d3a2fa8 100644 --- a/TheOtherRoles/Patches/PlayerPhysicsPatch.cs +++ b/TheOtherRoles/Patches/PlayerPhysicsPatch.cs @@ -14,7 +14,7 @@ public static void Postfix(PlayerPhysics __instance) __instance.body.velocity *= -1; if (Flash.flash != null && Flash.flash.Any(x => x.PlayerId == CachedPlayer.LocalId)) __instance.body.velocity *= Flash.speed; - if (Giant.giant != null && Giant.giant == PlayerControl.LocalPlayer && !isCamoComms && Camouflager.camouflageTimer <= 0f) + if (Giant.giant != null && Giant.giant == PlayerControl.LocalPlayer && !MushroomSabotageActive && !isCamoComms && Camouflager.camouflageTimer <= 0f) __instance.body.velocity *= Giant.speed; if (Swooper.swooper != null && Swooper.swooper == PlayerControl.LocalPlayer && Swooper.isInvisable) __instance.body.velocity *= Swooper.swoopSpeed; diff --git a/TheOtherRoles/Patches/RoleAssignmentPatch.cs b/TheOtherRoles/Patches/RoleAssignmentPatch.cs index 0bd30edd..9df7ec89 100644 --- a/TheOtherRoles/Patches/RoleAssignmentPatch.cs +++ b/TheOtherRoles/Patches/RoleAssignmentPatch.cs @@ -139,6 +139,7 @@ public static RoleAssignmentData getRoleAssignmentData() impSettings.Add((byte)RoleId.EvilTrapper, CustomOptionHolder.evilTrapperSpawnRate.GetSelection()); impSettings.Add((byte)RoleId.Gambler, CustomOptionHolder.gamblerSpawnRate.GetSelection()); impSettings.Add((byte)RoleId.Grenadier, CustomOptionHolder.grenadierSpawnRate.GetSelection()); + impSettings.Add((byte)RoleId.WolfLord, CustomOptionHolder.wolfLordSpawnRate.GetSelection()); neutralSettings.Add((byte)RoleId.Survivor, CustomOptionHolder.survivorSpawnRate.GetSelection()); //neutralSettings.Add((byte)RoleId.Pursuer, CustomOptionHolder.pursuerSpawnRate.getSelection()); @@ -544,6 +545,7 @@ private static void assignModifiers() RoleId.Radar, RoleId.Disperser, RoleId.Specoality, + RoleId.Vortox, RoleId.PoucherModifier, RoleId.Cursed, RoleId.Chameleon, @@ -782,6 +784,14 @@ private static void assignModifiersToPlayers(List modifiers, List x == RoleId.Specoality); } + if (modifiers.Contains(RoleId.Vortox)) + { + playerId = setModifierToRandomPlayer((byte)RoleId.Vortox, impPlayer); + impPlayer.RemoveAll(x => x.PlayerId == playerId); + playerList.RemoveAll(x => x.PlayerId == playerId); + modifiers.RemoveAll(x => x == RoleId.Vortox); + } + if (modifiers.Contains(RoleId.PoucherModifier)) { playerId = setModifierToRandomPlayer((byte)RoleId.PoucherModifier, impPlayer); @@ -982,6 +992,9 @@ private static int getSelectionForRoleId(RoleId roleId, bool multiplyQuantity = case RoleId.PoucherModifier: if (Poucher.spawnModifier) selection = CustomOptionHolder.modifierPoucher.GetSelection(); break; + case RoleId.Vortox: + selection = CustomOptionHolder.modifierVortox.GetSelection(); + break; case RoleId.Mini: selection = CustomOptionHolder.modifierMini.GetSelection(); break; diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 93893c26..0c327280 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -44,6 +44,7 @@ private static void resetNameTagsAndColors() player.cosmetics.colorBlindText.gameObject.SetActive(!hidePlayerName(localPlayer, player)); } + player.cosmetics.colorBlindText.gameObject.transform.SetLocalZ(0.0001f); nameText.color = color = amImpostor && data.Role.IsImpostor ? Palette.ImpostorRed : Color.white; nameText.color = nameText.color.SetAlpha(Chameleon.visibility(player.PlayerId)); } @@ -107,7 +108,7 @@ private static void setNameColors() { foreach (var p in Prophet.examined) { - setPlayerNameColor(p.Key, p.Value ? Palette.ImpostorRed : Color.green); + setPlayerNameColor(p.Key, p.Value ^ Vortox.Reversal ? Palette.ImpostorRed : Color.green); } } } @@ -127,6 +128,11 @@ private static void setNameColors() setPlayerNameColor(Mayor.mayor, Mayor.color); } + if (WolfLord.Player != null && WolfLord.Revealed) + { + setPlayerNameColor(WolfLord.Player, WolfLord.color); + } + if (Grenadier.grenadier != null && ((localPlayer.isImpostor() && Grenadier.indicatorsMode > 1) || localPlayer == Grenadier.grenadier || localPlayer.IsDead())) { @@ -139,8 +145,7 @@ private static void setNameColors() if (Jackal.jackal != null && Jackal.jackal.Any(x => x == localPlayer)) { // Jackal can see his sidekick - foreach (var p in Jackal.jackal) - setPlayerNameColor(p, Jackal.color); + foreach (var p in Jackal.jackal) setPlayerNameColor(p, Jackal.color); if (Jackal.sidekick != null) setPlayerNameColor(Jackal.sidekick, Jackal.color); } @@ -149,28 +154,18 @@ private static void setNameColors() { // Sidekick can see the jackal setPlayerNameColor(Jackal.sidekick, Jackal.color); - foreach (var p in Jackal.jackal) - setPlayerNameColor(p, Jackal.color); + foreach (var p in Jackal.jackal) setPlayerNameColor(p, Jackal.color); } if (Pavlovsdogs.pavlovsowner != null && Pavlovsdogs.pavlovsowner == localPlayer) { setPlayerNameColor(Pavlovsdogs.pavlovsowner, Pavlovsdogs.color); - if (Pavlovsdogs.pavlovsdogs != null) - { - foreach (var p in Pavlovsdogs.pavlovsdogs) - { - setPlayerNameColor(p, Pavlovsdogs.color); - } - } + foreach (var p in Pavlovsdogs.pavlovsdogs) setPlayerNameColor(p, Pavlovsdogs.color); } if (Pavlovsdogs.pavlovsdogs != null && Pavlovsdogs.pavlovsdogs.Any(p => p == localPlayer)) { - foreach (var p in Pavlovsdogs.pavlovsdogs) - { - setPlayerNameColor(p, Pavlovsdogs.color); - } + foreach (var p in Pavlovsdogs.pavlovsdogs) setPlayerNameColor(p, Pavlovsdogs.color); if (Pavlovsdogs.pavlovsowner != null) setPlayerNameColor(Pavlovsdogs.pavlovsowner, Pavlovsdogs.color); } @@ -221,9 +216,6 @@ private static void setNameColors() // No else if here, as the Impostors need the Spy name to be colored if (Spy.spy != null && localPlayer.Data.Role.IsImpostor) setPlayerNameColor(Spy.spy, Spy.color); - - // Crewmate roles with no changes: Mini - // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock, BountyHunter, Witch and Mafioso } private static void setNameTags() diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 043d4bb4..d38d5f8d 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -30,6 +30,7 @@ public enum RoleId Impostor, Morphling, + WolfLord, Bomber, Poucher, Butcher, @@ -106,6 +107,7 @@ public enum RoleId Assassin, Disperser, PoucherModifier, + Vortox, Specoality, LastImpostor, Bloody, @@ -236,7 +238,7 @@ public enum CustomRPC GrenadierFlash, WitnessReport, WitnessSetTarget, - WitnessWin, + WolfLordkilled, TrapperKill, PlaceTrap, @@ -293,7 +295,7 @@ public static void resetVariables() reloadPluginOptions(); clearAndReloadMapOptions(); clearAndReloadRoles(); - MapData.PositionCached.Clear(); + MapData.Clear(); Garlic.clearGarlics(); JackInTheBox.clearJackInTheBoxes(); NinjaTrace.clearTraces(); @@ -373,6 +375,9 @@ public static void setRole(byte roleId, byte playerId) case RoleId.Werewolf: Werewolf.werewolf = player; break; + case RoleId.WolfLord: + WolfLord.Player = player; + break; case RoleId.Blackmailer: Blackmailer.blackmailer = player; break; @@ -638,6 +643,9 @@ public static void setModifier(byte modifierId, byte playerId, byte flag) case RoleId.Radar: Radar.radar = player; break; + case RoleId.Vortox: + Vortox.Player = player; + break; case RoleId.Tunneler: Tunneler.tunneler = player; break; @@ -1268,6 +1276,7 @@ public static void erasePlayerRoles(byte playerId, bool ignoreModifier = true) if (player == Cleaner.cleaner) Cleaner.clearAndReload(); if (player == Undertaker.undertaker) Undertaker.clearAndReload(); if (player == Mimic.mimic) Mimic.clearAndReload(); + if (player == WolfLord.Player) WolfLord.ClearAndReload(); if (player == Warlock.warlock) Warlock.clearAndReload(); if (player == Butcher.butcher) Butcher.clearAndReload(); if (player == Witch.witch) Witch.clearAndReload(); @@ -1309,6 +1318,7 @@ public static void erasePlayerRoles(byte playerId, bool ignoreModifier = true) if (player == Akujo.akujo) Akujo.clearAndReload(); if (player == Witness.Player) Witness.ClearAndReload(); if (player == PartTimer.partTimer) PartTimer.clearAndReload(); + if (player == Vortox.Player) Vortox.ClearAndReload(); if (player == Cursed.cursed) Cursed.clearAndReload(); if (player == Shifter.shifter) Shifter.clearAndReload(); @@ -1915,154 +1925,6 @@ public static void executionerSetTarget(byte playerId) Executioner.target = playerById(playerId); } - public static void guesserShoot(byte killerId, byte dyingTargetId, byte guessedTargetId, byte guessedRoleId) - { - var dyingTarget = playerById(dyingTargetId); - var guessedTarget = playerById(guessedTargetId); - var guesser = playerById(killerId); - if (dyingTarget == null) return; - - var dyingPartner = dyingTarget.getPartner(); - - // Lawyer shouldn't be exiled with the client for guesses - if (Lawyer.target != null && (dyingTarget == Lawyer.target || dyingPartner == Lawyer.target)) - Lawyer.targetWasGuessed = true; - - if (Executioner.target != null && (dyingTarget == Executioner.target || dyingPartner == Executioner.target)) - Executioner.targetWasGuessed = true; - - if (Witch.witch != null && (dyingTarget == Witch.witch || dyingPartner == Witch.witch)) - Witch.witchWasGuessed = true; - - if (Thief.thief != null && Thief.thief.PlayerId == killerId && Thief.canStealWithGuess) - { - var roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); - if (Thief.thief.IsAlive() && Thief.tiefCanKill(dyingTarget, guesser)) - Thief.StealsRole(dyingTarget.PlayerId); - } - - if (Doomsayer.doomsayer != null && Doomsayer.doomsayer == guesser && Doomsayer.canGuess) - { - var roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); - if (!Doomsayer.doomsayer.Data.IsDead && guessedTargetId == dyingTargetId) - { - Doomsayer.killedToWin++; - if (Doomsayer.killedToWin >= Doomsayer.killToWin) Doomsayer.triggerDoomsayerrWin = true; - if (Guesser.guesserUI != null) Guesser.guesserUIExitButton.OnClick.Invoke(); - } - else - { - seedGuessChat(guesser, guessedTarget, guessedRoleId); - return; - } - } - - bool lawyerDiedAdditionally = false; - if (Lawyer.lawyer != null && Lawyer.lawyer.PlayerId == killerId && Lawyer.target != null && Lawyer.target.PlayerId == dyingTargetId) - { - // Lawyer guessed client. - if (CachedPlayer.LocalPlayer.PlayerControl == Lawyer.lawyer) - { - FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(Lawyer.lawyer.Data, Lawyer.lawyer.Data); - if (Guesser.guesserUI != null) Guesser.guesserUIExitButton.OnClick.Invoke(); - } - - Lawyer.lawyer.Exiled(); - lawyerDiedAdditionally = true; - OverrideDeathReasonAndKiller(Lawyer.lawyer, CustomDeathReason.LawyerSuicide, guesser); - } - - byte partnerId = dyingPartner != null ? dyingPartner.PlayerId : dyingTargetId; - - dyingTarget.Exiled(); - OverrideDeathReasonAndKiller(dyingTarget, CustomDeathReason.Guess, guesser); - if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(dyingTarget.KillSfx, false, 0.8f); - - if (MeetingHud.Instance) - { - MeetingHud.Instance.discussionTimer -= CustomOptionHolder.guessExtendmeetingTime.GetFloat(); - MeetingHudPatch.swapperCheckAndReturnSwap(MeetingHud.Instance, dyingTargetId); - - foreach (var pva in MeetingHud.Instance.playerStates) - { - bool shouldClearVote = CustomOptionHolder.guessReVote.GetBool() - || pva.VotedFor == dyingTargetId || pva.VotedFor == partnerId - || (lawyerDiedAdditionally && Lawyer.lawyer?.PlayerId == pva.TargetPlayerId); - - if (shouldClearVote) - { - pva.UnsetVote(); - var voteAreaPlayer = playerById(pva.TargetPlayerId); - if (voteAreaPlayer?.AmOwner == false) continue; - MeetingHud.Instance.ClearVote(); - MeetingHudPatch.swapperCheckAndReturnSwap(MeetingHud.Instance, partnerId); - } - } - - if (AmongUsClient.Instance.AmHost) - MeetingHud.Instance.CheckForEndVoting(); - } - - if (Doomsayer.doomsayer == null || Doomsayer.doomsayer != guesser) - { - HandleGuesser.remainingShots(killerId, true); - } - - if (FastDestroyableSingleton.Instance != null && guesser != null) - { - if (CachedPlayer.LocalPlayer.PlayerControl == dyingTarget) - { - FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(guesser.Data, dyingTarget.Data); - if (Guesser.guesserUI != null) Guesser.guesserUIExitButton.OnClick.Invoke(); - } - else if (dyingPartner != null && CachedPlayer.LocalPlayer.PlayerControl == dyingPartner) - { - FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(dyingPartner.Data, dyingPartner.Data); - if (Guesser.guesserUI != null) Guesser.guesserUIExitButton.OnClick.Invoke(); - } - } - - // remove shoot button from targets for all guessers and close their guesserUI - if (GuesserGM.isGuesser(PlayerControl.LocalPlayer.PlayerId) && PlayerControl.LocalPlayer != guesser && - !PlayerControl.LocalPlayer.Data.IsDead && - GuesserGM.remainingShots(PlayerControl.LocalPlayer.PlayerId) > 0 && MeetingHud.Instance) - { - MeetingHud.Instance.playerStates.ToList().ForEach(x => - { - if (x.TargetPlayerId == dyingTarget.PlayerId && x.transform.FindChild("ShootButton") != null) - Object.Destroy(x.transform.FindChild("ShootButton").gameObject); - }); - - if (dyingPartner != null) - { - MeetingHud.Instance.playerStates.ToList().ForEach(x => - { - if (x.TargetPlayerId == dyingPartner.PlayerId && x.transform.FindChild("ShootButton") != null) - Object.Destroy(x.transform.FindChild("ShootButton").gameObject); - }); - } - - if (Guesser.guesserUI != null && Guesser.guesserUIExitButton != null) - { - Guesser.guesserUIExitButton.OnClick.Invoke(); - } - } - if (guesser != null && guessedTarget != null) seedGuessChat(guesser, guessedTarget, guessedRoleId); - } - - public static void seedGuessChat(PlayerControl guesser, PlayerControl guessedTarget, byte guessedRoleId) - { - if (PlayerControl.LocalPlayer.IsDead() && PlayerControl.LocalPlayer != Specter.Player) - { - var roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); - var msg = $"{guesser.Data.PlayerName} 赌怪猜测 {guessedTarget.Data.PlayerName} 是 {roleInfo?.Name ?? ""}!"; - if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton.Instance) - { - _ = new LateTask(() => { FastDestroyableSingleton.Instance!.Chat.AddChat(guesser, msg); }, 0.1f, "Guess Chat"); - } - } - } - public static void useCameraTime(float time) { restrictCamerasTime -= time; @@ -2560,11 +2422,7 @@ private static bool Prefix([HarmonyArgument(0)] byte callId, [HarmonyArgument(1) break; case CustomRPC.GuesserShoot: - var killerId = reader.ReadByte(); - var dyingTarget = reader.ReadByte(); - var guessedTarget = reader.ReadByte(); - var guessedRoleId = reader.ReadByte(); - RPCProcedure.guesserShoot(killerId, dyingTarget, guessedTarget, guessedRoleId); + Guesser.guesserShoot(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); break; case CustomRPC.LawyerSetTarget: @@ -2572,7 +2430,7 @@ private static bool Prefix([HarmonyArgument(0)] byte callId, [HarmonyArgument(1) break; case CustomRPC.LawyerPromotesToPursuer: - Lawyer.PromotesToPursuer(); + Lawyer.PromotesToPursuer(reader.ReadBoolean()); break; case CustomRPC.ExecutionerSetTarget: @@ -2738,9 +2596,8 @@ private static bool Prefix([HarmonyArgument(0)] byte callId, [HarmonyArgument(1) case CustomRPC.WitnessSetTarget: Witness.target = playerById(reader.ReadByte()); break; - case CustomRPC.WitnessWin: - Witness.triggerWitnessWin = true; - Message("WitnessWin!"); + case CustomRPC.WolfLordkilled: + WolfLord.WolfLordkilled(reader.ReadByte()); break; case CustomRPC.YoyoBlink: RPCProcedure.yoyoBlink(reader.ReadByte() == byte.MaxValue, reader.ReadBytesAndSize()); diff --git a/TheOtherRoles/Resources/stringData.json b/TheOtherRoles/Resources/stringData.json index f2ccc39c..1f4b81ad 100644 --- a/TheOtherRoles/Resources/stringData.json +++ b/TheOtherRoles/Resources/stringData.json @@ -1546,6 +1546,9 @@ "witnessMeetingDie": { "13": "目标在会议中死亡也加分" }, + "witnessSkipMeeting": { + "13": "跳过会议时不会计分" + }, "partTimerCooldown": { "13": "打工冷却时间" }, @@ -3143,6 +3146,10 @@ "AssassinShortDesc": { "13": "生命就是一场豪赌!" }, + "WolfLord": { + "0": "WolfLord", + "13": "狼之主" + }, "Morphling": { "0": "Morphling", "13": "化形者" @@ -3863,6 +3870,16 @@ "SpecoalityShortDesc": { "13": "可以失误一次" }, + "Vortox": { + "0": "Vortox", + "13": "迷乱旋涡" + }, + "VortoxIntroDesc": { + "13": "反转反转再反转" + }, + "VortoxShortDesc": { + "13": "干扰船员信息" + }, "Bloody": { "0": "Bloody", "13": "溅血者" @@ -4171,6 +4188,9 @@ "AssassinFullDesc": { "13": "分配阵营:伪装者\n刺客使红狼具有会议上猜测别人职业的能力。\n所有刺客拥有共享的猜测次数。\n\n注:赌怪模式中不会出现侠客与刺客" }, + "WolfLordFullDesc": { + "13": "在会议中可以揭示身份,获得一次在会议中击杀玩家的机会。 \n揭示身份后无法再使用赌怪能力,\n同时,如果使用赌怪能力则本轮会议无法揭示身份。" + }, "MorphlingFullDesc": { "13": "采样后可以伪装成其他人。" }, @@ -4277,7 +4297,7 @@ "13": "拥有隐形和击杀的能力,并且隐身时拥有更快的移动速度!\n需要将所有玩家击杀才能胜利。" }, "ArsonistFullDesc": { - "13": "可以给玩家涂油,随时可以在已涂油的目标附近点火。\n点火时会同时击杀所有已涂了油的玩家!(无视各种保护强制击杀)\n\n根据设置,在仅剩纵火狂一名杀手玩家时可以在会议结束后移除点火的冷却。" + "13": "可以给玩家涂油,随时可以在已涂油的目标附近点火。\n点火时会同时击杀所有已涂了油的玩家!(无视各种保护强制击杀)\n另外,纵火狂的技能选中距离会比其他玩家更长。\n\n根据设置,在仅剩纵火狂一名杀手玩家时可以在会议结束后移除点火的冷却。" }, "WerewolfFullDesc": { "13": "月下狼人可以使用技能进入一段时间的狂暴状态。\n狂暴状态下,击杀CD为3秒。" @@ -4390,6 +4410,9 @@ "LastImpostorFullDesc": { "13": "分配阵营:伪装者\n\n仅剩余一名伪装者时,击杀冷却减少\n(目前赏金猎人与赌徒无法无效)" }, + "VortoxFullDesc": { + "13": "分配阵营:伪装者\n\n存活时会干扰部分信息类船员职业,查验结果会被反转,\n尸检报告的死亡时间也会受到一定的干扰。\n另外根据设置,存活时如果会议跳过n次红狼方可直接获胜。\n\n注:干扰对被法医保护的玩家不生效。\n\n目前的干扰会影响的职业有:预言家、情报师、设陷师,\n尸检类职业:法医、侦探。" + }, "BloodyFullDesc": { "13": "分配阵营:全部玩家\n\n被击杀后,凶手会在地上留下血迹" }, diff --git a/TheOtherRoles/Roles/Ghost/Specter.cs b/TheOtherRoles/Roles/Ghost/Specter.cs index 4c9a0a08..a81b23f2 100644 --- a/TheOtherRoles/Roles/Ghost/Specter.cs +++ b/TheOtherRoles/Roles/Ghost/Specter.cs @@ -85,6 +85,10 @@ public static void TakeRole(byte targetId) if (resetRole) Undertaker.clearAndReload(); Undertaker.undertaker = local; break; + case RoleId.WolfLord: + if (resetRole) WolfLord.ClearAndReload(); + WolfLord.Player = local; + break; case RoleId.Escapist: if (resetRole) Escapist.clearAndReload(); Escapist.escapist = local; @@ -320,6 +324,7 @@ public static void TakeRole(byte targetId) Balancer.balancer = local; break; } + AntiTeleport.antiTeleport.RemoveAll(x => x.PlayerId == local.PlayerId); RPCProcedure.clearGhostRoles(local.PlayerId); local.Revive(); } diff --git a/TheOtherRoles/Roles/Guesser.cs b/TheOtherRoles/Roles/Guesser.cs index 6ebc15a7..1865474b 100644 --- a/TheOtherRoles/Roles/Guesser.cs +++ b/TheOtherRoles/Roles/Guesser.cs @@ -3,9 +3,12 @@ using System.Linq; using Hazel; using Reactor.Utilities; +using TheOtherRoles.CustomGameModes; +using TheOtherRoles.Patches; using TheOtherRoles.Utilities; using TMPro; using UnityEngine; +using static TheOtherRoles.GameHistory; using Object = UnityEngine.Object; namespace TheOtherRoles.Roles; @@ -249,7 +252,7 @@ static void CreatePage(bool IsNext, MeetingHud __instance, Transform container) if (roleInfo.roleType == RoleType.Modifier && ModOption.allowModGuess && !roleInfo.isGuessable) continue; - if (roleInfo.roleType == RoleType.Ghost) + if (roleInfo.roleType is not RoleType.Crewmate and not RoleType.Neutral and not RoleType.Impostor and not RoleType.Modifier) continue; // remove all roles that cannot spawn due to the settings from the ui. @@ -265,6 +268,8 @@ static void CreatePage(bool IsNext, MeetingHud __instance, Transform container) continue; case RoleId.Mayor when Mayor.Revealed: continue; + case RoleId.WolfLord when WolfLord.Revealed: + continue; case RoleId.Vigilante when HandleGuesser.isGuesserGm || CachedPlayer.LocalPlayer.PlayerId == Vigilante.vigilante?.PlayerId: continue; case RoleId.Sidekick when !CustomOptionHolder.jackalCanCreateSidekick.GetBool(): @@ -354,8 +359,7 @@ void CreateRole(RoleInfo roleInfo = null) } if (focusedTarget == Indomitable.indomitable) { - //showFlash(new Color32(255, 197, 97, byte.MinValue)); - showFlash(Color.yellow, 0.75f); + showFlash(new Color32(255, 197, 97, byte.MinValue)); __instance.playerStates.ForEach(x => x.gameObject.SetActive(true)); Object.Destroy(container.gameObject); @@ -364,7 +368,7 @@ void CreateRole(RoleInfo roleInfo = null) AmongUsClient.Instance.FinishRpcImmediately(murderAttemptWriter); RPCProcedure.shieldedMurderAttempt(0); SoundEffectsManager.play("fail"); - RPCProcedure.seedGuessChat(CachedPlayer.LocalPlayer.PlayerControl, dyingTarget, (byte)roleInfo.roleId); + seedGuessChat(CachedPlayer.LocalPlayer.PlayerControl, dyingTarget, (byte)roleInfo.roleId); return; } @@ -393,7 +397,6 @@ void CreateRole(RoleInfo roleInfo = null) { if (x.TargetPlayerId == focusedTarget.PlayerId && x.transform.FindChild("ShootButton") != null) { - Message("清除按钮"); Object.Destroy(x.transform.FindChild("ShootButton").gameObject); } }); @@ -409,7 +412,7 @@ void CreateRole(RoleInfo roleInfo = null) writer.Write(focusedTarget.PlayerId); writer.Write((byte)roleInfo.roleId); AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.guesserShoot(CachedPlayer.LocalPlayer.PlayerId, dyingTarget.PlayerId, focusedTarget.PlayerId, (byte)roleInfo.roleId); + guesserShoot(CachedPlayer.LocalPlayer.PlayerId, dyingTarget.PlayerId, focusedTarget.PlayerId, (byte)roleInfo.roleId); // Reset the GUI __instance.playerStates.ForEach(x => x.gameObject.SetActive(true)); @@ -438,4 +441,151 @@ void CreateRole(RoleInfo roleInfo = null) guesserSelectRole(RoleType.Crewmate); ReloadPage(); } + + public static void guesserShoot(byte killerId, byte dyingTargetId, byte guessedTargetId, byte guessedRoleId) + { + var dyingTarget = playerById(dyingTargetId); + var guessedTarget = playerById(guessedTargetId); + var guesser = playerById(killerId); + if (dyingTarget == null) return; + + var dyingPartner = dyingTarget.getPartner(); + + // Lawyer shouldn't be exiled with the client for guesses + if (Lawyer.target != null && (dyingTarget == Lawyer.target || dyingPartner == Lawyer.target)) + Lawyer.targetWasGuessed = true; + + if (Executioner.target != null && (dyingTarget == Executioner.target || dyingPartner == Executioner.target)) + Executioner.targetWasGuessed = true; + + if (Witch.witch != null && (dyingTarget == Witch.witch || dyingPartner == Witch.witch)) + Witch.witchWasGuessed = true; + + if (Thief.thief != null && Thief.thief.PlayerId == killerId && Thief.canStealWithGuess) + { + var roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); + if (Thief.thief.IsAlive() && Thief.tiefCanKill(dyingTarget, guesser)) + Thief.StealsRole(dyingTarget.PlayerId); + } + + if (Doomsayer.doomsayer != null && Doomsayer.doomsayer == guesser && Doomsayer.canGuess) + { + var roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); + if (!Doomsayer.doomsayer.Data.IsDead && guessedTargetId == dyingTargetId) + { + Doomsayer.killedToWin++; + if (Doomsayer.killedToWin >= Doomsayer.killToWin) Doomsayer.triggerDoomsayerrWin = true; + if (guesserUI != null) guesserUIExitButton.OnClick.Invoke(); + } + else + { + seedGuessChat(guesser, guessedTarget, guessedRoleId); + return; + } + } + + bool lawyerDiedAdditionally = false; + if (Lawyer.lawyer != null && Lawyer.lawyer.PlayerId == killerId && Lawyer.target != null && Lawyer.target.PlayerId == dyingTargetId) + { + // Lawyer guessed client. + if (CachedPlayer.LocalPlayer.PlayerControl == Lawyer.lawyer) + { + FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(Lawyer.lawyer.Data, Lawyer.lawyer.Data); + } + + Lawyer.lawyer.Exiled(); + lawyerDiedAdditionally = true; + Message("辩护失败", "GuesserShoot"); + OverrideDeathReasonAndKiller(Lawyer.lawyer, CustomDeathReason.LawyerSuicide, guesser); + } + + byte partnerId = dyingPartner != null ? dyingPartner.PlayerId : dyingTargetId; + + dyingTarget.Exiled(); + OverrideDeathReasonAndKiller(dyingTarget, CustomDeathReason.Guess, guesser); + if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(dyingTarget.KillSfx, false, 0.8f); + + if (MeetingHud.Instance) + { + MeetingHud.Instance.discussionTimer -= CustomOptionHolder.guessExtendmeetingTime.GetFloat(); + MeetingHudPatch.swapperCheckAndReturnSwap(MeetingHud.Instance, dyingTargetId); + + foreach (var pva in MeetingHud.Instance.playerStates) + { + bool shouldClearVote = CustomOptionHolder.guessReVote.GetBool() + || pva.VotedFor == dyingTargetId || pva.VotedFor == partnerId + || (lawyerDiedAdditionally && Lawyer.lawyer?.PlayerId == pva.TargetPlayerId); + + if (shouldClearVote) + { + pva.UnsetVote(); + var voteAreaPlayer = playerById(pva.TargetPlayerId); + if (voteAreaPlayer?.AmOwner == false) continue; + MeetingHud.Instance.ClearVote(); + MeetingHudPatch.swapperCheckAndReturnSwap(MeetingHud.Instance, partnerId); + } + } + if (AmongUsClient.Instance.AmHost) MeetingHud.Instance.CheckForEndVoting(); + } + + HandleGuesser.remainingShots(killerId, true); + + if (FastDestroyableSingleton.Instance != null && guesser != null) + { + if (CachedPlayer.LocalPlayer.PlayerControl == dyingTarget) + { + FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(guesser.Data, dyingTarget.Data); + } + else if (dyingPartner != null && CachedPlayer.LocalPlayer.PlayerControl == dyingPartner) + { + FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(dyingPartner.Data, dyingPartner.Data); + } + } + + // remove shoot button from targets for all guessers and close their guesserUI + if (GuesserGM.isGuesser(PlayerControl.LocalPlayer.PlayerId) && PlayerControl.LocalPlayer != guesser && !PlayerControl.LocalPlayer.Data.IsDead && + GuesserGM.remainingShots(PlayerControl.LocalPlayer.PlayerId) > 0 && MeetingHud.Instance) + { + MeetingHud.Instance.playerStates.ToList().ForEach(x => + { + if (x.TargetPlayerId == dyingTarget.PlayerId && x.transform.FindChild("ShootButton") != null) + Object.Destroy(x.transform.FindChild("ShootButton")?.gameObject); + }); + + if (dyingPartner != null) + { + MeetingHud.Instance.playerStates.ToList().ForEach(x => + { + if (x.TargetPlayerId == dyingPartner.PlayerId && x.transform.FindChild("ShootButton") != null) + Object.Destroy(x.transform.FindChild("ShootButton")?.gameObject); + }); + } + + if (lawyerDiedAdditionally) + { + MeetingHud.Instance.playerStates.ToList().ForEach(x => + { + if (x.TargetPlayerId == Lawyer.lawyer?.PlayerId && x.transform.FindChild("ShootButton") != null) + Object.Destroy(x.transform.FindChild("ShootButton")?.gameObject); + }); + } + } + + if (guesserUI != null && guesserUIExitButton != null) guesserUIExitButton.OnClick.Invoke(); + if (guesser != null && guessedTarget != null) seedGuessChat(guesser, guessedTarget, guessedRoleId); + if (WolfLord.Player == guesser && !WolfLord.Revealed && PlayerControl.LocalPlayer == guesser) WolfLord.WolfLord_Patch.ClearButton(); + } + + public static void seedGuessChat(PlayerControl guesser, PlayerControl guessedTarget, byte guessedRoleId) + { + if (PlayerControl.LocalPlayer.IsDead() && PlayerControl.LocalPlayer != Specter.Player) + { + var roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); + var msg = $"{guesser.Data.PlayerName} 赌怪猜测 {guessedTarget.Data.PlayerName} 是 {roleInfo?.Name ?? ""}!"; + if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton.Instance) + { + _ = new LateTask(() => { FastDestroyableSingleton.Instance!.Chat.AddChat(guesser, msg); }, 0.1f, "Guess Chat"); + } + } + } } \ No newline at end of file diff --git a/TheOtherRoles/Roles/Impostor/WolfLord.cs b/TheOtherRoles/Roles/Impostor/WolfLord.cs new file mode 100644 index 00000000..66d9aeb3 --- /dev/null +++ b/TheOtherRoles/Roles/Impostor/WolfLord.cs @@ -0,0 +1,167 @@ +using System; +using System.Linq; +using TheOtherRoles.Utilities; +using TMPro; +using UnityEngine; +using static MeetingHud; +using Object = UnityEngine.Object; + +namespace TheOtherRoles.Roles.Impostor; + +public class WolfLord +{ + public static PlayerControl Player; + public static Color color = Palette.ImpostorRed; + + public static bool Revealed; + public static bool Killed; + + public static Sprite TargetSprite = new ResourceSprite("TargetIcon.png", 150); + + public static void ClearAndReload() + { + Player = null; + Revealed = false; + Killed = false; + } + + public static void WolfLordkilled(byte targetId) + { + var target = playerById(targetId); + Revealed = true; + if (target == null) return; + + Killed = true; + target.Exiled(); + GameHistory.OverrideDeathReasonAndKiller(target, CustomDeathReason.Kill, Player); + if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(target.KillSfx, false, 0.8f); + + if (CachedPlayer.LocalPlayer.PlayerControl == target) + FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(Player.Data, target.Data); + } + + [HarmonyPatch] + public static class WolfLord_Patch + { + private static TextMeshPro meetingExtraButtonLabel; + public static GameObject MeetingExtraButton; + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + [HarmonyPostfix] + internal static void MeetingStartPostfix(MeetingHud __instance) + { + if (__instance && !Killed && Revealed) { ButtonToggle(__instance); return; } + if (Player.IsAlive() && PlayerControl.LocalPlayer == Player && !Revealed) + { + var meetingUI = Object.FindObjectsOfType().FirstOrDefault(x => x.name == "PhoneUI"); + + var buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase"); + var maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea"); + var textTemplate = __instance.playerStates[0].NameText; + var meetingExtraButtonParent = new GameObject().transform; + meetingExtraButtonParent.SetParent(meetingUI); + var meetingExtraButton = Object.Instantiate(buttonTemplate, meetingExtraButtonParent); + MeetingExtraButton = meetingExtraButton.gameObject; + + var meetingExtraButtonMask = Object.Instantiate(maskTemplate, meetingExtraButtonParent); + meetingExtraButtonLabel = Object.Instantiate(textTemplate, meetingExtraButton); + meetingExtraButton.GetComponent().sprite = ShipStatus.Instance.CosmeticsCache.GetNameplate("nameplate_NoPlate").Image; + + meetingExtraButtonParent.localPosition = new Vector3(0, -2.225f, -5); + meetingExtraButtonParent.localScale = new Vector3(0.55f, 0.55f, 1f); + meetingExtraButtonLabel.alignment = TextAlignmentOptions.Center; + meetingExtraButtonLabel.transform.localPosition = + new Vector3(0, 0, meetingExtraButtonLabel.transform.localPosition.z); + + var localScale = meetingExtraButtonLabel.transform.localScale; + localScale = new Vector3( + localScale.x * 1.5f, + localScale.x * 1.7f, + localScale.x * 1.7f); + meetingExtraButtonLabel.transform.localScale = localScale; + meetingExtraButtonLabel.text = cs(color, "猎杀时刻"); + + var passiveButton = meetingExtraButton.GetComponent(); + passiveButton.OnClick.RemoveAllListeners(); + if (Player.IsAlive()) passiveButton.OnClick.AddListener((Action)(() => ButtonToggle(__instance))); + + meetingExtraButton.parent.gameObject.SetActive(false); + __instance.StartCoroutine(Effects.Lerp(7.27f, new Action(p => + { + if ((int)p == 1) meetingExtraButton.parent.gameObject.SetActive(true); + }))); + } + } + + public static void ClearButton() + { + if (MeetingExtraButton != null) Object.Destroy(MeetingExtraButton); + } + + private static void ButtonToggle(MeetingHud __instance) + { + __instance.playerStates[0].Cancel(); // This will stop the underlying buttons of the template from showing up + if (__instance.state == VoteStates.Results || Player.IsDead()) return; + + var writer = StartRPC(CachedPlayer.LocalPlayer.PlayerControl, CustomRPC.WolfLordkilled); + writer.Write(byte.MaxValue); + writer.EndRPC(); + WolfLordkilled(byte.MaxValue); + + Object.Destroy(MeetingExtraButton); + + foreach (var playerState in __instance.playerStates) + { + var guesser = playerState.transform.FindChild("ShootButton"); + if (guesser != null) Object.Destroy(guesser.gameObject); + } + + if (Guesser.guesserUI != null && Guesser.guesserUIExitButton != null) + Guesser.guesserUIExitButton.OnClick.Invoke(); + + if (PlayerControl.LocalPlayer == Player && PlayerControl.LocalPlayer.IsAlive()) + { + foreach (var pva in __instance.playerStates) + { + var player = playerById(pva.TargetPlayerId); + if (player.IsAlive() && player != Player && !player.isImpostor()) + { + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = Object.Instantiate(template, pva.transform); + targetBox.name = "WolfLordIcon"; + targetBox.transform.localPosition = new Vector3(1f, 0.03f, -1f); + SpriteRenderer renderer = targetBox.GetComponent(); + renderer.sprite = TargetSprite; + renderer.color = Color.red; + PassiveButton button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener(() => WolfLordOnClick(pva, __instance)); + } + } + } + } + + public static void WolfLordOnClick(PlayerVoteArea pva, MeetingHud __instance) + { + var target = playerById(pva.TargetPlayerId); + if (Player == null || !Revealed || Killed || target == null) return; + if (__instance.state is not (VoteStates.Voted or VoteStates.NotVoted)) return; + var writer = StartRPC(CachedPlayer.LocalPlayer.PlayerControl, CustomRPC.WolfLordkilled); + writer.Write(target.PlayerId); + writer.EndRPC(); + WolfLordkilled(target.PlayerId); + + foreach (var playerState in __instance.playerStates) + { + var icon = playerState.transform.FindChild("WolfLordIcon"); + if (icon != null) Object.Destroy(icon.gameObject); + + var guesser = playerState.transform.FindChild("ShootButton"); + if (guesser != null) Object.Destroy(guesser.gameObject); + } + + if (Guesser.guesserUI != null && Guesser.guesserUIExitButton != null) + Guesser.guesserUIExitButton.OnClick.Invoke(); + } + } +} diff --git a/TheOtherRoles/Roles/Modifier/Chameleon.cs b/TheOtherRoles/Roles/Modifier/Chameleon.cs index c7e8f9a5..ef99c4bf 100644 --- a/TheOtherRoles/Roles/Modifier/Chameleon.cs +++ b/TheOtherRoles/Roles/Modifier/Chameleon.cs @@ -35,9 +35,8 @@ public static float visibility(byte playerId) } } - if (PlayerControl.LocalPlayer.Data.IsDead && visibility < 0.1f) - // Ghosts can always see! - visibility = 0.1f; + // Ghosts can always see! + if (PlayerControl.LocalPlayer.Data.IsDead && visibility < 0.1f) visibility = 0.1f; return visibility; } @@ -80,9 +79,7 @@ public static void update() foreach (var shadowRend in chameleonPlayer.cosmetics.currentPet.shadows) shadowRend.color = shadowRend.color.SetAlpha(petVisibility); } - catch - { - } + catch { } } } } diff --git a/TheOtherRoles/Roles/Modifier/Giant.cs b/TheOtherRoles/Roles/Modifier/Giant.cs index 491f70f7..3bc1d21e 100644 --- a/TheOtherRoles/Roles/Modifier/Giant.cs +++ b/TheOtherRoles/Roles/Modifier/Giant.cs @@ -4,7 +4,7 @@ public static class Giant { public static PlayerControl giant; public static float speed = 0.72f; - public static float size = 1.08f; + public static readonly float size = 1.05f; public static void clearAndReload() { diff --git a/TheOtherRoles/Roles/Modifier/Radar.cs b/TheOtherRoles/Roles/Modifier/Radar.cs index d496b967..9282cf36 100644 --- a/TheOtherRoles/Roles/Modifier/Radar.cs +++ b/TheOtherRoles/Roles/Modifier/Radar.cs @@ -11,13 +11,10 @@ public static class Radar public static List localArrows = new(); public static PlayerControl ClosestPlayer; public static Color color = new Color32(255, 0, 128, byte.MaxValue); - public static bool showArrows = true; - public static void clearAndReload() { radar = null; - showArrows = true; if (localArrows != null) foreach (var arrow in localArrows) if (arrow?.arrow != null) diff --git a/TheOtherRoles/Roles/Modifier/Vortox.cs b/TheOtherRoles/Roles/Modifier/Vortox.cs new file mode 100644 index 00000000..8dd0476d --- /dev/null +++ b/TheOtherRoles/Roles/Modifier/Vortox.cs @@ -0,0 +1,25 @@ +using UnityEngine; + +namespace TheOtherRoles.Roles.Modifier; + +public class Vortox +{ + public static PlayerControl Player; + public static Color color = Palette.ImpostorRed; + + public static bool skipMeeting; + public static int skipMeetingNum; + public static int skipCount; + public static bool triggerImpWin; + + public static bool Reversal => Player.IsAlive() && PlayerControl.LocalPlayer != Medic.futureShielded; + + public static void ClearAndReload() + { + Player = null; + triggerImpWin = false; + skipMeeting = CustomOptionHolder.modifierVortoxSkipMeeting.GetBool(); + skipMeetingNum = CustomOptionHolder.modifierVortoxSkipNum.GetInt(); + skipCount = 0; + } +} diff --git a/TheOtherRoles/Roles/Neutral/Amnisiac.cs b/TheOtherRoles/Roles/Neutral/Amnisiac.cs index 4bede470..d31bf90e 100644 --- a/TheOtherRoles/Roles/Neutral/Amnisiac.cs +++ b/TheOtherRoles/Roles/Neutral/Amnisiac.cs @@ -377,6 +377,12 @@ public static void TakeRole(byte targetId, byte playerId) Warlock.warlock = local; break; + case RoleId.WolfLord: + Player.RemoveAll(x => x.PlayerId == local.PlayerId); + if (resetRole) WolfLord.ClearAndReload(); + WolfLord.Player = local; + break; + case RoleId.Grenadier: Player.RemoveAll(x => x.PlayerId == local.PlayerId); if (resetRole) Grenadier.clearAndReload(); diff --git a/TheOtherRoles/Roles/Neutral/Lawyer.cs b/TheOtherRoles/Roles/Neutral/Lawyer.cs index 6770634b..162e0f92 100644 --- a/TheOtherRoles/Roles/Neutral/Lawyer.cs +++ b/TheOtherRoles/Roles/Neutral/Lawyer.cs @@ -17,11 +17,17 @@ public static class Lawyer public static bool targetCanBeJester; public static bool targetWasGuessed; - - public static void PromotesToPursuer() + public static void PromotesToPursuer(bool suicide = false) { var player = lawyer; - var target = Lawyer.target; + + if (suicide) + { + player?.Exiled(); + Message("律师辩护失败", "Exiled"); + GameHistory.RpcOverrideDeathReasonAndKiller(player, CustomDeathReason.LawyerSuicide, player); + return; + } if (player.IsAlive() && target.IsDead()) { @@ -29,6 +35,7 @@ public static void PromotesToPursuer() clearAndReload(); } } + public static void clearAndReload(bool clearTarget = true) { lawyer = null; diff --git a/TheOtherRoles/Roles/Neutral/Thief.cs b/TheOtherRoles/Roles/Neutral/Thief.cs index f2c110c4..6e336889 100644 --- a/TheOtherRoles/Roles/Neutral/Thief.cs +++ b/TheOtherRoles/Roles/Neutral/Thief.cs @@ -97,6 +97,7 @@ public static void StealsRole(byte playerId) if (target == Cleaner.cleaner) Cleaner.cleaner = thief; if (target == Warlock.warlock) Warlock.warlock = thief; if (target == Grenadier.grenadier) Grenadier.grenadier = thief; + if (target == WolfLord.Player) WolfLord.Player = thief; if (target == BountyHunter.bountyHunter) BountyHunter.bountyHunter = thief; if (target == Witch.witch) { diff --git a/TheOtherRoles/Roles/Neutral/Witness.cs b/TheOtherRoles/Roles/Neutral/Witness.cs index 026d0842..fd42963d 100644 --- a/TheOtherRoles/Roles/Neutral/Witness.cs +++ b/TheOtherRoles/Roles/Neutral/Witness.cs @@ -16,6 +16,7 @@ public class Witness public static int markTimer; public static int exileToWin; public static bool meetingDie; + public static bool skipMeeting; public static Sprite TargetSprite = new ResourceSprite("TargetIcon.png", 150); @@ -36,15 +37,16 @@ public static void ClearAndReload() markTimer = CustomOptionHolder.witnessMarkTimer.GetInt() + 8; exileToWin = CustomOptionHolder.witnessWinCount.GetInt(); meetingDie = CustomOptionHolder.witnessMeetingDie.GetBool(); + skipMeeting = CustomOptionHolder.witnessSkipMeeting.GetBool(); } internal static void WitnessReport(byte targetId) { var target = playerById(targetId); - if (target == null || !Player.IsAlive() || PlayerControl.LocalPlayer != Player) return; + if (!Player.IsAlive()) return; - killerTarget = DetermineKillerTarget(target) ?? null; + killerTarget = DetermineKillerTarget(target); static PlayerControl DetermineKillerTarget(PlayerControl target) { @@ -77,8 +79,8 @@ public static void MeetingOnClick(PlayerVoteArea pva, MeetingHud __instance) foreach (var playerState in __instance.playerStates) { - var witnessIcon = playerState.transform.FindChild("WitnessIcon"); - if (witnessIcon != null) Object.Destroy(witnessIcon.gameObject); + var icon = playerState.transform.FindChild("WitnessIcon"); + if (icon != null) Object.Destroy(icon.gameObject); } } @@ -93,7 +95,7 @@ internal static void MeetingHudStartPostfix(MeetingHud __instance) foreach (var pva in __instance.playerStates) { var player = playerById(pva.TargetPlayerId); - if (player.IsAlive() && player != Witness.Player) + if (player.IsAlive()) { GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; GameObject targetBox = Object.Instantiate(template, pva.transform); @@ -122,11 +124,8 @@ internal static void TimeUpdatePostfix(MeetingHud __instance) { foreach (var playerState in __instance.playerStates) { - var witnessIcon = playerState.transform.FindChild("WitnessIcon"); - if (witnessIcon != null) - { - Object.Destroy(witnessIcon.gameObject); - } + var icon = playerState.transform.FindChild("WitnessIcon"); + if (icon != null) Object.Destroy(icon.gameObject); } endTime = true; } diff --git a/TheOtherRoles/Roles/RoleHelpers.cs b/TheOtherRoles/Roles/RoleHelpers.cs index 57368a7d..a58510ce 100644 --- a/TheOtherRoles/Roles/RoleHelpers.cs +++ b/TheOtherRoles/Roles/RoleHelpers.cs @@ -107,6 +107,7 @@ public static void ResetRoleSelection() { RoleId.Veteran, CustomOptionHolder.veteranSpawnRate.GetSelection() }, { RoleId.Vigilante, CustomOptionHolder.guesserSpawnRate.GetSelection() }, + { RoleId.WolfLord, CustomOptionHolder.wolfLordSpawnRate.GetSelection() }, { RoleId.Blackmailer, CustomOptionHolder.blackmailerSpawnRate.GetSelection() }, { RoleId.Bomber, CustomOptionHolder.bomberSpawnRate.GetSelection() }, { RoleId.BountyHunter, CustomOptionHolder.bountyHunterSpawnRate.GetSelection() }, @@ -265,6 +266,7 @@ public static void clearAndReloadRoles() PartTimer.clearAndReload(); Grenadier.clearAndReload(); Witness.ClearAndReload(); + WolfLord.ClearAndReload(); // Modifier Assassin.clearAndReload(); @@ -293,6 +295,7 @@ public static void clearAndReloadRoles() ButtonBarry.clearAndReload(); LastImpostor.clearAndReload(); Specoality.clearAndReload(); + Vortox.ClearAndReload(); GhostEngineer.ClearAndReload(); Specter.ClearAndReload(); diff --git a/TheOtherRoles/Roles/RoleInfo.cs b/TheOtherRoles/Roles/RoleInfo.cs index bd10352b..1dfbf290 100644 --- a/TheOtherRoles/Roles/RoleInfo.cs +++ b/TheOtherRoles/Roles/RoleInfo.cs @@ -22,6 +22,7 @@ public class RoleInfo(string name, Color color, RoleId roleId, RoleType roleType public static RoleInfo impostor = new("Impostor", Palette.ImpostorRed, RoleId.Impostor, RoleType.Impostor); public static RoleInfo morphling = new("Morphling", Morphling.color, RoleId.Morphling, RoleType.Impostor); + public static RoleInfo wolfLord = new("WolfLord", WolfLord.color, RoleId.WolfLord, RoleType.Impostor); public static RoleInfo bomber = new("Bomber", Bomber.color, RoleId.Bomber, RoleType.Impostor); public static RoleInfo poucher = new("Poucher", Poucher.color, RoleId.Poucher, RoleType.Impostor); public static RoleInfo butcher = new("Butcher", Eraser.color, RoleId.Butcher, RoleType.Impostor); @@ -98,7 +99,8 @@ public class RoleInfo(string name, Color color, RoleId roleId, RoleType roleType public static RoleInfo lover = new("Lover", Lovers.color, RoleId.Lover, RoleType.Modifier, true); public static RoleInfo disperser = new("Disperser", Disperser.color, RoleId.Disperser, RoleType.Modifier, true); public static RoleInfo specoality = new("Specoality", Specoality.color, RoleId.Specoality, RoleType.Modifier); - public static RoleInfo poucherModifier = new("Poucher", Poucher.color, RoleId.PoucherModifier, RoleType.Modifier); + public static RoleInfo vortox = new("Vortox", Vortox.color, RoleId.Vortox, RoleType.Modifier, true); + public static RoleInfo poucherModifier = new("Poucher", Poucher.color, RoleId.PoucherModifier, RoleType.Modifier, true); public static RoleInfo lastImpostor = new("LastImpostor", LastImpostor.color, RoleId.LastImpostor, RoleType.Modifier); public static RoleInfo bloody = new("Bloody", Color.yellow, RoleId.Bloody, RoleType.Modifier, true); public static RoleInfo antiTeleport = new("AntiTeleport", Color.yellow, RoleId.AntiTeleport, RoleType.Modifier); @@ -111,7 +113,7 @@ public class RoleInfo(string name, Color color, RoleId roleId, RoleType roleType public static RoleInfo multitasker = new("Multitasker", Color.yellow, RoleId.Multitasker, RoleType.Modifier, true); public static RoleInfo giant = new("Giant", Color.yellow, RoleId.Giant, RoleType.Modifier); public static RoleInfo mini = new("Mini", Color.yellow, RoleId.Mini, RoleType.Modifier); - public static RoleInfo vip = new("Vip", Color.yellow, RoleId.Vip, RoleType.Modifier, true); + public static RoleInfo vip = new("Vip", Color.yellow, RoleId.Vip, RoleType.Modifier); public static RoleInfo indomitable = new("Indomitable", Color.yellow, RoleId.Indomitable, RoleType.Modifier); public static RoleInfo slueth = new("Slueth", Color.yellow, RoleId.Slueth, RoleType.Modifier, true); public static RoleInfo cursed = new("Cursed", Color.yellow, RoleId.Cursed, RoleType.Modifier, true); @@ -130,6 +132,7 @@ public class RoleInfo(string name, Color color, RoleId roleId, RoleType roleType public static List allRoleInfos = [ impostor, + wolfLord, morphling, bomber, poucher, @@ -207,6 +210,7 @@ public class RoleInfo(string name, Color color, RoleId roleId, RoleType roleType poucherModifier, disperser, specoality, + vortox, lastImpostor, bloody, antiTeleport, @@ -274,6 +278,7 @@ public static List getRoleInfoForPlayer(PlayerControl p, bool showModi if (p == Specoality.specoality) infos.Add(specoality); if (p == Poucher.poucher && Poucher.spawnModifier) infos.Add(poucherModifier); if (p == Giant.giant) infos.Add(giant); + if (p == Vortox.Player) infos.Add(vortox); if (Invert.invert.Any(x => x.PlayerId == p.PlayerId)) infos.Add(invert); if (Chameleon.chameleon.Any(x => x.PlayerId == p.PlayerId)) infos.Add(chameleon); if (p == Shifter.shifter) infos.Add(shifter); @@ -290,6 +295,7 @@ public static List getRoleInfoForPlayer(PlayerControl p, bool showModi if (p == Miner.miner) infos.Add(miner); if (p == Poucher.poucher && !Poucher.spawnModifier) infos.Add(poucher); if (p == Butcher.butcher) infos.Add(butcher); + if (p == WolfLord.Player) infos.Add(wolfLord); if (p == Morphling.morphling) infos.Add(morphling); if (p == Bomber.bomber) infos.Add(bomber); if (p == Camouflager.camouflager) infos.Add(camouflager); @@ -397,11 +403,13 @@ public static string GetRolesString(PlayerControl p, bool useColors, bool showMo if (showGhostInfo && p != null) { - if (p == Shifter.shifter && - (CachedPlayer.LocalPlayer.PlayerControl == Shifter.shifter || shouldShowGhostInfo()) && Shifter.futureShift != null) + if (p == Shifter.shifter && (CachedPlayer.LocalPlayer.PlayerControl == Shifter.shifter || shouldShowGhostInfo()) && Shifter.futureShift != null) roleName += cs(Color.yellow, " ← " + Shifter.futureShift.Data.PlayerName); if (p == Vulture.vulture && (CachedPlayer.LocalPlayer.PlayerControl == Vulture.vulture || shouldShowGhostInfo())) roleName += cs(Vulture.color, string.Format("roleInfoRemaining".Translate(), Vulture.vultureNumberToWin - Vulture.eatenBodies)); + if (p == Witness.Player && (CachedPlayer.LocalPlayer.PlayerControl == Witness.Player || shouldShowGhostInfo())) + roleName += cs(Witness.color, string.Format("roleInfoRemaining".Translate(), Witness.exileToWin - Witness.exiledCount)); + if (shouldShowGhostInfo()) { if (Eraser.futureErased.Contains(p)) diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index dcb38c15..146e8cb6 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,7 +1,7 @@  net6.0 - 1.1.0.8 + 1.1.1.1 TheOtherUs mxyx-club latest diff --git a/TheOtherRoles/Utilities/MapData.cs b/TheOtherRoles/Utilities/MapData.cs index cb9096c0..c5da8b44 100644 --- a/TheOtherRoles/Utilities/MapData.cs +++ b/TheOtherRoles/Utilities/MapData.cs @@ -1,14 +1,13 @@ using System.Collections.Generic; using System.Linq; -using Reactor.Utilities.Extensions; using UnityEngine; namespace TheOtherRoles.Utilities; public class MapData { - public static List PositionCached = new(); + public static List VentCached = new(); public static readonly List SkeldSpawnPosition = [ @@ -217,12 +216,13 @@ public static List MapSpawnPosition() _ => FindVentSpawnPositions() }; PositionCached = pos; + Message("", "MapPos"); return pos; } public static List FindVentSpawnPositions() { - if (PositionCached?.Count > 1) return PositionCached; + if (VentCached?.Count > 1) return VentCached; var pos = new List(); foreach (var vent in DestroyableSingleton.Instance.AllVents) { @@ -231,7 +231,8 @@ public static List FindVentSpawnPositions() pos.Add(new Vector3(position.x, position.y + 0.3f, position.z = 0.0f)); } - PositionCached = pos; + VentCached = pos; + Message("", "VentPos"); return pos; } @@ -252,39 +253,9 @@ public static void RandomSpawnPlayers() Message($"Span to Vector3: {newPosition.x}, {newPosition.y}, {newPosition.z}"); } - public static void RandomSpawnAllPlayers() => RandomSpawnPlayers(PlayerControl.AllPlayerControls.ToArray()); - - public static void RandomSpawnAllPlayersToVent() => RandomSpawnToVent(PlayerControl.AllPlayerControls.ToArray().Where(n => n.IsAlive())); - - public static void RandomSpawnAllPlayersToMap() => RandomSpawnToMap(PlayerControl.AllPlayerControls.ToArray().Where(n => n.IsAlive())); - - public static void RandomSpawnPlayers(IEnumerable players) - { - if (CustomOptionHolder.randomGameStartToVents.GetBool()) RandomSpawnToVent(players); - else RandomSpawnToMap(players); - } - - public static void RandomSpawnToVent(IEnumerable spawnPlayer) + public static void Clear() { - var players = spawnPlayer.Where(p => !AntiTeleport.antiTeleport.Contains(p, player => player.PlayerId)); - - foreach (var p in players) - { - var poss = FindVentSpawnPositions().Random(); - p.NetTransform.RpcSnapTo(poss); - Message($"Spawn PLayer {p.Data.PlayerName} To {poss}"); - } - } - - public static void RandomSpawnToMap(IEnumerable spawnPlayer) - { - var players = spawnPlayer.Where(p => !AntiTeleport.antiTeleport.Contains(p, player => player.PlayerId)); - - foreach (var p in players) - { - var poss = MapSpawnPosition().Random(); - p.NetTransform.RpcSnapTo(poss); - Message($"Spawn PLayer {p.Data.PlayerName} To {poss}"); - } + VentCached.Clear(); + PositionCached.Clear(); } } \ No newline at end of file