From e4d5a77c2a44d610e93a89dd2f04e0340d4df688 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Wed, 14 Aug 2024 21:07:15 +0200 Subject: [PATCH] Add Assumer, add Deadlined (add-on) --- CTA/CustomTeamAssigner/CustomRoles.cs | 1 + CustomRoles.cs | 2 + EHR.sln.DotSettings | 1 + Modules/AUnlocker.cs | 50 ++++++++++++++++++ Modules/CustomRolesHelper.cs | 3 ++ Modules/GameState.cs | 13 ++--- Modules/Utils.cs | 2 + Patches/ChatCommandPatch.cs | 7 +++ Patches/ChatControlPatch.cs | 2 +- Patches/HudPatch.cs | 1 + Patches/MeetingHudPatch.cs | 1 + Patches/PlayerControlPatch.cs | 4 ++ README.md | 9 ++++ Resources/Lang/en_US.json | 15 ++++++ Roles/AddOns/Common/Deadlined.cs | 59 +++++++++++++++++++++ Roles/Crewmate/Whisperer.cs | 34 ++++++------ Roles/Impostor/Assumer.cs | 74 +++++++++++++++++++++++++++ Roles/RoleBase.cs | 60 ++++++++++++++++++++++ main.cs | 1 + 19 files changed, 312 insertions(+), 27 deletions(-) create mode 100644 Modules/AUnlocker.cs create mode 100644 Roles/AddOns/Common/Deadlined.cs create mode 100644 Roles/Impostor/Assumer.cs diff --git a/CTA/CustomTeamAssigner/CustomRoles.cs b/CTA/CustomTeamAssigner/CustomRoles.cs index 36c8a1b29..c22b5f2d7 100644 --- a/CTA/CustomTeamAssigner/CustomRoles.cs +++ b/CTA/CustomTeamAssigner/CustomRoles.cs @@ -21,6 +21,7 @@ public enum CustomRoles Hacker, // Anonymous AntiAdminer, Sans, // Arrogance + Assumer, Augmenter, Bard, Blackmailer, diff --git a/CustomRoles.cs b/CustomRoles.cs index 36c8a1b29..656216175 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -21,6 +21,7 @@ public enum CustomRoles Hacker, // Anonymous AntiAdminer, Sans, // Arrogance + Assumer, Augmenter, Bard, Blackmailer, @@ -395,6 +396,7 @@ public enum CustomRoles Clumsy, Contagious, Damocles, + Deadlined, DeadlyQuota, Disco, Diseased, diff --git a/EHR.sln.DotSettings b/EHR.sln.DotSettings index 546c183d8..5f4a04ab0 100644 --- a/EHR.sln.DotSettings +++ b/EHR.sln.DotSettings @@ -36,6 +36,7 @@ TMP EHR TP + TS UI VN CustomRoles.Crewmate, CustomRoles.Inhibitor => CustomRoles.Impostor, CustomRoles.Kidnapper => CustomRoles.Shapeshifter, + CustomRoles.Assumer => CustomRoles.Impostor, CustomRoles.Augmenter => CustomRoles.Shapeshifter, CustomRoles.Ventriloquist => CustomRoles.Impostor, CustomRoles.Echo => CustomRoles.Shapeshifter, @@ -573,6 +574,7 @@ internal static class CustomRolesHelper CustomRoles.Augmenter or CustomRoles.Inhibitor or CustomRoles.Kidnapper or + CustomRoles.Assumer or CustomRoles.Generator or CustomRoles.Blackmailer or CustomRoles.Commander or @@ -1172,6 +1174,7 @@ internal static class CustomRolesHelper { CustomRoles.Sans => RoleOptionType.Impostor_Killing, CustomRoles.Assassin => RoleOptionType.Impostor_Killing, + CustomRoles.Assumer => RoleOptionType.Impostor_Killing, CustomRoles.Augmenter => RoleOptionType.Impostor_Killing, CustomRoles.Bomber => RoleOptionType.Impostor_Killing, CustomRoles.BountyHunter => RoleOptionType.Impostor_Killing, diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 0452e8488..4b6c37239 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using EHR.AddOns.Common; using EHR.AddOns.Crewmate; using EHR.AddOns.GhostRoles; using EHR.Crewmate; @@ -54,6 +55,7 @@ public class PlayerState(byte playerId) Consumed, BadLuck, Asthma, + Assumed, etc = -1 } @@ -334,15 +336,6 @@ public class TaskState SpeedrunManager.ResetTimer(player); } - if (player.Is(CustomRoles.Unlucky) && alive) - { - var Ue = IRandom.Instance; - if (Ue.Next(0, 100) < Options.UnluckyTaskSuicideChance.GetInt()) - { - player.Suicide(); - } - } - if (alive && Mastermind.ManipulatedPlayers.ContainsKey(player.PlayerId)) { Mastermind.OnManipulatedPlayerTaskComplete(player); @@ -380,7 +373,9 @@ public class TaskState var addons = Main.PlayerStates[player.PlayerId].SubRoles; + if (addons.Contains(CustomRoles.Deadlined)) Deadlined.SetDone(player); if (addons.Contains(CustomRoles.Stressed)) Stressed.OnTaskComplete(player); + if (addons.Contains(CustomRoles.Unlucky) && alive && IRandom.Instance.Next(0, 100) < Options.UnluckyTaskSuicideChance.GetInt()) player.Suicide(); if (GhostRolesManager.AssignedGhostRoles.TryGetValue(player.PlayerId, out var ghostRole)) { if (ghostRole is { Role: CustomRoles.Specter, Instance: Specter specter } && (CompletedTasksCount + 1 >= AllTasksCount)) specter.OnFinishedTasks(player); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index ca699725e..2e2dbac81 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1888,6 +1888,7 @@ public static class Utils if (seer.Is(CustomRoles.Asthmatic)) SelfSuffix.Append(Asthmatic.GetSuffixText(seer.PlayerId)); if (seer.Is(CustomRoles.Sonar)) SelfSuffix.Append(Sonar.GetSuffix(seer, isForMeeting)); + if (seer.Is(CustomRoles.Deadlined)) SelfSuffix.Append(Deadlined.GetSuffix(seer)); SelfSuffix.Append(Bloodmoon.GetSuffix(seer)); SelfSuffix.Append(Haunter.GetSuffix(seer)); @@ -2582,6 +2583,7 @@ public static class Utils Damocles.AfterMeetingTasks(); Stressed.AfterMeetingTasks(); Circumvent.AfterMeetingTasks(); + Deadlined.AfterMeetingTasks(); if (Options.AirshipVariableElectrical.GetBool()) AirshipElectricalDoors.Initialize(); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 043f39920..091e31d41 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -156,6 +156,7 @@ internal static class ChatCommands new(["decree", "постановление"], "{number}", GetString("CommandDescription.Decree"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DecreeCommand, true, [GetString("CommandArgs.Decree.Number")]), new(["addvip", "добавитьвип"], "{id}", GetString("CommandDescription.AddVIP"), Command.UsageLevels.Host, Command.UsageTimes.Always, AddVIPCommand, true, [GetString("CommandArgs.AddVIP.Id")]), new(["deletevip", "удалитьвип"], "{id}", GetString("CommandDescription.DeleteVIP"), Command.UsageLevels.Host, Command.UsageTimes.Always, DeleteVIPCommand, true, [GetString("CommandArgs.DeleteVIP.Id")]), + new(["assume", "предположить"], "{id} {number}", GetString("CommandDescription.Assume"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AssumeCommand, true, [GetString("CommandArgs.Assume.Id"), GetString("CommandArgs.Assume.Number")]), // Commands with action handled elsewhere new(["shoot", "guess", "bet", "bt", "st", "угадать", "бт"], "{id} {role}", GetString("CommandDescription.Guess"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Guess.Id"), GetString("CommandArgs.Guess.Role")]), @@ -282,6 +283,12 @@ internal static class ChatCommands // --------------------------------------------------------------------------------------------------------------------------------------------- + private static void AssumeCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 3 || !byte.TryParse(args[1], out var id) || !int.TryParse(args[2], out var num) || !player.Is(CustomRoles.Assumer)) return; + Assumer.Assume(player.PlayerId, id, num); + } + private static void DeleteVIPCommand(ChatController __instance, PlayerControl player, string text, string[] args) { if (args.Length < 2 || !byte.TryParse(args[1], out var VIPId)) return; diff --git a/Patches/ChatControlPatch.cs b/Patches/ChatControlPatch.cs index 287cd847a..19741a255 100644 --- a/Patches/ChatControlPatch.cs +++ b/Patches/ChatControlPatch.cs @@ -163,7 +163,7 @@ public static class ChatManager int operate = message switch { { } str when CheckCommand(ref str, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id|shoot|guess|bet|st|gs|bt|猜|赌|sp|jj|tl|trial|审判|判|审|xp|效颦|效|颦|sw|换票|换|swap", false) || CheckName(ref playername, "系统消息", false) => 1, - { } str when CheckCommand(ref str, "up|ask|target|vote|chat|check|decree", false) => 2, + { } str when CheckCommand(ref str, "up|ask|target|vote|chat|check|decree|assume", false) => 2, { } str when CheckCommand(ref str, "r|role|m|myrole|n|now") => 4, _ => 3 }; diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 71978b323..a302b1369 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -240,6 +240,7 @@ class HudManagerPatch { CustomRoles.Asthmatic => Asthmatic.GetSuffixText(player.PlayerId), CustomRoles.Spurt => Spurt.GetSuffix(player, true), + CustomRoles.Deadlined => Deadlined.GetSuffix(player, true), _ => string.Empty }); return string.Join(string.Empty, suffixes); diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 3f8d5db6f..b8d3ed337 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -199,6 +199,7 @@ class CheckForEndVotingPatch states = [.. statesList]; var VotingData = __instance.CustomCalculateVotes(); + Assumer.OnVotingEnd(VotingData); byte exileId = byte.MaxValue; int max = 0; voteLog.Info("===Decision to expel player processing begins==="); diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 53af4dfd0..5332538c4 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -646,6 +646,8 @@ class MurderPlayerPatch if (killer.Is(CustomRoles.Pickpocket) && killer.PlayerId != target.PlayerId) killer.Notify(string.Format(GetString("PickpocketGetVote"), ((Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == killer.PlayerId) + 1) * Pickpocket.VotesPerKill.GetFloat()).ToString("0.0#####"))); + if (killer.Is(CustomRoles.Deadlined)) Deadlined.SetDone(killer); + if (target.Is(CustomRoles.Avanger)) { var pcList = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId).ToArray(); @@ -1054,6 +1056,7 @@ class ReportDeadBodyPatch Spiritualist.OnReportDeadBody(target); Bloodmoon.OnMeetingStart(); + Deadlined.OnMeetingStart(); Main.LastVotedPlayerInfo = null; Witness.AllKillers.Clear(); @@ -1667,6 +1670,7 @@ static class FixedUpdatePatch Suffix.Append(Haunter.GetSuffix(seer)); if (seer.Is(CustomRoles.Asthmatic)) Suffix.Append(Asthmatic.GetSuffixText(seer.PlayerId)); if (seer.Is(CustomRoles.Sonar)) Suffix.Append(Sonar.GetSuffix(seer, GameStates.IsMeeting)); + if (seer.Is(CustomRoles.Deadlined)) Suffix.Append(Deadlined.GetSuffix(seer)); } switch (Options.CurrentGameMode) diff --git a/README.md b/README.md index d20a0bd8b..e39a8a8ea 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,15 @@ > > This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or > otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are the property of Innersloth LLC. +>
+>
+> This mod does not unlock all cosmetics permanently, so it does not add them to your account. This is because your +> progress is stored on the Innersloth servers and we cannot just modify it. However, this mod uses BepInEx and the +> Reactor Framework to Postfix Among Us game methods and unlock all cosmetics temporarily. If you uninstall this mod, +> the +> cosmetics will be locked again. The cosmetics you have already unlocked legitimately, e.g., through buying a cosmicube +> are untouched by this mod. This mod is not a cheat, it is a mod that adds new roles and features to the game. If you +> have any questions, feel free to ask in our Discord server.

diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 9b8ca3765..400f4f4cc 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -1787,6 +1787,7 @@ "DeathReason.Consumed": "Consumed", "DeathReason.BadLuck": "Bad Luck", "DeathReason.Asthma": "Asthma", + "DeathReason.Assumed": "Assumed", "Alive": "Alive", "Win": " Wins!", "Last-": "Last ", @@ -2209,6 +2210,7 @@ "CommandDescription.Decree": "Use a decree as the President", "CommandDescription.AddVIP": "Add a player to the VIP list", "CommandDescription.DeleteVIP": "Remove a player from the VIP list", + "CommandDescription.Assume": "Assume how many votes a player will receive as the Assumer", "CommandArgs.ChangeSetting.Name": "The setting's name to change (leave blank to view all settings)", "CommandArgs.ChangeSetting.UnknownValue": "(leave blank to view what to put here)", "CommandArgs.Rename.Name": "Your new name", @@ -2264,6 +2266,8 @@ "CommandArgs.Decree.Number": "0/1/2/3/4/5 (leave blank to view all decrees)", "CommandArgs.AddVIP.Id": "The player's ID to add to the VIP list", "CommandArgs.DeleteVIP.Id": "The player's ID to remove from the VIP list", + "CommandArgs.Assume.Id": "The player's ID to assume votes for", + "CommandArgs.Assume.Number": "The number of votes to assume", "Commands.ChangeSettingHelp": "Arguments:\\n- map (theskeld/mirahq/polus/dlekseht/airship/thefungle/custom)\\n- impostors (number)\\n- players (number)\\n- recommended (on/off)\\n- confirmejects (on/off)\\n- emergencymeetings (number)\\n- anonymousvotes (on/off)\\n- emergencycooldown (number)\\n- discussiontime (number)\\n- votingtime (number)\\n- playerspeed (float)\\n- crewmatevision (float)\\n- impostorvision (float)\\n- killcooldown (float)\\n- killdistance (short/medium/long/custom)\\n- taskbarupdates (always/meetings/never)\\n- visualtasks (on/off)\\n- commontasks (number)\\n- longtasks (number)\\n- shorttasks (number)\\n- scientistcount (number)\\n- scientistchance (number)\\n- vitalsdisplaycooldown (float)\\n- batteryduration (float)\\n- engineercount (number)\\n- engineerchance (number)\\n- ventusecooldown (float)\\n- maxtimeinvents (float)\\n- guardianangelcount (number)\\n- guardianangelchance (number)\\n- protectcooldown (float)\\n- protectduration (float)\\n- protectvisibletoimpostors (on/off)\\n- shapeshiftercount (number)\\n- shapeshifterchance (number)\\n- shapeshiftduration (float)\\n- shapeshiftcooldown (float)\\n- leaveshapeshiftevidence (on/off)\\n- phantomcount (number)\\n- phantomchance (number)\\n- inviscooldown (float)\\n- invisduration (float)\\n- noisemakercount (number)\\n- noisemakerchance (number)\\n- noisemakerimpostoralert (on/off)\\n- noisemakeralertduration (number)\\n- trackercount (number)\\n- trackerchance (number)\\n- trackduration (float)\\n- trackcooldown (float)\\n- trackdelay (float)\\n- ghostdotasks (on/off)\\n\\nNotes:\\n1. Upper and lowercase matters\\n2. Where it says custom, you obviously put a number argument after it\\n3. A float is a single precision floating number, like 1.7\\n4. \"ghostdotasks\" causes issues if you set it\\n5. Example uses:\\n - \"/cs map polus\"\\n - \"/cs killdistance custom 4\"\\n - \"/cs trackdelay 1,7\"\\n - \"/cs confirmejects on\"", "PollResultTitle": "Poll Results", "Poll.Title": "<#ff0000>➡ POLL ⬅", @@ -3332,6 +3336,17 @@ "TornadoDuration": "Tornado Duration", "TornadoRange": "Tornado Range", "TeleportedByTornado": "\\nOh no! You just got caught up in a tornado,\\nand you flew to over here!", + "Deadlined": "Deadlined", + "DeadlinedInfo": "Must do a task or kill each round", + "DeadlinedInfoLong": "(Add-ons):\\nAs the Deadlined, you must complete a task or kill someone each round. If you fail to do so, you will die. If a meeting gets called too soon after the previous one ends (time set by the host), you won't die.", + "Prefix.Deadlined": "Deadlined", + "Deadlined.InactiveTime": "Safe Time After Meetings", + "Assumer": "Assumer", + "AssumerInfo": "Assume Vote Counts", + "AssumerInfoLong": "(Impostors):\\nAs the Assumer, you can type /assume {id} {number} to assume that the selected player (whose ID you typed as the first argument, replacing '{id}') will get exactly {number} votes (the number you typed in the second argument, replacing '{number}').\\nExample: /assume 0 3 (assume that the host gets 3 votes).\\n\\nIf you are correct, every player that voted for your target dies.", + "Assumer.AssumedMessage": "You're assuming that {0} will receive {1} votes", + "Assumer.VoteReceiverDies": "Vote Receiver Dies on Correct Assumption", + "Assumer.MinPlayersToAssume": "Minimum Alive Players to Make Assumptions", "Sentinel": "Sentinel", "SentinelInfo": "Scare Killers Away", "SentinelInfoLong": "(Crewmates):\\nAs the Sentinel, you may vent to start patrolling your surrounding area. When you do, all killers within this area are notified to dip. If any killer attempts to kill someone within this area while you're patrolling, you'll kill them instead. When patrolling is over, all killers in the patrolling area suicide. While patrolling, you have lower vision.", diff --git a/Roles/AddOns/Common/Deadlined.cs b/Roles/AddOns/Common/Deadlined.cs new file mode 100644 index 000000000..c03a8c6fc --- /dev/null +++ b/Roles/AddOns/Common/Deadlined.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +namespace EHR.AddOns.Common +{ + public class Deadlined : IAddon + { + private static HashSet DidTask = []; + private static long MeetingEndTS; + private static OptionItem InactiveTime; + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(649293, CustomRoles.Deadlined, canSetNum: true, teamSpawnOptions: true); + InactiveTime = new IntegerOptionItem(649299, "Deadlined.InactiveTime", new(0, 60, 1), 15, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Deadlined]) + .SetValueFormat(OptionFormat.Seconds); + } + + public static void SetDone(PlayerControl pc) + { + DidTask.Add(pc.PlayerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public static void AfterMeetingTasks() + { + DidTask = []; + MeetingEndTS = Utils.TimeStamp; + + foreach (var pc in Main.AllPlayerControls) + { + TaskState ts = pc.GetTaskState(); + if (pc.Is(CustomRoles.Deadlined) && (!pc.IsAlive() || ts.IsTaskFinished || !ts.hasTasks)) + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Deadlined); + } + } + + public static void OnMeetingStart() + { + if (MeetingEndTS + InactiveTime.GetInt() > Utils.TimeStamp) return; + foreach (var pc in Main.AllAlivePlayerControls) + { + if (!DidTask.Contains(pc.PlayerId)) + pc.Suicide(); + } + } + + public static string GetSuffix(PlayerControl seer, bool hud = false) + { + if (!seer.Is(CustomRoles.Deadlined) || (seer.IsModClient() && !hud)) return string.Empty; + if (DidTask.Contains(seer.PlayerId)) return "<#00ff00>\u2713"; + long now = Utils.TimeStamp; + return MeetingEndTS + InactiveTime.GetInt() <= now + ? Translator.GetString("Deadlined.MustDoTask") + : string.Format(Translator.GetString("Deadlined.SafeTime"), InactiveTime.GetInt() - (now - MeetingEndTS)); + } + } +} \ No newline at end of file diff --git a/Roles/Crewmate/Whisperer.cs b/Roles/Crewmate/Whisperer.cs index 71fad0020..b66803dcd 100644 --- a/Roles/Crewmate/Whisperer.cs +++ b/Roles/Crewmate/Whisperer.cs @@ -29,23 +29,23 @@ namespace EHR.Crewmate public override void SetupCustomOption() { - // int id = 649550; - // Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Whisperer); - // Cooldown = new IntegerOptionItem(++id, "WhispererCooldown", new(0, 60, 1), 5, TabGroup.CrewmateRoles) - // .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - // .SetValueFormat(OptionFormat.Seconds); - // Duration = new IntegerOptionItem(++id, "WhispererDuration", new(0, 60, 1), 7, TabGroup.CrewmateRoles) - // .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - // .SetValueFormat(OptionFormat.Seconds); - // AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - // .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - // .SetValueFormat(OptionFormat.Times); - // WhispererAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - // .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - // .SetValueFormat(OptionFormat.Times); - // AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - // .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - // .SetValueFormat(OptionFormat.Times); + int id = 649550; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Whisperer); + Cooldown = new IntegerOptionItem(++id, "WhispererCooldown", new(0, 60, 1), 5, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Seconds); + Duration = new IntegerOptionItem(++id, "WhispererDuration", new(0, 60, 1), 7, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Seconds); + AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Times); + WhispererAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Times); + AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Times); } public override void Init() diff --git a/Roles/Impostor/Assumer.cs b/Roles/Impostor/Assumer.cs new file mode 100644 index 000000000..9ef5da5ca --- /dev/null +++ b/Roles/Impostor/Assumer.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using EHR.Patches; + +namespace EHR.Impostor +{ + public class Assumer : RoleBase + { + public static bool On; + private static List Instances = []; + + private static OptionItem VoteReceiverDies; + private static OptionItem MinPlayersToAssume; + private static OptionItem CanKill; + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private byte AssumerId; + + private (byte Id, int VoteNum) Assumption; + private bool HasAssumed => Assumption.Id != byte.MaxValue; + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + StartSetup(649250, TabGroup.ImpostorRoles, CustomRoles.Assumer) + .AutoSetupOption(ref VoteReceiverDies, false) + .AutoSetupOption(ref MinPlayersToAssume, 6, new IntegerValueRule(1, 15, 1), OptionFormat.Players) + .AutoSetupOption(ref CanKill, true, overrideName: "CanKill") + .AutoSetupOption(ref KillCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideName: "KillCooldown", overrideParent: CanKill) + .AutoSetupOption(ref CanVent, false, overrideName: "CanVent"); + } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + AssumerId = playerId; + Assumption = (byte.MaxValue, 0); + } + + public override bool CanUseKillButton(PlayerControl pc) => CanKill.GetBool(); + public override void SetKillCooldown(byte id) => Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + public override bool CanUseImpostorVentButton(PlayerControl pc) => CanVent.GetBool(); + + public static void OnVotingEnd(Dictionary voteNum) + { + foreach (Assumer instance in Instances) + { + if (instance.HasAssumed && voteNum.TryGetValue(instance.Assumption.Id, out var num) && num == instance.Assumption.VoteNum) + { + foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) + { + if (pva.VotedFor == instance.Assumption.Id || (VoteReceiverDies.GetBool() && pva.TargetPlayerId == instance.Assumption.Id)) + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Assumed, pva.TargetPlayerId); + } + } + } + } + + public static void Assume(byte assumerId, byte id, int num) + { + if (Main.AllAlivePlayerControls.Length < MinPlayersToAssume.GetInt()) return; + var assumer = Instances.Find(x => x.AssumerId == assumerId); + if (assumer == null || assumer.HasAssumed) return; + assumer.Assumption = (id, num); + Utils.SendMessage("\n", assumerId, string.Format(Translator.GetString("Assumer.AssumedMessage"), id.ColoredPlayerName(), num)); + } + } +} \ No newline at end of file diff --git a/Roles/RoleBase.cs b/Roles/RoleBase.cs index 2004dde71..9b1f14dbd 100644 --- a/Roles/RoleBase.cs +++ b/Roles/RoleBase.cs @@ -1,5 +1,7 @@ global using Object = UnityEngine.Object; using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; using AmongUs.GameOptions; using EHR.AddOns.Crewmate; @@ -188,5 +190,63 @@ namespace EHR var seerRole = seer.GetCustomRole(); return seerRole.IsNK() && seerRole == target.GetCustomRole() && seer.GetTeam() == target.GetTeam(); } + + // Option setup simplifier + protected static OptionSetupHandler StartSetup(int id, TabGroup tab, CustomRoles role) + { + Options.SetupRoleOptions(id++, tab, role); + return new(++id, tab, role); + } + } + + public class OptionSetupHandler(int id, TabGroup tab, CustomRoles role) + { + private readonly OptionItem Parent = Options.CustomRoleSpawnChances[role]; + + public OptionSetupHandler AutoSetupOption(ref OptionItem field, object defaultValue, object valueRule = null, OptionFormat format = OptionFormat.None, [CallerArgumentExpression("field")] string fieldName = "", string overrideName = "", OptionItem overrideParent = null) + { + try + { + var name = overrideName == "" ? $"{role}.{fieldName}" : overrideName; + switch (valueRule, defaultValue) + { + case (null, bool bdv): + field = new BooleanOptionItem(++id, name, bdv, tab); + field.SetParent(overrideParent ?? Parent); + break; + case (IntegerValueRule ivr, int idv): + field = new IntegerOptionItem(++id, name, ivr, idv, tab); + field.SetParent(overrideParent ?? Parent); + break; + case (FloatValueRule fvr, float fdv): + field = new FloatOptionItem(++id, name, fvr, fdv, tab); + field.SetParent(overrideParent ?? Parent); + break; + case (IList selections, int index): + field = new StringOptionItem(++id, name, selections, index, tab); + field.SetParent(overrideParent ?? Parent); + break; + default: + throw new ArgumentException("The valueRule and defaultValue combination is not supported."); + } + + if (format != OptionFormat.None) field?.SetValueFormat(format); + } + catch (Exception e) + { + Logger.Error($"Failed to setup option {fieldName} for {role}", "OptionSetupHandler"); + Utils.ThrowException(e); + } + + return this; + } + } + + public enum OptionType + { + Boolean, + Int, + Float, + String } } \ No newline at end of file diff --git a/main.cs b/main.cs index 2f9164983..6cb1467f9 100644 --- a/main.cs +++ b/main.cs @@ -547,6 +547,7 @@ public class Main : BasePlugin { CustomRoles.Messenger, "#28b573" }, { CustomRoles.Dynamo, "#ebe534" }, { CustomRoles.AntiTP, "#fcba03" }, + { CustomRoles.Deadlined, "#ffa500" }, { CustomRoles.Rookie, "#bf671f" }, { CustomRoles.Taskcounter, "#ff1919" }, { CustomRoles.Stained, "#e6bf91" },