diff --git a/CustomRoles.cs b/CustomRoles.cs index 4da8df9fe..e25073f26 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -55,6 +55,7 @@ public enum CustomRoles Eraser, EvilGuesser, EvilTracker, + Fabricator, FireWorks, Forger, Framer, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index a9bd05212..88980794c 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -160,6 +160,7 @@ internal static class CustomRolesHelper CustomRoles.Kidnapper => CustomRoles.Shapeshifter, CustomRoles.Ambusher => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, CustomRoles.Stasis => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Fabricator => CustomRoles.Impostor, CustomRoles.Wasp => CustomRoles.Impostor, CustomRoles.Assumer => CustomRoles.Impostor, CustomRoles.Augmenter => CustomRoles.Shapeshifter, @@ -729,6 +730,7 @@ internal static class CustomRolesHelper CustomRoles.Psychopath or CustomRoles.Ambusher or CustomRoles.Kidnapper or + CustomRoles.Fabricator or CustomRoles.Centralizer or CustomRoles.Postponer or CustomRoles.Venerer or @@ -1677,6 +1679,7 @@ internal static class CustomRolesHelper CustomRoles.Duellist => RoleOptionType.Impostor_Concealing, CustomRoles.Echo => RoleOptionType.Impostor_Concealing, CustomRoles.Escapee => RoleOptionType.Impostor_Concealing, + CustomRoles.Fabricator => RoleOptionType.Impostor_Concealing, CustomRoles.Forger => RoleOptionType.Impostor_Concealing, CustomRoles.Hangman => RoleOptionType.Impostor_Concealing, CustomRoles.Kidnapper => RoleOptionType.Impostor_Concealing, diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 7f869466f..622510160 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -1474,8 +1474,9 @@ internal static class RPC { byte playerId = reader.ReadByte(); var deathReason = (PlayerState.DeathReason)reader.ReadInt32(); - Main.PlayerStates[playerId].deathReason = deathReason; - Main.PlayerStates[playerId].IsDead = true; + PlayerState state = Main.PlayerStates[playerId]; + state.deathReason = deathReason; + state.IsDead = true; } public static void ForceEndGame(CustomWinner win) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index dfbd33fb1..23611a411 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -225,6 +225,7 @@ internal static class ChatCommands new(["revive", "воскрешение", "воскрешать", "复活", "reviver"], "{id}", GetString("CommandDescription.Revive"), Command.UsageLevels.Host, Command.UsageTimes.InGame, ReviveCommand, true, false, [GetString("CommandArgs.Revive.Id")]), new(["select", "выбратьигрока", "选择玩家", "selecionar"], "{id} {role}", GetString("CommandDescription.Select"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, SelectCommand, true, true, [GetString("CommandArgs.Select.Id"), GetString("CommandArgs.Select.Role")]), new(["uiscale", "масштаб"], "{scale}", GetString("CommandDescription.UIScale"), Command.UsageLevels.Modded, Command.UsageTimes.Always, UIScaleCommand, true, false, [GetString("CommandArgs.UIScale.Scale")]), + new(["fabricate", "фабриковать", "伪造", "fabricar"], "{deathreason}", GetString("CommandDescription.Fabricate"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, FabricateCommand, true, true, [GetString("CommandArgs.Fabricate.DeathReason")]), new(["confirmauth"], "{uuid}", GetString("CommandDescription.ConfirmAuth"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, ConfirmAuthCommand, true, false, [GetString("CommandArgs.ConfirmAuth.UUID")]), @@ -488,7 +489,32 @@ internal static class ChatCommands // --------------------------------------------------------------------------------------------------------------------------------------------- - public static void UIScaleCommand(PlayerControl player, string text, string[] args) + private static void FabricateCommand(PlayerControl player, string text, string[] args) + { + if (Starspawn.IsDayBreak) return; + + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(SelectCommand), text); + return; + } + + if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) || state.IsDead || state.Role is not Fabricator fab) return; + + if (args.Length < 2 || !Enum.GetValues().FindFirst(x => GetString(x.ToString()).Replace(" ", string.Empty).Equals(args[1].Replace(" ", string.Empty), StringComparison.OrdinalIgnoreCase), out PlayerState.DeathReason newDeathReason)) + { + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("Fabricator.InvalidDeathReason"), args.Length >= 2 ? args[1] : "")); + return; + } + + fab.NextDeathReason = newDeathReason; + + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("Fabricator.SetDeathReason"), GetString(newDeathReason.ToString()))); + + MeetingManager.SendCommandUsedMessage(args[0]); + } + + private static void UIScaleCommand(PlayerControl player, string text, string[] args) { if (args.Length < 2 || !float.TryParse(args[1], out float scale) || scale == 0f) return; HudManagerStartPatch.TryResizeUI(scale); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index dbb5e7e64..df30be9d1 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -2511,6 +2511,7 @@ "CommandDescription.Revive": "Revives a dead player. Requires special access or No Game End enabled.", "CommandDescription.Select": "Select your impostor partner and their role.", "CommandDescription.UIScale": "Set the scale of the buttons in the UI", + "CommandDescription.Fabricate": "Set a fake death reason for your next kill as the Fabricator", "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", @@ -2607,6 +2608,7 @@ "CommandArgs.Select.Id": "The player's ID to select as your impostor partner", "CommandArgs.Select.Role": "The role to select for your impostor partner", "CommandArgs.UIScale.Scale": "The scale to set (1.0 = default, 0.5 = half size, 2.0 = double size)", + "CommandArgs.Fabricate.DeathReason": "The fake death reason (must be available in the mod)", "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\"", "AddTagSuccess": "<#00ffa5>✓ Suceessfully created the tag {0} for {1}. If you want to change it, just use this command again and it will override the current tag. To delete it, simply type <#00a5ff>/deletetag {2}!", "DeleteTagSuccess": "<#00ffa5>✓ Successfully deleted {0}'s tag.", @@ -4383,6 +4385,11 @@ "Imitator.TargetMustBeCrew": "<#ff5555>X <#bb0000>Your target must be a <#8cffff>Crewmate!", "Imitator.TargetMustBeDead": "<#ff5555>X <#bb0000>Your target must be dead!", "Imitator.Success": "<#00ff00>✓ <#00bb00>You will be imitating {0} in the next round!", + "Fabricator": "Fabricator", + "FabricatorInfo": "Change Death Reasons", + "FabricatorInfoLong": "(Impostors):\\nAs the Fabricator, you can use /fabricate {deathreason} to set the death reason for your next kill. Replace '{deathreason}' with one of the available death reasons in the mod.", + "Fabricator.InvalidDeathReason": "<#ff5555>X <#bb0000>Invalid death reason '<#ffffff>{0}'!", + "Fabricator.SetDeathReason": "<#00ff00>✓ <#00bb00>Your next kill will have the death reason: <#ffffff>{0}", "Slenderman": "Slenderman", "SlendermanInfo": "Everyone Gets Blinded Near You", "SlendermanInfoLong": "(Neutrals):\\nAs the Slenderman, whenever someone gets close to you, they will be blinded until they move away from you.\\n\\nKill everyone to win!", diff --git a/Roles/Impostor/Fabricator.cs b/Roles/Impostor/Fabricator.cs new file mode 100644 index 000000000..cc299468c --- /dev/null +++ b/Roles/Impostor/Fabricator.cs @@ -0,0 +1,42 @@ +using EHR.Modules; + +namespace EHR.Impostor; + +public class Fabricator : RoleBase +{ + public static bool On; + + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityUseGainWithEachKill; + + public override bool IsEnable => On; + + public PlayerState.DeathReason NextDeathReason; + + public override void SetupCustomOption() + { + StartSetup(655900) + .AutoSetupOption(ref AbilityUseLimit, 1f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.5f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + NextDeathReason = PlayerState.DeathReason.Kill; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat()); + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (NextDeathReason == PlayerState.DeathReason.Kill || killer.GetAbilityUseLimit() < 1 || !Main.PlayerStates.TryGetValue(target.PlayerId, out var state)) return; + state.deathReason = NextDeathReason; + RPC.SendDeathReason(target.PlayerId, state.deathReason); + killer.RpcRemoveAbilityUse(); + } +} \ No newline at end of file