New Role: Thanos (Neutral Killing)

This commit is contained in:
Gurge44 committed 2025-09-09 13:53:21 +02:00
1 parent c04bca764e
commit 088c218325
16 files changed
+340 -9

No files matched your search

+1
View File
@@ -367,6 +367,7 @@ public enum CustomRoles
Technician,
Tank,
Terrorist,
Thanos,
Tiger,
Traitor,
Tremor,
+2
View File
@@ -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,
+4
View File
@@ -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,
+3
View File
@@ -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)
{
+8
View File
@@ -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\\n<size=70%>Space 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</size>",
"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.",
+2
View File
@@ -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);
+3
View File
@@ -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;
+2 -1
View File
@@ -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();
+3 -3
View File
@@ -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, () =>
{
+3
View File
@@ -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();
+3 -2
View File
@@ -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, () =>
{
+3
View File
@@ -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;
+3 -3
View File
@@ -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;
+2
View File
@@ -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();
+2
View File
@@ -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, () =>
+296
View File
@@ -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<Thanos> 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<Stone> StonesWaitingForUse;
private HashSet<Stone> CollectedStones;
private Stone? ActiveStone;
private bool MindStoneUsed;
private List<byte> 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<Stone>().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<Stone>().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<Stone>().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<Stone>().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();
}
}