diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index e3fa770cf..b5d6522de 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -33,6 +33,13 @@ internal static class CustomRolesHelper CustomRoles.Dasher ]; + public static readonly List ExperimentalRoleList = + [ + CustomRoles.Shifter + ]; + + public static bool IsExperimental(this CustomRoles role) => ExperimentalRoleList.Contains(role); + public static RoleBase GetRoleClass(this CustomRoles role) { var roleClass = role switch diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index eaaecfee4..52c09a4c3 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -24,9 +24,9 @@ static class ExtendedPlayerControl { public const MurderResultFlags ResultFlags = MurderResultFlags.Succeeded; - public static void RpcSetCustomRole(this PlayerControl player, CustomRoles role, bool isRoleForced = false, bool replaceAllAddons = false) + public static void RpcSetCustomRole(this PlayerControl player, CustomRoles role, bool replaceAllAddons = false) { - if (role < CustomRoles.NotAssigned || isRoleForced) + if (role < CustomRoles.NotAssigned) { Main.PlayerStates[player.PlayerId].SetMainRole(role); } diff --git a/Modules/GameState.cs b/Modules/GameState.cs index fbe595467..db73a5257 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -97,7 +97,7 @@ public class PlayerState(byte playerId) { CustomRoles.DarkHide => !DarkHide.SnatchesWin.GetBool() ? CountTypes.DarkHide : CountTypes.Crew, CustomRoles.Arsonist => Options.ArsonistKeepsGameGoing.GetBool() ? CountTypes.Arsonist : CountTypes.Crew, - _ => role.GetCountTypes(), + _ => role.GetCountTypes() }; Role = role.GetRoleClass(); @@ -111,6 +111,10 @@ public class PlayerState(byte playerId) Role.Add(PlayerId); + Logger.Info($"ID {PlayerId} => {role}", "SetMainRole"); + + if (!AmongUsClient.Instance.AmHost) return; + if (!Main.HasJustStarted) { var pc = Utils.GetPlayerById(PlayerId); diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index d29ca0975..7758dc439 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -303,8 +303,10 @@ public class StringOptionEnablePatch if (option.Id == Options.UsePets.Id) LoadLangs(); else if (!Options.UsePets.GetBool() && CustomRolesHelper.OnlySpawnsWithPetsRoleList.Any(role => role.ToString().Equals(option.GetName().RemoveHtmlTags().Replace(" ", string.Empty)))) __instance.TitleText.text += GetString("RequiresPetIndicator"); - else if (Options.UsePets.GetBool() && Enum.TryParse(option.GetName().RemoveHtmlTags().Replace(" ", string.Empty), true, out CustomRoles enumerable) && enumerable.PetActivatedAbility()) + else if (Options.UsePets.GetBool() && Enum.TryParse(option.GetName().RemoveHtmlTags().Replace(" ", string.Empty), true, out CustomRoles petRole) && petRole.PetActivatedAbility()) __instance.TitleText.text += GetString("SupportsPetIndicator"); + if (CustomRolesHelper.ExperimentalRoleList.Any(role => role.ToString().Equals(option.GetName().RemoveHtmlTags().Replace(" ", string.Empty)))) + __instance.TitleText.text += GetString("ExperimentalRoleIndicator"); __instance.Value = __instance.oldValue = option.CurrentValue; __instance.ValueText.text = option.GetString(); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 6ee88ea73..e9dff9f02 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -729,7 +729,7 @@ class RpcShapeshiftPatch class ReportDeadBodyPatch { public static Dictionary CanReport; - public static Dictionary> WaitReport = []; + public static readonly Dictionary> WaitReport = []; public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] GameData.PlayerInfo target) { diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 8dad9cc1d..cd69129a3 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1392,6 +1392,7 @@ "NotifyGodAlive": "Inform players at meetings that God is still alive", "TransporterTeleportMax": "Max number of teleports", "TriggerKill": "Kill", + "ExperimentalRoleIndicator": " EXPERIMENTAL", "RequiresPetIndicator": " <#ff0000>PET", "SupportsPetIndicator": " <#00ff00>✓PET", "SupportsPetMessage": "\\n\\n<#00ff00>[✓] This role supports Pet button usage for ability triggers or other actions, maybe replacing venting, shapeshifting or killing.", @@ -3437,7 +3438,13 @@ "Shifter": "Shifter", "ShifterInfo": "Steal Someone's Role", "ShifterInfoLong": "(Neutrals):\\nAs the Shifter, you must steal someone's role by using your pet button near them. Depending on the settings, you may also steal their progress in their role and ability uses, and their add-ons.\\nIf you have this role when the game ends, you lose!", - "Shifter.TrySwapBasis": "Swap Role Basis <#ffff00>(EXPERIMENTAL)", + "Tremor": "Tremor", + "TremorInfo": "When Doom Happens, Everyone is Doomed", + "TremorInfoLong": "(Neutral):\\nAs the Tremor, you see a timer below your name indicating when doom happens. When you kill, the timer decreases. If doom happens, you kill everyone that comes near you, and you have a specified amount of time to kill everyone, otherwise you suicide.", + "Tremor.TimerStart": "Time Until Doom Happens", + "Tremor.TimerDecrease": "When Killing, Timer Decreases by", + "Tremor.DoomTime": "Doom Time (= Time to Kill Everyone)", + "Tremor.DoomNotify": "Doom started! The Tremor now kills everyone that comes near them!", "Rogue": "Rogue", "RogueInfo": "Complete Objectives, Get Rewards", "RogueInfoLong": "(Neutral):\\nAs the Rogue, you get an objective with a reward each round. You can optionally complete the objective to get the reward.", diff --git a/Roles/Neutral/Shifter.cs b/Roles/Neutral/Shifter.cs index 255780564..a2ab9eae7 100644 --- a/Roles/Neutral/Shifter.cs +++ b/Roles/Neutral/Shifter.cs @@ -2,6 +2,7 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; +using HarmonyLib; namespace EHR.Neutral { @@ -16,10 +17,9 @@ namespace EHR.Neutral private static OptionItem KillCooldown; private static OptionItem CanVent; private static OptionItem HasImpostorVision; - private static OptionItem TrySwapBasis; public override bool IsEnable => On; - public static bool ForceDisableTasks(int id) => !TrySwapBasis.GetBool() && WasShifter.Contains(id) && AllPlayerBasis.TryGetValue(id, out var basis) && basis is RoleTypes.Impostor or RoleTypes.Shapeshifter; + public static bool ForceDisableTasks(int id) => WasShifter.Contains(id) && AllPlayerBasis.TryGetValue(id, out var basis) && basis is RoleTypes.Impostor or RoleTypes.Shapeshifter; public static void SetupCustomOption() { @@ -31,8 +31,6 @@ namespace EHR.Neutral .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Shifter]); HasImpostorVision = BooleanOptionItem.Create(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Shifter]); - TrySwapBasis = BooleanOptionItem.Create(Id + 5, "Shifter.TrySwapBasis", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Shifter]); } public override void Init() @@ -65,12 +63,43 @@ namespace EHR.Neutral var clientId = killer.GetClientId(); if (clientId != -1) WasShifter.Add(clientId); - if (TrySwapBasis.GetBool()) killer.ChangeRoleBasis(AllPlayerBasis[target.GetClientId()]); + killer.RpcSetCustomRole(target.GetCustomRole()); - (killer.PlayerId, target.PlayerId) = (target.PlayerId, killer.PlayerId); + Main.PlayerStates[killer.PlayerId].Role = Main.PlayerStates[target.PlayerId].Role; + killer.SetAbilityUseLimit(target.GetAbilityUseLimit()); - Utils.SyncAllSettings(); - Utils.NotifyRoles(NoCache: true); + var taskState = target.GetTaskState(); + if (taskState.hasTasks) Main.PlayerStates[killer.PlayerId].taskState = taskState; + + var killerSubRoles = killer.GetCustomSubRoles(); + var targetSubRoles = target.GetCustomSubRoles(); + if (targetSubRoles.Count > 0) + { + killer.RpcSetCustomRole(targetSubRoles[0], replaceAllAddons: true); + targetSubRoles.Skip(1).Do(x => killer.RpcSetCustomRole(x)); + } + + if (killerSubRoles.Count > 0) + { + target.RpcSetCustomRole(killerSubRoles[0], replaceAllAddons: true); + killerSubRoles.Skip(1).Do(x => target.RpcSetCustomRole(x)); + } + + Main.AbilityCD.Remove(killer.PlayerId); + killer.SyncSettings(); + + // ------------------------------------------------------------------------------------------ + + target.RpcSetCustomRole(CustomRoles.Shifter); + Main.AbilityUseLimit.Remove(target.PlayerId); + Utils.SendRPC(CustomRPC.RemoveAbilityUseLimit, target.PlayerId); + target.SyncSettings(); + target.SetKillCooldown(); + + // ------------------------------------------------------------------------------------------ + + Utils.NotifyRoles(SpecifyTarget: killer); + Utils.NotifyRoles(SpecifyTarget: target); return false; } diff --git a/Roles/Neutral/Tremor.cs b/Roles/Neutral/Tremor.cs index 4df398d8e..3c62f183f 100644 --- a/Roles/Neutral/Tremor.cs +++ b/Roles/Neutral/Tremor.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using AmongUs.GameOptions; using EHR.Modules; @@ -37,7 +38,7 @@ public class Tremor : RoleBase .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]); HasImpostorVision = BooleanOptionItem.Create(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]); - TimerStart = IntegerOptionItem.Create(Id + 5, "Tremor.TimerStart", new(0, 600, 1), 180, TabGroup.NeutralRoles) + TimerStart = IntegerOptionItem.Create(Id + 5, "Tremor.TimerStart", new(0, 600, 5), 180, TabGroup.NeutralRoles) .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) .SetValueFormat(OptionFormat.Seconds); TimerDecrease = IntegerOptionItem.Create(Id + 6, "Tremor.TimerDecrease", new(0, 180, 1), 15, TabGroup.NeutralRoles) @@ -56,7 +57,7 @@ public class Tremor : RoleBase public override void Add(byte playerId) { On = true; - Timer = TimerStart.GetInt(); + Timer = TimerStart.GetInt() + 8; DoomTimer = 0; if (!AmongUsClient.Instance.AmHost) return; @@ -82,15 +83,19 @@ public class Tremor : RoleBase } if (wasDoom != IsDoom) + { Main.AllAlivePlayerControls.Do(x => x.Notify(Translator.GetString("Tremor.DoomNotify"))); + DoomTimer = DoomTime.GetInt(); + } if (IsDoom) { Count++; - if (Count < 5) return; - Count = 0; - pc.RpcGuardAndKill(); + if (Count % 3 == 0) pc.RpcGuardAndKill(); + + if (Count < 15) return; + Count = 0; var pos = pc.Pos(); Main.AllAlivePlayerControls @@ -109,7 +114,7 @@ public class Tremor : RoleBase public override void OnMurder(PlayerControl killer, PlayerControl target) { - Timer -= TimerDecrease.GetInt(); + Timer = Math.Max(Timer - TimerDecrease.GetInt(), 0); Utils.SendRPC(CustomRPC.SyncTremor, killer.PlayerId, Timer); }