From 088c2183258d247f97a2cac189e416da326c6900 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Tue, 9 Sep 2025 13:53:21 +0200 Subject: [PATCH] New Role: Thanos (Neutral Killing) --- CustomRoles.cs | 1 + Main.cs | 2 + Modules/CustomRolesHelper.cs | 4 + Modules/Utils.cs | 3 + Resources/Lang/en_US.json | 8 + Roles/Coven/Dreamweaver.cs | 2 + Roles/Coven/VoodooMaster.cs | 3 + Roles/Impostor/Anonymous.cs | 3 +- Roles/Impostor/Blackmailer.cs | 6 +- Roles/Impostor/Echo.cs | 3 + Roles/Impostor/Mastermind.cs | 5 +- Roles/Impostor/Penguin.cs | 3 + Roles/Impostor/Puppeteer.cs | 6 +- Roles/Impostor/Venerer.cs | 2 + Roles/Impostor/Warlock.cs | 2 + Roles/Neutral/Thanos.cs | 296 ++++++++++++++++++++++++++++++++++ 16 files changed, 340 insertions(+), 9 deletions(-) create mode 100644 Roles/Neutral/Thanos.cs diff --git a/CustomRoles.cs b/CustomRoles.cs index 500cb3b0a..51eb836e5 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -367,6 +367,7 @@ public enum CustomRoles Technician, Tank, Terrorist, + Thanos, Tiger, Traitor, Tremor, diff --git a/Main.cs b/Main.cs index 48cff339a..d46e4784c 100644 --- a/Main.cs +++ b/Main.cs @@ -536,6 +536,7 @@ public class Main : BasePlugin { CustomRoles.HexMaster, "#ff00ff" }, { CustomRoles.Wraith, "#4B0082" }, { CustomRoles.Duality, "#8D6F64" }, + { CustomRoles.Thanos, "#F9D401" }, { CustomRoles.SerialKiller, "#233fcc" }, { CustomRoles.Slenderman, "#2c2e00" }, { CustomRoles.Amogus, "#ff0000" }, @@ -1016,6 +1017,7 @@ public enum CustomWinner Necromancer = CustomRoles.Necromancer, Wraith = CustomRoles.Wraith, SerialKiller = CustomRoles.SerialKiller, + Thanos = CustomRoles.Thanos, Duality = CustomRoles.Duality, Slenderman = CustomRoles.Slenderman, Amogus = CustomRoles.Amogus, diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index a9ad1558b..953e8cd0b 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -482,6 +482,7 @@ internal static class CustomRolesHelper CustomRoles.Poisoner => RoleTypes.Impostor, CustomRoles.Duality => RoleTypes.Impostor, CustomRoles.SerialKiller => RoleTypes.Impostor, + CustomRoles.Thanos => RoleTypes.Shapeshifter, CustomRoles.Slenderman => RoleTypes.Impostor, CustomRoles.Amogus => RoleTypes.Impostor, CustomRoles.Weatherman => RoleTypes.Impostor, @@ -610,6 +611,7 @@ internal static class CustomRolesHelper CustomRoles.Rogue or CustomRoles.Parasite or CustomRoles.SerialKiller or + CustomRoles.Thanos or CustomRoles.Duality or CustomRoles.Slenderman or CustomRoles.Amogus or @@ -1033,6 +1035,7 @@ internal static class CustomRolesHelper CustomRoles.Sprayer or CustomRoles.Werewolf or CustomRoles.WeaponMaster or + CustomRoles.Thanos or CustomRoles.Tiger or CustomRoles.Bargainer or CustomRoles.Chemist or @@ -1860,6 +1863,7 @@ public enum CountTypes HexMaster, Wraith, SerialKiller, + Thanos, Duality, Slenderman, Amogus, diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 0e2a4e8a4..b57c6dfce 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -839,6 +839,7 @@ public static class Utils case CustomRoles.Eclipse: case CustomRoles.Pyromaniac: case CustomRoles.SerialKiller: + case CustomRoles.Thanos: case CustomRoles.Slenderman: case CustomRoles.Amogus: case CustomRoles.Weatherman: @@ -3217,6 +3218,7 @@ public static class Utils int cd = role switch { CustomRoles.Farmer => 2, + CustomRoles.Thanos => 5, CustomRoles.PortalMaker => 5, CustomRoles.Mole => Mole.CD.GetInt(), CustomRoles.Monitor => Monitor.VentCooldown.GetInt(), @@ -3498,6 +3500,7 @@ public static class Utils Hitman.CheckAndResetTargets(); Reaper.OnAnyoneDead(target); Wyrd.OnAnyoneDeath(target); + Thanos.OnDeath(targetRealKiller, target, disconnect); if (!onMeeting && !disconnect) { diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 265d36cc0..2f2f711bb 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -3464,6 +3464,14 @@ "OccultistActionSwitchMode.Vanish": "Vanish", "OccultistReviving": "Reviving {0}....", "OccultistRevived": "{0} has been revived!", + "Thanos": "Thanos", + "ThanosInfo": "Collect Infinity Stones", + "ThanosInfoLong": "(Neutrals):\\nAs Thanos, your job is to collect all infinity stones and/or kill everyone to win. Random players (you don't know who) in the game have infinity stones, kill them to take it. Shift/Vanish/Pet to use the selected infinity stone. Each one has a different effect.\\n\\nSpace Stone: Your next kill will not leave a body behind and ignore all shields\\nMind Stone: Makes you permanently immune to controlling abilities\\nReality Stone: Gives you a shield.\\nPower Stone: Your kill cooldown will be significantly lower after your next kill\\nTime Stone: Slows down everyone except you\\nSoul Stone: Gives you an arrow to someone holding an infinity stone", + "Thanos.FirstKillAlwaysSoulStone": "The First Infinity Stone is Always The Soul Stone", + "Thanos.PowerStoneReducesKillCooldownBy": "Power Stone Kill Cooldown Reduction", + "Thanos.PowerStoneKillsIgnoreShields": "Kills With The Power Stone Ignore Shields", + "Thanos.TimeStoneReducesSpeedBy": "Time Stone Reduces Speed By", + "Thanos.CanWinAfterCollectingAllStones": "Can Win After Collecting All Infinity Stones", "Swapster": "Swapster", "SwapsterInfo": "Swap Players", "SwapsterInfoLong": "(Impostors):\\nAs the Swapster, you can select 2 players using your shapeshift menu to swap them.", diff --git a/Roles/Coven/Dreamweaver.cs b/Roles/Coven/Dreamweaver.cs index 774de2305..82181d3cc 100644 --- a/Roles/Coven/Dreamweaver.cs +++ b/Roles/Coven/Dreamweaver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using EHR.Modules; +using EHR.Neutral; using Hazel; namespace EHR.Coven; @@ -62,6 +63,7 @@ public class Dreamweaver : Coven public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { + if (Thanos.IsImmune(target)) return false; InsanePlayers.Add(target.PlayerId); Utils.SendRPC(CustomRPC.SyncRoleData, DreamweaverId, 1, target.PlayerId); Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); diff --git a/Roles/Coven/VoodooMaster.cs b/Roles/Coven/VoodooMaster.cs index af2eb3d18..3561d72f1 100644 --- a/Roles/Coven/VoodooMaster.cs +++ b/Roles/Coven/VoodooMaster.cs @@ -1,4 +1,5 @@ using System; +using EHR.Neutral; namespace EHR.Coven; @@ -52,6 +53,8 @@ public class VoodooMaster : Coven public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { + if (Thanos.IsImmune(target)) return HasNecronomicon; + if (killer.GetAbilityUseLimit() > 0) { RoleBase roleBase = Main.PlayerStates[target.PlayerId].Role; diff --git a/Roles/Impostor/Anonymous.cs b/Roles/Impostor/Anonymous.cs index 2c602f588..05e160e80 100644 --- a/Roles/Impostor/Anonymous.cs +++ b/Roles/Impostor/Anonymous.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using AmongUs.GameOptions; +using EHR.Neutral; using static EHR.Options; using static EHR.Translator; @@ -84,7 +85,7 @@ public class Anonymous : RoleBase public override bool OnShapeshift(PlayerControl pc, PlayerControl ssTarget, bool shapeshifting) { - if (!shapeshifting || pc.GetAbilityUseLimit() < 1 || ssTarget == null || ssTarget.Is(CustomRoles.Needy) || ssTarget.Is(CustomRoles.Lazy)) return false; + if (!shapeshifting || pc.GetAbilityUseLimit() < 1 || ssTarget == null || ssTarget.Is(CustomRoles.Needy) || ssTarget.Is(CustomRoles.Lazy) || Thanos.IsImmune(ssTarget)) return false; pc.RpcRemoveAbilityUse(); diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 31c145731..14fad0a93 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using EHR.Neutral; namespace EHR.Impostor; @@ -45,12 +46,11 @@ internal class Blackmailer : RoleBase public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { if (killer.GetAbilityUseLimit() < 1) return true; - if (NumBlackmailedThisRound >= MaxBlackmailedPlayersPerMeeting.GetInt()) return true; - if (BlackmailedPlayerIds.Count >= MaxBlackmailedPlayersAtOnce.GetInt()) return true; - if (BlackmailedPlayerIds.Contains(target.PlayerId)) return true; + + if (Thanos.IsImmune(target)) return false; return killer.CheckDoubleTrigger(target, () => { diff --git a/Roles/Impostor/Echo.cs b/Roles/Impostor/Echo.cs index 2f400c3c2..bb7420b23 100644 --- a/Roles/Impostor/Echo.cs +++ b/Roles/Impostor/Echo.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using AmongUs.GameOptions; using EHR.Modules; +using EHR.Neutral; namespace EHR.Impostor; @@ -80,6 +81,8 @@ public class Echo : RoleBase public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) { + if (Thanos.IsImmune(target)) return false; + if (shapeshifting) { Vector2 pos = target.Pos(); diff --git a/Roles/Impostor/Mastermind.cs b/Roles/Impostor/Mastermind.cs index dc9b39ed7..4436a1b72 100644 --- a/Roles/Impostor/Mastermind.cs +++ b/Roles/Impostor/Mastermind.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using EHR.Crewmate; using EHR.Modules; +using EHR.Neutral; using static EHR.Options; using static EHR.Translator; using static EHR.Utils; @@ -72,10 +73,10 @@ public class Mastermind : RoleBase public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { if (!IsEnable) return false; - if (killer == null) return false; - if (target == null) return false; + + if (Thanos.IsImmune(target)) return false; return killer.CheckDoubleTrigger(target, () => { diff --git a/Roles/Impostor/Penguin.cs b/Roles/Impostor/Penguin.cs index 8ab46ed23..e5d9e10fb 100644 --- a/Roles/Impostor/Penguin.cs +++ b/Roles/Impostor/Penguin.cs @@ -2,6 +2,7 @@ using AmongUs.GameOptions; using EHR.Crewmate; using EHR.Modules; +using EHR.Neutral; using Hazel; using UnityEngine; using static EHR.Translator; @@ -165,6 +166,8 @@ public class Penguin : RoleBase private void AddVictim(PlayerControl target) { if (!IsEnable) return; + + if (Thanos.IsImmune(target)) return; AbductVictim = target; AbductTimer = AbductTimerLimit; diff --git a/Roles/Impostor/Puppeteer.cs b/Roles/Impostor/Puppeteer.cs index 87cf80111..9fb73cd5c 100644 --- a/Roles/Impostor/Puppeteer.cs +++ b/Roles/Impostor/Puppeteer.cs @@ -113,9 +113,9 @@ internal class Puppeteer : RoleBase public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { - if (target.Is(CustomRoles.Needy) && PuppeteerManipulationBypassesLazyGuy.GetBool()) return false; - - if (target.Is(CustomRoles.Lazy) && PuppeteerManipulationBypassesLazy.GetBool()) return false; + if (Thanos.IsImmune(target)) return false; + if (target.Is(CustomRoles.Needy) && !PuppeteerManipulationBypassesLazyGuy.GetBool()) return false; + if (target.Is(CustomRoles.Lazy) && !PuppeteerManipulationBypassesLazy.GetBool()) return false; if (Medic.ProtectList.Contains(target.PlayerId)) return false; diff --git a/Roles/Impostor/Venerer.cs b/Roles/Impostor/Venerer.cs index 8ef93eaa5..647bf63c3 100644 --- a/Roles/Impostor/Venerer.cs +++ b/Roles/Impostor/Venerer.cs @@ -52,6 +52,8 @@ public class Venerer : RoleBase public override void ApplyGameOptions(IGameOptions opt, byte playerId) { + if (Options.UsePets.GetBool()) return; + if (Options.UsePhantomBasis.GetBool()) { AURoleOptions.PhantomCooldown = AbilityCooldown.GetInt(); diff --git a/Roles/Impostor/Warlock.cs b/Roles/Impostor/Warlock.cs index 647251b30..5f894fc45 100644 --- a/Roles/Impostor/Warlock.cs +++ b/Roles/Impostor/Warlock.cs @@ -135,6 +135,8 @@ internal class Warlock : RoleBase public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) { + if (target.Is(CustomRoles.Needy) || target.Is(CustomRoles.Lazy) || Thanos.IsImmune(target)) return false; + if (killer.IsShifted()) return false; if (killer.CheckDoubleTrigger(target, () => diff --git a/Roles/Neutral/Thanos.cs b/Roles/Neutral/Thanos.cs new file mode 100644 index 000000000..74050eb0f --- /dev/null +++ b/Roles/Neutral/Thanos.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AmongUs.GameOptions; +using EHR.Modules; +using Hazel; +using UnityEngine; + +namespace EHR.Neutral; + +public class Thanos : RoleBase +{ + public static bool On; + private static List Instances = []; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem ImpostorVision; + private static OptionItem FirstKillAlwaysSoulStone; + private static OptionItem PowerStoneReducesKillCooldownBy; + private static OptionItem PowerStoneKillsIgnoreShields; + private static OptionItem TimeStoneReducesSpeedBy; + private static OptionItem CanWinAfterCollectingAllStones; + + public override bool IsEnable => On; + + private List StonesWaitingForUse; + private HashSet CollectedStones; + private Stone? ActiveStone; + private bool MindStoneUsed; + private List PlayersWithStones; + private byte ThanosId; + + public override void SetupCustomOption() + { + StartSetup(654200) + .AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref CanVent, true) + .AutoSetupOption(ref ImpostorVision, true) + .AutoSetupOption(ref FirstKillAlwaysSoulStone, false) + .AutoSetupOption(ref PowerStoneReducesKillCooldownBy, 10f, new FloatValueRule(0f, 60f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref PowerStoneKillsIgnoreShields, true) + .AutoSetupOption(ref TimeStoneReducesSpeedBy, 0.5f, new FloatValueRule(0.1f, 3f, 0.1f), OptionFormat.Multiplier) + .AutoSetupOption(ref CanWinAfterCollectingAllStones, true); + } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + StonesWaitingForUse = []; + CollectedStones = []; + ActiveStone = null; + MindStoneUsed = false; + PlayersWithStones = Main.AllAlivePlayerControls.Shuffle().Take(Enum.GetValues().Length).Select(x => x.PlayerId).ToList(); + ThanosId = playerId; + Instances.Add(this); + } + + public override void Remove(byte playerId) + { + Instances.Remove(this); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + if (ActiveStone is Stone.Power) Main.AllPlayerKillCooldown[id] -= PowerStoneReducesKillCooldownBy.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(ImpostorVision.GetBool()); + + if (Options.UsePets.GetBool()) return; + + if (Options.UsePhantomBasis.GetBool()) + { + AURoleOptions.PhantomCooldown = 5f; + AURoleOptions.PhantomDuration = 1f; + } + else + { + AURoleOptions.ShapeshifterCooldown = 5f; + AURoleOptions.ShapeshifterDuration = 1f; + } + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (target.Is(CustomRoles.Pestilence)) return ActiveStone is Stone.Power or Stone.Space; + + if (ActiveStone is Stone.Space) + { + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + ActiveStone = null; + Utils.SendRPC(CustomRPC.SyncRoleData, ThanosId, 2); + target.Data.IsDead = true; + target.SetRealKiller(killer); + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Scavenged; + target.RpcExileV2(); + Main.PlayerStates[target.PlayerId].SetDead(); + Utils.AfterPlayerDeathTasks(target); + target.SetRealKiller(killer); + killer.SetKillCooldown(KillCooldown.GetFloat()); + return false; + } + + return true; + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return ActiveStone is not Stone.Reality; + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (PlayersWithStones.Contains(target.PlayerId)) + { + Stone stone = CollectedStones.Count == 0 && FirstKillAlwaysSoulStone.GetBool() ? Stone.Soul : Enum.GetValues().Except(CollectedStones).RandomElement(); + CollectedStones.Add(stone); + StonesWaitingForUse.Add(stone); + Utils.SendRPC(CustomRPC.SyncRoleData, ThanosId, 1, (int)stone); + } + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + UseStone(shapeshifter); + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + UseStone(pc); + return false; + } + + public override void OnPet(PlayerControl pc) + { + UseStone(pc); + } + + void UseStone(PlayerControl pc) + { + if (CollectedStones.Count == Enum.GetValues().Length && CanWinAfterCollectingAllStones.GetBool()) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Thanos); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + return; + } + + if (StonesWaitingForUse.Count == 0) return; + + if (ActiveStone.HasValue) + { + switch (ActiveStone) + { + case Stone.Time: + Main.AllAlivePlayerControls.DoIf(x => x.PlayerId != pc.PlayerId, x => + { + Main.AllPlayerSpeed[x.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + if (GameStates.IsInTask && !ExileController.Instance && !AntiBlackout.SkipTasks) x.MarkDirtySettings(); + }); + break; + case Stone.Soul: + TargetArrow.RemoveAllTarget(pc.PlayerId); + break; + } + } + + Stone stone = StonesWaitingForUse[0]; + StonesWaitingForUse.RemoveAt(0); + ActiveStone = stone; + + switch (stone) + { + case Stone.Space: + break; + case Stone.Mind: + MindStoneUsed = true; + ActiveStone = null; + break; + case Stone.Reality: + break; + case Stone.Power: + pc.ResetKillCooldown(); + break; + case Stone.Time: + Main.AllAlivePlayerControls.DoIf(x => x.PlayerId != pc.PlayerId, x => + { + Main.AllPlayerSpeed[x.PlayerId] -= TimeStoneReducesSpeedBy.GetFloat(); + x.MarkDirtySettings(); + }); + break; + case Stone.Soul: + TargetArrow.Add(pc.PlayerId, PlayersWithStones.RandomElement()); + break; + } + + Utils.SendRPC(CustomRPC.SyncRoleData, ThanosId, 3); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public static void OnDeath(PlayerControl killer, PlayerControl target, bool noKiller) + { + if (killer == null || killer.PlayerId == target.PlayerId) noKiller = true; + + foreach (Thanos instance in Instances) + { + if (!instance.PlayersWithStones.Remove(target.PlayerId)) continue; + instance.PlayersWithStones.Add(noKiller ? Main.AllAlivePlayerControls.RandomElement().PlayerId : killer.PlayerId); + } + } + + public static bool IsImmune(PlayerControl pc) => On && pc != null && Instances.Exists(x => x.ThanosId == pc.PlayerId && x.MindStoneUsed); + + enum Stone + { + Space, + Mind, + Reality, + Power, + Time, + Soul + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + { + Stone stone = (Stone)reader.ReadPackedInt32(); + CollectedStones.Add(stone); + StonesWaitingForUse.Add(stone); + break; + } + case 2: + { + ActiveStone = null; + break; + } + case 3: + { + Stone stone = StonesWaitingForUse[0]; + StonesWaitingForUse.RemoveAt(0); + ActiveStone = stone; + + if (stone == Stone.Mind) + { + MindStoneUsed = true; + ActiveStone = null; + } + + break; + } + } + } + + public override string GetProgressText(byte playerId, bool comms) + { + return base.GetProgressText(playerId, comms) + $" {CollectedStones.Count}/{Enum.GetValues().Length}"; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != ThanosId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting) return string.Empty; + + StringBuilder sb = new(); + + if (ActiveStone.HasValue) sb.AppendLine(string.Format(Translator.GetString("Thanos.ActiveStone"), Translator.GetString($"Thanos.Stone.{ActiveStone.Value}"))); + if (MindStoneUsed) sb.AppendLine(Translator.GetString("Thanos.MindStoneUsed")); + + if (StonesWaitingForUse.Count > 0) + { + string action = Translator.GetString(Options.UsePets.GetBool() ? "PetButtonText" : Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool() ? "AbilityButtonText.Phantom" : "Shapeshift"); + sb.AppendLine(string.Format(Translator.GetString("Thanos.StoneWaitingForUse"), action, Translator.GetString($"Thanos.Stone.{StonesWaitingForUse[0]}"))); + } + + sb.AppendLine(TargetArrow.GetAllArrows(seer.PlayerId)); + + return sb.ToString().Trim(); + } +} \ No newline at end of file