diff --git a/.gitignore b/.gitignore
index 74c2aaa..7c0fbec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ obj/
References/
NewMod/Components/Minigames
NewMod/Components/Hidden.cs
+NewMod/Private
/packages/
riderModule.iml
.idea
diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs
index e9b717b..d35f4da 100644
--- a/NewMod/Buttons/Necromancer/ReviveButton.cs
+++ b/NewMod/Buttons/Necromancer/ReviveButton.cs
@@ -6,6 +6,10 @@
using UnityEngine;
using NewMod.Utilities;
using MiraAPI.Keybinds;
+using AmongUs.GameOptions;
+using Reactor.Utilities;
+using MiraAPI.Utilities;
+using System.Linq;
namespace NewMod.Buttons.Necromancer
{
@@ -54,15 +58,29 @@ public class ReviveButton : CustomActionButton
///
protected override void OnClick()
{
+ var local = PlayerControl.LocalPlayer;
+
+ var body = Helpers.GetNearestDeadBodies(
+ local.GetTruePosition(),
+ ShipStatus.Instance.MaxLightRadius,
+ Helpers.CreateFilter(Constants.NotShipMask))
+ .Where(b => b != null && !PranksterUtilities.IsPranksterBody(b))
+ .OrderBy(b => Vector2.Distance(local.GetTruePosition(), b.TruePosition))
+ .FirstOrDefault();
+
+ if (body == null) return;
+
SoundManager.Instance.PlaySound(NewModAsset.ReviveSound?.LoadAsset(), false, 2f);
- var closestBody = Utils.GetClosestBody();
- if (closestBody != null)
- {
- Utils.RpcRevive(closestBody);
- }
+ Utils.HandleRevive(
+ local,
+ body.ParentId,
+ RoleTypes.Crewmate,
+ body.transform.position.x,
+ body.transform.position.y
+ );
+ NecromancerRole.RevivedPlayers[body.ParentId] = local.PlayerId;
}
-
///
/// Determines whether this button is enabled for the role, returning true if the role is .
///
@@ -79,32 +97,26 @@ public override bool Enabled(RoleBehaviour role)
/// True if all requirements to use this button are met; otherwise false.
public override bool CanUse()
{
- bool isTimerDone = Timer <= 0;
- bool hasUsesLeft = UsesLeft > 0;
- var closestBody = Utils.GetClosestBody();
- bool isNearDeadBody = closestBody != null;
- bool isFakeBody = isNearDeadBody && PranksterUtilities.IsPranksterBody(closestBody);
+ var bodiesInRange = Helpers.GetNearestDeadBodies(
+ PlayerControl.LocalPlayer.transform.position,
+ ShipStatus.Instance.MaxLightRadius,
+ Helpers.CreateFilter(Constants.NotShipMask));
- if (closestBody == null)
+ bool canUse = bodiesInRange.Any(body =>
{
- return false;
- }
+ if (PranksterUtilities.IsPranksterBody(body)) return false;
- bool wasNotKilledByNecromancer = true;
- var deadBody = closestBody.GetComponent();
- if (deadBody != null)
- {
- var killedPlayer = GameData.Instance.GetPlayerById(deadBody.ParentId)?.Object;
- if (killedPlayer != null)
- {
- var killer = Utils.GetKiller(killedPlayer);
- if (killer != null && killer.Data.Role is NecromancerRole)
- {
- wasNotKilledByNecromancer = false;
- }
- }
- }
- return isTimerDone && hasUsesLeft && isNearDeadBody && wasNotKilledByNecromancer && !isFakeBody;
+ var killedPlayer = GameData.Instance.GetPlayerById(body.ParentId)?.Object;
+ if (killedPlayer == null) return false;
+
+ var killer = Utils.GetKiller(killedPlayer);
+ if (killer.PlayerId == PlayerControl.LocalPlayer.PlayerId)
+ return false;
+
+ return true;
+ });
+
+ return canUse;
}
}
}
diff --git a/NewMod/Buttons/RevivedKillButton.cs b/NewMod/Buttons/RevivedKillButton.cs
new file mode 100644
index 0000000..b32a61d
--- /dev/null
+++ b/NewMod/Buttons/RevivedKillButton.cs
@@ -0,0 +1,63 @@
+using MiraAPI.Hud;
+using MiraAPI.Keybinds;
+using MiraAPI.Utilities.Assets;
+using NewMod.Roles.ImpostorRoles;
+using Reactor.Utilities;
+using AmongUs.GameOptions;
+using System.Linq;
+using UnityEngine;
+using MiraAPI.Networking;
+using MiraAPI.Utilities;
+
+namespace NewMod.Buttons
+{
+ public class RevivedKillButton : CustomActionButton
+ {
+ public override string Name => "KILL";
+ public override float Cooldown => GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown);
+ public override int MaxUses => 0;
+ public override float EffectDuration => 0f;
+ public override MiraKeybind Keybind => MiraGlobalKeybinds.PrimaryAbility;
+ public override ButtonLocation Location => ButtonLocation.BottomRight;
+ public override LoadableAsset Sprite => NewModAsset.VanillaKillButton;
+ public override bool Enabled(RoleBehaviour role)
+ {
+ return NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId);
+ }
+ public override PlayerControl GetTarget()
+ {
+ return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance);
+ }
+ public override bool IsTargetValid(PlayerControl target)
+ {
+ return target.PlayerId != PlayerControl.LocalPlayer.PlayerId;
+ }
+ public override void SetOutline(bool active)
+ {
+ Target.cosmetics.SetOutline(active, new Il2CppSystem.Nullable(Palette.ImpostorRed));
+ }
+
+ public override bool CanUse()
+ {
+ if (!NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) return false;
+ return true;
+ }
+
+ protected override void OnClick()
+ {
+ var local = PlayerControl.LocalPlayer;
+
+ local.RpcCustomMurder(
+ Target,
+ didSucceed: true,
+ resetKillTimer: true,
+ createDeadBody: true,
+ teleportMurderer: true,
+ showKillAnim: true,
+ playKillSound: true
+ );
+
+ NecromancerRole.RevivedPlayers.Remove(local.PlayerId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/NewMod/Buttons/WraithCaller/CallWraith.cs b/NewMod/Buttons/WraithCaller/CallWraith.cs
index 7a1031f..a3d8e75 100644
--- a/NewMod/Buttons/WraithCaller/CallWraith.cs
+++ b/NewMod/Buttons/WraithCaller/CallWraith.cs
@@ -85,7 +85,7 @@ protected override void OnClick()
player =>
{
menu.Close();
- WraithCallerUtilities.RpcSummonNPC(PlayerControl.LocalPlayer, player);
+ WraithCallerUtilities.RpcRequestSummonNPC(PlayerControl.LocalPlayer, player);
SetTimerPaused(false);
});
diff --git a/NewMod/Components/FearPulseArea.cs b/NewMod/Components/FearPulseArea.cs
index 989b31f..bdcd8a5 100644
--- a/NewMod/Components/FearPulseArea.cs
+++ b/NewMod/Components/FearPulseArea.cs
@@ -13,16 +13,25 @@ namespace NewMod.Components
public class FearPulseArea(IntPtr ptr) : MonoBehaviour(ptr)
{
public byte ownerId;
- float _radius, _duration, _speedMul, _t;
+
+ float _radius;
+ float _duration;
+ float _speedMul;
+ float _t;
+
readonly Dictionary _origSpeed = new();
readonly HashSet _insideNow = new();
+
public static readonly HashSet AffectedPlayers = new();
public static readonly HashSet _speedNotifShown = new();
public static readonly HashSet _visionNotifShown = new();
+
public AudioClip _enterClip;
public AudioClip _heartbeatClip;
public bool _pulsingHb;
+ bool _restored;
+
public void Init(byte ownerId, float radius, float duration, float speedMul)
{
this.ownerId = ownerId;
@@ -35,6 +44,8 @@ public void Init(byte ownerId, float radius, float duration, float speedMul)
public void Update()
{
+ if (_restored) return;
+
_t += Time.deltaTime;
if (_t > _duration)
{
@@ -89,12 +100,13 @@ public void Update()
{
_visionNotifShown.Add(p.PlayerId);
var notif = Helpers.CreateAndShowNotification(
- "You have entered the Fear Pulse Area. Your vision is reduced!",
- new Color(1f, 0.8f, 0.2f),
- spr: NewModAsset.VisionDebuff.LoadAsset()
- );
+ "You have entered the Fear Pulse Area. Your vision is reduced!",
+ new Color(1f, 0.8f, 0.2f),
+ spr: NewModAsset.VisionDebuff.LoadAsset()
+ );
notif.Text.SetOutlineThickness(0.36f);
}
+
Coroutines.Start(Utils.CoShakeCamera(Camera.main.GetComponent(), 0.5f));
}
@@ -112,40 +124,57 @@ public void Update()
var toRestore = _origSpeed.Keys.Where(id => !_insideNow.Contains(id)).ToList();
foreach (var id in toRestore)
{
- var p = Utils.PlayerById(id);
- if (p) p.MyPhysics.Speed = _origSpeed[id];
- _origSpeed.Remove(id);
+ RestorePlayer(id);
+ }
+ }
+ }
+
+ public void RestorePlayer(byte playerId)
+ {
+ if (_origSpeed.TryGetValue(playerId, out var originalSpeed))
+ {
+ var p = Utils.PlayerById(playerId);
+
+ if (!p.Data.IsDead || !p.Data.Disconnected)
+ {
+ p.MyPhysics.Speed = originalSpeed;
if (p.AmOwner)
{
- AffectedPlayers.Remove(p.PlayerId);
p.lightSource.lightChild.SetActive(true);
- _speedNotifShown.Remove(p.PlayerId);
- _visionNotifShown.Remove(p.PlayerId);
- Helpers.CreateAndShowNotification("Your vision is restored.", new Color(0.8f, 1f, 0.8f));
+
+ Helpers.CreateAndShowNotification(
+ "Your speed and vision are restored.",
+ new Color(0.8f, 1f, 0.8f)
+ );
}
}
+
+ _origSpeed.Remove(playerId);
}
+
+ AffectedPlayers.Remove(playerId);
+ _speedNotifShown.Remove(playerId);
+ _visionNotifShown.Remove(playerId);
}
+
public void RestoreAll()
{
- foreach (var kv in _origSpeed)
+ if (_restored) return;
+ _restored = true;
+
+ var ids = _origSpeed.Keys.ToList();
+ foreach (var id in ids)
{
- var p = Utils.PlayerById(kv.Key);
- if (p) p.MyPhysics.Speed = kv.Value;
+ RestorePlayer(id);
}
- _origSpeed.Clear();
- AffectedPlayers.Clear();
-
- var lp = PlayerControl.LocalPlayer;
-
- if (AffectedPlayers.Contains(lp.PlayerId))
- AffectedPlayers.Remove(lp.PlayerId);
- lp.lightSource.lightChild.SetActive(true);
+ _insideNow.Clear();
+ }
- _speedNotifShown.Remove(lp.PlayerId);
- _visionNotifShown.Remove(lp.PlayerId);
+ public void OnDestroy()
+ {
+ RestoreAll();
}
}
-}
+}
\ No newline at end of file
diff --git a/NewMod/Components/WraithCallerNpc.cs b/NewMod/Components/WraithCallerNpc.cs
index 599081d..9df95e7 100644
--- a/NewMod/Components/WraithCallerNpc.cs
+++ b/NewMod/Components/WraithCallerNpc.cs
@@ -19,52 +19,45 @@ public class WraithCallerNpc(IntPtr ptr) : MonoBehaviour(ptr)
public PlayerControl Target { get; set; }
public PlayerControl npc;
public LightSource ownerLight;
- public bool isActive = false;
+ public bool isActive;
[HideFromIl2Cpp]
-
// Inspired by: https://github.com/NuclearPowered/Reactor/blob/e27a79249ea706318f3c06f3dc56a5c42d65b1cf/Reactor.Debugger/Window/Tabs/GameTab.cs#L70
- public void Initialize(PlayerControl wraith, PlayerControl target)
+ public void Initialize(PlayerControl wraith, PlayerControl target, PlayerControl spawned)
{
Owner = wraith;
Target = target;
+ npc = spawned;
- var prefab = AmongUsClient.Instance.PlayerPrefab;
- npc = Instantiate(prefab);
- npc.PlayerId = (byte)GameData.Instance.GetAvailableId();
-
- var npcData = GameData.Instance.AddDummy(npc);
- AmongUsClient.Instance.Spawn(npcData);
- AmongUsClient.Instance.Spawn(npc);
+ isActive = true;
- npc.isDummy = false;
- npc.notRealPlayer = true;
KillAnimation.SetMovement(npc, true);
- npc.NetTransform.RpcSnapTo(Owner.transform.position);
- npc.MyPhysics.Speed = OptionGroupSingleton.Instance.NPCSpeed;
-
- var color = (byte)(npc.PlayerId % Palette.PlayerColors.Length);
- npc.RpcSetName("Wraith NPC");
- npc.RpcSetColor(color);
- npc.RpcSetHat("");
- npc.RpcSetSkin("");
- npc.RpcSetPet("");
- npc.RpcSetVisor("");
npc.Collider.enabled = false;
+ npc.MyPhysics.Speed = OptionGroupSingleton.Instance.NPCSpeed;
- var noShadow = npc.gameObject.AddComponent();
- if (noShadow != null)
- {
- noShadow.rend = npc.cosmetics.currentBodySprite.BodySprite;
- noShadow.hitOverride = npc.Collider;
- }
+ if (!npc.TryGetComponent(out _))
+ npc.gameObject.AddComponent();
- if (!npc.TryGetComponent(out var mc))
+ if (AmongUsClient.Instance.AmHost)
{
- mc = npc.gameObject.AddComponent();
+ NewMod.Instance.Log.LogMessage($"Host is setting cosmetics for NPC (ID: {npc.PlayerId}");
+ npc.NetTransform.RpcSnapTo(Owner.transform.position);
+
+ var color = (byte)(npc.PlayerId % Palette.PlayerColors.Length);
+ npc.RpcSetName("Wraith NPC");
+ npc.RpcSetColor(color);
+
+ var noShadow = npc.gameObject.AddComponent();
+ if (noShadow != null)
+ {
+ noShadow.rend = npc.cosmetics.currentBodySprite.BodySprite;
+ noShadow.hitOverride = npc.Collider;
+ }
}
-
+
+ Coroutines.Start(WalkToTarget());
+
if (OptionGroupSingleton.Instance.ShouldSwitchCamToNPC)
{
Camera.main.GetComponent().SetTarget(npc);
@@ -73,52 +66,46 @@ public void Initialize(PlayerControl wraith, PlayerControl target)
ownerLight.transform.localPosition = npc.Collider.offset;
}
- npc.cosmetics.enabled = false;
+ npc.cosmetics.enabled = true;
npc.enabled = false;
- isActive = true;
-
- Coroutines.Start(WalkToTarget());
-
if (Target.AmOwner)
- {
SoundManager.Instance.PlaySound(NewModAsset.HeartbeatSound.LoadAsset(), false, 1f);
- }
}
+
[HideFromIl2Cpp]
- public System.Collections.IEnumerator WalkToTarget()
+ private System.Collections.IEnumerator WalkToTarget()
{
- if (Target.Data.IsDead || Target.Data.Disconnected)
- {
- Dispose();
- }
+ //yield return null;
+
+ if (!AmongUsClient.Instance.AmHost) yield break;
+
while (isActive && !MeetingHud.Instance)
{
+ if (!Target || !Target.Data || Target.Data.IsDead)
+ break;
+
Vector2 npcPos = npc.GetTruePosition();
Vector2 targetPos = Target.GetTruePosition();
Vector2 dir = (targetPos - npcPos).normalized;
npc.MyPhysics.SetNormalizedVelocity(dir);
- float distance = Vector2.Distance(npcPos, targetPos);
-
- if (distance <= 0.1f)
+ if (Vector2.Distance(npcPos, targetPos) <= 0.1f)
{
npc.MyPhysics.SetNormalizedVelocity(Vector2.zero);
Owner.RpcCustomMurder(Target, true, teleportMurderer: false);
if (Target.AmOwner)
- {
CoroutinesHelper.CoNotify("Oops! The Wraith NPC got you...");
- }
- WraithCallerUtilities.AddKillNPC(Owner.PlayerId);
- Dispose();
- yield break;
+ WraithCallerUtilities.AddKillNPC(Owner.PlayerId);
+ break;
}
yield return new WaitForFixedUpdate();
}
+
npc.MyPhysics.SetNormalizedVelocity(Vector2.zero);
Dispose();
}
@@ -127,25 +114,26 @@ public System.Collections.IEnumerator WalkToTarget()
public void Dispose()
{
if (!isActive) return;
-
isActive = false;
- if (npc != null)
+ if (OptionGroupSingleton.Instance.ShouldSwitchCamToNPC)
+ {
+ Camera.main.GetComponent().SetTarget(Owner);
+ ownerLight.transform.SetParent(Owner.transform, false);
+ ownerLight.transform.localPosition = Owner.Collider.offset;
+ }
+
+ if (AmongUsClient.Instance.AmHost)
{
- if (OptionGroupSingleton.Instance.ShouldSwitchCamToNPC)
- {
- var cam = Camera.main.GetComponent();
- cam.SetTarget(Owner);
- ownerLight.transform.SetParent(Owner.transform, false);
- ownerLight.transform.localPosition = Owner.Collider.offset;
- }
var info = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(d => d.PlayerId == npc.PlayerId);
GameData.Instance.RemovePlayer(info.PlayerId);
PlayerControl.AllPlayerControls.Remove(npc);
+ npc.Despawn();
Destroy(npc.gameObject);
npc = null;
}
+
Destroy(gameObject);
}
}
diff --git a/NewMod/CustomRPC.cs b/NewMod/CustomRPC.cs
index c340bf9..c196db1 100644
--- a/NewMod/CustomRPC.cs
+++ b/NewMod/CustomRPC.cs
@@ -2,7 +2,7 @@ namespace NewMod;
public enum CustomRPC
{
- Revive,
+ HandleRevive,
Drain,
FakeBody,
AssignMission,
diff --git a/NewMod/NewMod.cs b/NewMod/NewMod.cs
index 4822c30..b43aa33 100644
--- a/NewMod/NewMod.cs
+++ b/NewMod/NewMod.cs
@@ -47,7 +47,7 @@ public override void Load()
{
Instance = this;
AddComponent();
- ReactorCredits.Register("NewMod", "v1.2.9 Hotfix 1", true, ReactorCredits.AlwaysShow);
+ ReactorCredits.Register("NewMod", "v1.2.9 Hotfix 2", true, ReactorCredits.AlwaysShow);
Harmony.PatchAll();
NewModEventHandler.RegisterEventsLogs();
@@ -170,7 +170,7 @@ public static class SetTaskTextPatch
{
public static void Postfix(TaskPanelBehaviour __instance, [HarmonyArgument(0)] string str)
{
- if (PlayerControl.LocalPlayer.Data.IsDead)
+ if (__instance.taskText != null && PlayerControl.LocalPlayer.Data.IsDead)
{
__instance.taskText.text += "\n" + (OptionGroupSingleton.Instance.AllowCams ? "Press F2 For Open Cams" : "You cannot open cams because the host has disabled this setting");
}
diff --git a/NewMod/NewMod.csproj b/NewMod/NewMod.csproj
index ecceb52..b21bb28 100644
--- a/NewMod/NewMod.csproj
+++ b/NewMod/NewMod.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/NewMod/NewModAsset.cs b/NewMod/NewModAsset.cs
index 0e92036..2105dcb 100644
--- a/NewMod/NewModAsset.cs
+++ b/NewMod/NewModAsset.cs
@@ -42,6 +42,7 @@ public static class NewModAsset
public static LoadableResourceAsset Shield { get; } = new("NewMod.Resources.Shield.png");
public static LoadableResourceAsset Slash { get; } = new("NewMod.Resources.Slash.png");
public static LoadableResourceAsset DeployZone { get; } = new("NewMod.Resources.deployzone.png");
+ public static LoadableResourceAsset VanillaKillButton { get; } = new("NewMod.Resources.killbutton.png");
// SFX
public static LoadableAudioResourceAsset ReviveSound { get; } = new("NewMod.Resources.Sounds.revive.wav");
diff --git a/NewMod/NewModDateTime.cs b/NewMod/NewModDateTime.cs
index 049d833..6adbaf0 100644
--- a/NewMod/NewModDateTime.cs
+++ b/NewMod/NewModDateTime.cs
@@ -40,7 +40,6 @@ public static bool IsNewModBirthdayWeek
return now >= start && now < end;
}
}
- public static bool IsWraithCallerUnlocked => DateTime.Now >= BirthdayStartThisYear;
private const int HalloweenMonth = 10;
private const int HalloweenDay = 31;
public static readonly TimeSpan HalloweenWindow = TimeSpan.FromDays(7);
diff --git a/NewMod/NewModEventHandler.cs b/NewMod/NewModEventHandler.cs
index 93f1b4d..4ea18f6 100644
--- a/NewMod/NewModEventHandler.cs
+++ b/NewMod/NewModEventHandler.cs
@@ -4,6 +4,8 @@
using System.Reflection;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
+using NewMod.Roles.ImpostorRoles;
+using NewMod.Utilities;
namespace NewMod
{
@@ -56,13 +58,15 @@ public static void RegisterEventsLogs()
NewMod.Instance.Log.LogInfo(sb.ToString());
}
- // General events
[RegisterEvent]
public static void OnRoundStart(RoundStartEvent evt)
{
if (!evt.TriggeredByIntro) return;
HudManager.Instance.Chat.enabled = false;
+
+ Utils.ResetKillTracking();
+ NecromancerRole.RevivedPlayers.Clear();
}
}
}
diff --git a/NewMod/Patches/EndGamePatch.cs b/NewMod/Patches/EndGamePatch.cs
index c42a5f4..35e4e2a 100644
--- a/NewMod/Patches/EndGamePatch.cs
+++ b/NewMod/Patches/EndGamePatch.cs
@@ -25,6 +25,15 @@ namespace NewMod.Patches
{
public static class EndGamePatch
{
+ public static bool EndGameTriggered = false;
+
+ [RegisterEvent]
+ public static void OnGameStart(RoundStartEvent evt)
+ {
+ EndGameTriggered = false;
+ EndGameResult.CachedWinners.Clear();
+ }
+
[RegisterEvent]
public static void OnGameEnd(GameEndEvent evt)
{
@@ -68,6 +77,7 @@ public static void OnGameEnd(GameEndEvent evt)
{
poolablePlayer.SetFlipX(i % 2 == 0);
}
+
poolablePlayer.UpdateFromPlayerOutfit(
playerData.Outfit,
PlayerMaterial.MaskType.None,
@@ -151,6 +161,7 @@ public static void OnGameEnd(GameEndEvent evt)
endGameManager.WinText.transform.position.y - 0.5f,
endGameManager.WinText.transform.position.z);
customWinTextObject.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
+
var customWinTextComponent = customWinTextObject.GetComponent();
customWinTextComponent.text = customWinText;
customWinTextComponent.color = customWinColor;
@@ -173,6 +184,7 @@ public static string GetRoleName(CachedPlayerData playerData, out Color roleColo
{
return $"{newmodRole.RoleName}\n{Utils.GetFactionDisplay((INewModRole)customRole)}";
}
+
return customRole.RoleName;
}
else
@@ -214,6 +226,20 @@ private static Color GetRoleColor(RoleTypes roleType)
return Color.white;
}
}
+
+ public static bool EndCustomGame(GameOverReason reason, Action winners = null)
+ {
+ if (EndGameTriggered) return true;
+ if (EndGameResult.CachedWinners.Count > 0) return true;
+
+ EndGameTriggered = true;
+
+ EndGameResult.CachedWinners.Clear();
+ winners?.Invoke();
+
+ GameManager.Instance.RpcEndGame(reason, false);
+ return true;
+ }
}
[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))]
@@ -222,7 +248,10 @@ public static class CheckGameEndPatch
public static bool Prefix(ShipStatus __instance)
{
if (DestroyableSingleton.InstanceExists) return true;
+ if (!AmongUsClient.Instance.AmHost) return true;
if (Time.timeSinceLevelLoad < 2f) return true;
+ if (EndGamePatch.EndGameTriggered) return false;
+
if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.WraithCallerWin)) return false;
if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.ShadeWin)) return false;
if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.PulseBladeWin)) return false;
@@ -231,18 +260,22 @@ public static bool Prefix(ShipStatus __instance)
if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.SpecialAgentWin)) return false;
if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.PranksterWin, 3)) return false;
if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.EnergyThiefWin)) return false;
+ if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.InjectorWin)) return false;
+
return true;
}
+
public static bool CheckForEndGameFaction(ShipStatus __instance, GameOverReason winReason, int maxCount = 1) where TFaction : INewModRole
{
var players = PlayerControl.AllPlayerControls.ToArray()
- .Where(p => p.Data.Role is TFaction)
- .Take(maxCount)
- .ToList();
+ .Where(p => p.Data.Role is TFaction)
+ .Take(maxCount)
+ .ToList();
foreach (var player in players)
{
bool shouldEndGame = false;
+ Action extraWinners = null;
if (typeof(TFaction) == typeof(PulseBlade))
{
@@ -260,51 +293,58 @@ public static bool CheckForEndGameFaction(ShipStatus __instance, GameO
shouldEndGame = true;
}
}
+
if (typeof(TFaction) == typeof(Tyrant))
{
if (Tyrant.ApexThroneReady && Tyrant.ApexThroneOutcomeSet)
{
shouldEndGame = true;
- var tyrantRole = player.Data.Role as Tyrant;
- byte champId = tyrantRole.GetChampion();
- var champion = Utils.PlayerById(champId);
+ extraWinners = () =>
+ {
+ var tyrantRole = player.Data.Role as Tyrant;
+ byte champId = tyrantRole.GetChampion();
+ var champion = Utils.PlayerById(champId);
- bool championWin = Tyrant.Outcome == Tyrant.ThroneOutcome.ChampionSideWin;
+ bool championWin = Tyrant.Outcome == Tyrant.ThroneOutcome.ChampionSideWin;
- if (champion && championWin)
- {
- EndGameResult.CachedWinners.Add(new(champion.Data));
- }
+ if (champion && championWin)
+ {
+ EndGameResult.CachedWinners.Add(new(champion.Data));
+ }
+ };
}
}
+
if (typeof(TFaction) == typeof(WraithCaller))
{
int required = (int)OptionGroupSingleton.Instance.RequiredNPCsToSend;
int current = WraithCallerUtilities.GetKillsNPC(player.PlayerId);
shouldEndGame = current >= required;
}
+
if (typeof(TFaction) == typeof(Shade))
{
Shade.ShadeKills.TryGetValue(player.PlayerId, out var count);
int required = (int)OptionGroupSingleton.Instance.RequiredKills;
shouldEndGame = count >= required;
}
+
if (shouldEndGame)
{
- GameManager.Instance.RpcEndGame(winReason, false);
- CustomStatsManager.IncrementRoleWin((INewModRole)player.Data.Role);
- return true;
+ return EndGamePatch.EndCustomGame(winReason, extraWinners);
}
}
+
return false;
}
+
public static bool CheckEndGameForRole(ShipStatus __instance, GameOverReason winReason, int maxCount = 1) where T : RoleBehaviour
{
var rolePlayers = PlayerControl.AllPlayerControls.ToArray()
- .Where(p => p.Data.Role is T)
- .Take(maxCount)
- .ToList();
+ .Where(p => p.Data.Role is T)
+ .Take(maxCount)
+ .ToList();
foreach (var player in rolePlayers)
{
@@ -316,19 +356,21 @@ public static bool CheckEndGameForRole(ShipStatus __instance, GameOverReason
bool isSabotageActive = Utils.IsSabotage();
shouldEndGame = tasksCompleted && isSabotageActive;
}
+
if (typeof(T) == typeof(EnergyThief))
{
int drainCount = Utils.GetDrainCount(player.PlayerId);
int requiredDrainCount = (int)OptionGroupSingleton.Instance.RequiredDrainCount;
-
shouldEndGame = drainCount >= requiredDrainCount;
}
+
if (typeof(T) == typeof(Prankster))
{
int WinReportCount = 2;
int currentReportCount = PranksterUtilities.GetReportCount(player.PlayerId);
shouldEndGame = currentReportCount >= WinReportCount;
}
+
if (typeof(T) == typeof(SpecialAgent))
{
int missionSuccessCount = Utils.GetMissionSuccessCount(player.PlayerId);
@@ -336,20 +378,21 @@ public static bool CheckEndGameForRole(ShipStatus __instance, GameOverReason
int netScore = missionSuccessCount - missionFailureCount;
shouldEndGame = netScore >= OptionGroupSingleton.Instance.RequiredMissionsToWin;
}
+
if (typeof(T) == typeof(InjectorRole))
{
int injectedCount = Utils.GetInjectedCount();
int required = (int)OptionGroupSingleton.Instance.RequiredInjectCount;
shouldEndGame = injectedCount >= required;
}
+
if (shouldEndGame)
{
- GameManager.Instance.RpcEndGame(winReason, false);
- CustomStatsManager.IncrementRoleWin((ICustomRole)player.Data.Role);
- return true;
+ return EndGamePatch.EndCustomGame(winReason);
}
}
+
return false;
}
}
-}
+}
\ No newline at end of file
diff --git a/NewMod/Patches/MainMenuPatch.cs b/NewMod/Patches/MainMenuPatch.cs
index 0e62cb4..acd3793 100644
--- a/NewMod/Patches/MainMenuPatch.cs
+++ b/NewMod/Patches/MainMenuPatch.cs
@@ -22,7 +22,6 @@ public static class MainMenuPatch
public static SpriteRenderer LogoSprite;
public static Texture2D _cachedCursor;
public static Transform RightPanel;
- public static bool _wraithRegistered = false;
[HarmonyPatch(nameof(MainMenuManager.Start))]
[HarmonyPostfix]
@@ -40,11 +39,6 @@ public static void StartPostfix(MainMenuManager __instance)
RightPanel = __instance.transform.Find("MainUI/AspectScaler/RightPanel");
- if (NewModDateTime.IsWraithCallerUnlocked && !_wraithRegistered)
- {
- _wraithRegistered = true;
- }
-
if (NewModDateTime.IsNewModBirthdayWeek)
{
Coroutines.Start(ApplyBirthdayUI(__instance));
diff --git a/NewMod/Patches/RolePatch.cs b/NewMod/Patches/RolePatch.cs
index 94c010f..3730d0e 100644
--- a/NewMod/Patches/RolePatch.cs
+++ b/NewMod/Patches/RolePatch.cs
@@ -4,7 +4,6 @@
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
-using Hazel;
using MiraAPI.GameOptions;
using MiraAPI.Roles;
using MiraAPI.Utilities;
@@ -31,7 +30,7 @@ public static void Postfix(RoleManager __instance)
private static IEnumerator CoAdjustNeutrals()
{
yield return null;
- yield return new WaitForSeconds(0.05f);
+ yield return new WaitForSeconds(0.06f);
var opts = OptionGroupSingleton.Instance;
int target = Mathf.RoundToInt(opts.TotalNeutrals);
@@ -220,7 +219,7 @@ private static IEnumerator CoAdjustNeutrals()
Logger.Instance.LogMessage("-------------- NEUTRAL ADJUST: END --------------");
}
- public struct Candidate
+ public class Candidate
{
public ICustomRole Role;
public int Left;
diff --git a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs
index 08f5635..f8d71d2 100644
--- a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs
+++ b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs
@@ -15,17 +15,17 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] EndGam
Utils.ResetMissionFailureCount();
Utils.ResetInjections();
Utils.ResetStrikeCount();
+ Utils.ResetKillTracking();
PranksterUtilities.ResetReportCount();
VisionaryUtilities.DeleteAllScreenshots();
WraithCallerUtilities.ClearAll();
Shade.ShadeKills.Clear();
- Revenant.HasUsedFeignDeath = false;
- Revenant.FeignDeathStates.Remove(PlayerControl.LocalPlayer.PlayerId);
- Revenant.StalkingStates[PlayerControl.LocalPlayer.PlayerId] = false;
+ Revenant.ResetAllStates();
+ NecromancerRole.RevivedPlayers.Clear();
NewMod.Instance.Log.LogInfo("Reset Drain Count Successfully");
NewMod.Instance.Log.LogInfo("Reset Clone Report Count Successfully");
NewMod.Instance.Log.LogInfo("Reset Mission Success Count Successfully");
NewMod.Instance.Log.LogInfo("Reset Mission Failure Count Successfully");
NewMod.Instance.Log.LogInfo("Deleted all Visionary's screenshots Successfully");
}
-}
\ No newline at end of file
+}
diff --git a/NewMod/Patches/StatsPopupPatch.cs b/NewMod/Patches/StatsPopupPatch.cs
index eaee3e1..8fead5e 100644
--- a/NewMod/Patches/StatsPopupPatch.cs
+++ b/NewMod/Patches/StatsPopupPatch.cs
@@ -1,4 +1,4 @@
-using AmongUs.GameOptions;
+/*using AmongUs.GameOptions;
using UnityEngine;
using HarmonyLib;
using MiraAPI.Roles;
@@ -173,4 +173,4 @@ public static bool Prefix(StatsPopup __instance)
return false;
}
}
-}
\ No newline at end of file
+}*/
\ No newline at end of file
diff --git a/NewMod/Resources/killbutton.png b/NewMod/Resources/killbutton.png
new file mode 100644
index 0000000..d137058
Binary files /dev/null and b/NewMod/Resources/killbutton.png differ
diff --git a/NewMod/Roles/CrewmateRoles/Specialist.cs b/NewMod/Roles/CrewmateRoles/Specialist.cs
index fac940d..c43cb92 100644
--- a/NewMod/Roles/CrewmateRoles/Specialist.cs
+++ b/NewMod/Roles/CrewmateRoles/Specialist.cs
@@ -37,17 +37,17 @@ public class Specialist : CrewmateRole, ICustomRole
[RegisterEvent]
public static void OnTaskComplete(CompleteTaskEvent evt)
{
- PlayerControl player = evt.Player;
- if (player.Data.Role is not Specialist) return;
+ PlayerControl specialist = evt.Player;
+ if (specialist.Data.Role is not Specialist) return;
List abilityAction = new List
{
() =>
{
- var target = Utils.GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected && p != player);
+ var target = Utils.GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected && p != specialist);
if (target != null)
{
- Utils.RpcRandomDrainActions(player, target);
+ Utils.RpcRandomDrainActions(specialist, target);
Helpers.CreateAndShowNotification($"Energy Drain activated on {target.Data.PlayerName}!",Color.green);
}
},
@@ -57,13 +57,13 @@ public static void OnTaskComplete(CompleteTaskEvent evt)
var player = Utils.PlayerById(closestBody.ParentId);
if (closestBody != null)
{
- Utils.RpcRevive(closestBody);
+ Utils.HandleRevive(specialist, closestBody.ParentId, AmongUs.GameOptions.RoleTypes.Crewmate, closestBody.transform.position.x, closestBody.transform.position.y);
Helpers.CreateAndShowNotification($"Player {player.Data.PlayerName} has been revived.", Color.green);
}
},
() =>
{
- PranksterUtilities.CreatePranksterDeadBody(player, player.PlayerId);
+ PranksterUtilities.CreatePranksterDeadBody(specialist, specialist.PlayerId);
Helpers.CreateAndShowNotification("Fake Body created!", Color.green);
},
() =>
diff --git a/NewMod/Roles/ImpostorRoles/Necromancer.cs b/NewMod/Roles/ImpostorRoles/Necromancer.cs
index 6ea738d..921fe04 100644
--- a/NewMod/Roles/ImpostorRoles/Necromancer.cs
+++ b/NewMod/Roles/ImpostorRoles/Necromancer.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using MiraAPI.GameOptions;
using MiraAPI.Roles;
using MiraAPI.Utilities;
@@ -9,6 +10,7 @@ namespace NewMod.Roles.ImpostorRoles;
public class NecromancerRole : ImpostorRole, ICustomRole
{
+ public static Dictionary RevivedPlayers = new();
public string RoleName => "Necromancer";
public string RoleDescription => "You can revive dead players who weren't killed by you";
public string RoleLongDescription => "As the Necromancer, you possess a unique and powerful ability: the power to bring one dead player back to life. However,\nyou can only revive someone who wasn't killed by you";
@@ -24,6 +26,22 @@ public class NecromancerRole : ImpostorRole, ICustomRole
public TeamIntroConfiguration TeamConfiguration => new()
{
IntroTeamDescription = RoleDescription,
- IntroTeamColor = RoleColor
+ IntroTeamColor = RoleColor
};
+ public override bool DidWin(GameOverReason reason)
+ {
+ if (reason == (GameOverReason)NewModEndReasons.TyrantWin ||
+ reason == (GameOverReason)NewModEndReasons.ShadeWin ||
+ reason == (GameOverReason)NewModEndReasons.WraithCallerWin ||
+ reason == (GameOverReason)NewModEndReasons.SpecialAgentWin ||
+ reason == (GameOverReason)NewModEndReasons.PranksterWin ||
+ reason == (GameOverReason)NewModEndReasons.EnergyThiefWin ||
+ reason == (GameOverReason)NewModEndReasons.InjectorWin ||
+ reason == (GameOverReason)NewModEndReasons.DoubleAgentWin)
+ {
+ return false;
+ }
+
+ return base.DidWin(reason);
+ }
}
diff --git a/NewMod/Roles/ImpostorRoles/PulseBlade.cs b/NewMod/Roles/ImpostorRoles/PulseBlade.cs
index a7c0f07..f4d3628 100644
--- a/NewMod/Roles/ImpostorRoles/PulseBlade.cs
+++ b/NewMod/Roles/ImpostorRoles/PulseBlade.cs
@@ -61,9 +61,24 @@ public StringBuilder SetTabText()
return tabText;
}
- public override bool DidWin(GameOverReason gameOverReason)
+ public override bool DidWin(GameOverReason reason)
{
- return gameOverReason == (GameOverReason)NewModEndReasons.PulseBladeWin;
+ if (reason == (GameOverReason)NewModEndReasons.PulseBladeWin)
+ return true;
+
+ if (reason == (GameOverReason)NewModEndReasons.TyrantWin ||
+ reason == (GameOverReason)NewModEndReasons.ShadeWin ||
+ reason == (GameOverReason)NewModEndReasons.WraithCallerWin ||
+ reason == (GameOverReason)NewModEndReasons.SpecialAgentWin ||
+ reason == (GameOverReason)NewModEndReasons.PranksterWin ||
+ reason == (GameOverReason)NewModEndReasons.EnergyThiefWin ||
+ reason == (GameOverReason)NewModEndReasons.InjectorWin ||
+ reason == (GameOverReason)NewModEndReasons.DoubleAgentWin)
+ {
+ return false;
+ }
+
+ return base.DidWin(reason);
}
}
}
\ No newline at end of file
diff --git a/NewMod/Roles/ImpostorRoles/Revenant.cs b/NewMod/Roles/ImpostorRoles/Revenant.cs
index 1d067f1..eba7774 100644
--- a/NewMod/Roles/ImpostorRoles/Revenant.cs
+++ b/NewMod/Roles/ImpostorRoles/Revenant.cs
@@ -41,17 +41,32 @@ public class FeignDeathInfo
public bool Reported;
}
+ public static void ResetAllStates()
+ {
+ FeignDeathStates.Clear();
+ StalkingStates.Clear();
+ HasUsedFeignDeath = false;
+ }
+
[RegisterEvent]
public static void OnPlayerExit(PlayerLeaveEvent evt)
{
- if (FeignDeathStates.ContainsKey(evt.ClientData.Character.PlayerId))
- {
- FeignDeathStates.Remove(evt.ClientData.Character.PlayerId);
- }
- if (StalkingStates.ContainsKey(evt.ClientData.Character.PlayerId))
+ ResetAllStates();
+ }
+ public override bool DidWin(GameOverReason reason)
+ {
+ if (reason == (GameOverReason)NewModEndReasons.TyrantWin ||
+ reason == (GameOverReason)NewModEndReasons.ShadeWin ||
+ reason == (GameOverReason)NewModEndReasons.WraithCallerWin ||
+ reason == (GameOverReason)NewModEndReasons.SpecialAgentWin ||
+ reason == (GameOverReason)NewModEndReasons.PranksterWin ||
+ reason == (GameOverReason)NewModEndReasons.EnergyThiefWin ||
+ reason == (GameOverReason)NewModEndReasons.InjectorWin ||
+ reason == (GameOverReason)NewModEndReasons.DoubleAgentWin)
{
- StalkingStates.Remove(evt.ClientData.Character.PlayerId);
+ return false;
}
- HasUsedFeignDeath = false;
+
+ return base.DidWin(reason);
}
}
diff --git a/NewMod/Roles/NeutralRoles/Tyrant.cs b/NewMod/Roles/NeutralRoles/Tyrant.cs
index 82eea6b..00d6dcc 100644
--- a/NewMod/Roles/NeutralRoles/Tyrant.cs
+++ b/NewMod/Roles/NeutralRoles/Tyrant.cs
@@ -94,6 +94,23 @@ void AppendAbilityLine(int index, string text)
return tabText;
}
+ public override bool DidWin(GameOverReason reason)
+ {
+ if (reason == (GameOverReason)NewModEndReasons.TyrantWin) return true;
+
+ if (reason == (GameOverReason)NewModEndReasons.ShadeWin ||
+ reason == (GameOverReason)NewModEndReasons.WraithCallerWin ||
+ reason == (GameOverReason)NewModEndReasons.SpecialAgentWin ||
+ reason == (GameOverReason)NewModEndReasons.PranksterWin ||
+ reason == (GameOverReason)NewModEndReasons.EnergyThiefWin ||
+ reason == (GameOverReason)NewModEndReasons.InjectorWin ||
+ reason == (GameOverReason)NewModEndReasons.DoubleAgentWin)
+ {
+ return false;
+ }
+
+ return base.DidWin(reason);
+ }
public int _kills;
public static byte _championId;
public static bool ApexThroneReady;
@@ -111,7 +128,7 @@ public static void OnAfterMurderEvent(AfterMurderEvent evt)
{
if (!Utils.IsRoleActive("Tyrant")) return;
- var tyrant = evt.Source.Data.Role as Tyrant;
+ if (evt.Source.Data.Role is not Tyrant tyrant) return;
tyrant._kills++;
diff --git a/NewMod/Utilities/CoroutinesHelper.cs b/NewMod/Utilities/CoroutinesHelper.cs
index 23f1be4..b7433c3 100644
--- a/NewMod/Utilities/CoroutinesHelper.cs
+++ b/NewMod/Utilities/CoroutinesHelper.cs
@@ -9,6 +9,7 @@
using NewMod.Roles.NeutralRoles;
using Reactor.Utilities.Extensions;
using NewMod.Components.ScreenEffects;
+using AmongUs.GameOptions;
namespace NewMod.Utilities
{
@@ -296,7 +297,7 @@ public static IEnumerator CoReviveAndKill(PlayerControl target)
{
revivedParentId = deadBody.ParentId;
- Utils.RpcRevive(deadBody);
+ Utils.HandleRevive(target, deadBody.ParentId, RoleTypes.Crewmate, deadBody.transform.position.x, deadBody.transform.position.y);
yield return new WaitForSeconds(0.5f);
diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs
index 8aead77..3cec4ab 100644
--- a/NewMod/Utilities/Utils.cs
+++ b/NewMod/Utilities/Utils.cs
@@ -102,7 +102,7 @@ public static PlayerControl PlayerById(byte id)
/// The player who was killed.
public static void RecordOnKill(PlayerControl killer, PlayerControl victim)
{
- if (PlayerKiller.ContainsKey(killer))
+ if (PlayerKiller.ContainsKey(victim))
{
PlayerKiller[victim] = killer;
}
@@ -111,6 +111,10 @@ public static void RecordOnKill(PlayerControl killer, PlayerControl victim)
PlayerKiller.Add(victim, killer);
}
}
+ public static void ResetKillTracking()
+ {
+ PlayerKiller.Clear();
+ }
///
/// Retrieves the killer of the specified victim.
@@ -153,57 +157,6 @@ public static DeadBody GetClosestBody()
return closestBody;
}
- // Inspired By : https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/CrewmateRoles/AltruistMod/Coroutine.cs#L57
- public static void Revive(DeadBody body)
- {
- if (body == null) return;
-
- var parentId = body.ParentId;
- var player = PlayerById(parentId);
-
- if (player != null)
- {
- foreach (var deadBody in GameObject.FindObjectsOfType())
- {
- if (deadBody.ParentId == body.ParentId)
- Object.Destroy(deadBody.gameObject);
- }
- player.Revive();
-
- if (player.Data.Role is NoisemakerRole role)
- {
- Object.Destroy(role.deathArrowPrefab.gameObject);
- }
- player.RpcSetRole(RoleTypes.Impostor, true);
- }
- }
-
- // Inspired By : https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/CrewmateRoles/AltruistMod/Coroutine.cs#L57
- public static void ReviveV2(DeadBody body)
- {
- if (body == null) return;
-
- var parentId = body.ParentId;
- var player = PlayerById(parentId);
-
- if (player != null)
- {
- foreach (var deadBody in GameObject.FindObjectsOfType())
- {
- if (deadBody.ParentId == body.ParentId)
- Object.Destroy(deadBody.gameObject);
- }
- player.Revive();
-
- if (player.Data.Role is NoisemakerRole role)
- {
- Object.Destroy(role.deathArrowPrefab.gameObject);
- }
- player.RpcSetRole((RoleTypes)RoleId.Get(), true);
- NewMod.Instance.Log.LogError($"---------------^^^^^^SETTING ROLE TO REVENANT-------------^^^^ NEW ROLE: {player.Data.Role.NiceName}");
- }
- }
-
// Thanks to: https://github.com/Rabek009/MoreGamemodes/blob/master/Modules/Utils.cs#L66
///
/// Checks if a particular system type is active on the current map.
@@ -423,38 +376,71 @@ public static void ResetInjections()
{
InjectedPlayerIds.Clear();
}
- ///
- /// Sends an RPC to revive a player from a dead body.
- ///
- /// The DeadBody instance to revive from.
- public static void RpcRevive(DeadBody body)
- {
- Revive(body);
- var writer = AmongUsClient.Instance.StartRpcImmediately(
- PlayerControl.LocalPlayer.NetId,
- (byte)CustomRPC.Revive,
- SendOption.Reliable
- );
- writer.Write(PlayerControl.LocalPlayer.PlayerId);
- writer.Write(body.ParentId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- }
+ // Inspired By: https://github.com/AU-Avengers/TOU-Mira/blob/dev/TownOfUs/Modules/ReviveUtilities.cs#L40
- ///
- /// Sends an RPC to revive a player from a dead body and set their role to Revenant.
- ///
- /// The DeadBody instance to revive from.
- public static void RpcReviveV2(DeadBody body)
+ [MethodRpc((uint)CustomRPC.HandleRevive)]
+ public static IEnumerator HandleRevive(PlayerControl source, byte revivedId, RoleTypes roleToSet, float reviveX, float reviveY)
{
- ReviveV2(body);
- var writer = AmongUsClient.Instance.StartRpcImmediately(
- PlayerControl.LocalPlayer.NetId,
- (byte)CustomRPC.Revive,
- SendOption.Reliable
- );
- writer.Write(PlayerControl.LocalPlayer.PlayerId);
- writer.Write(body.ParentId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
+ var revived = PlayerById(revivedId);
+
+ if (revived.Data.Disconnected)
+ yield break;
+
+ yield return new WaitForSeconds(0.15f);
+
+ if (revived.Data.Disconnected || !revived.Data.IsDead)
+ yield break;
+
+ var revivePos = new Vector2(reviveX, reviveY);
+ var inMeetingOrExile = MeetingHud.Instance || ExileController.Instance;
+
+ if (revived.Data.Role is NoisemakerRole noisemaker && noisemaker.deathArrowPrefab != null)
+ {
+ Object.Destroy(noisemaker.deathArrowPrefab.gameObject);
+ }
+
+ revived.Revive();
+ revived.RemainingEmergencies = 0;
+
+ if (AmongUsClient.Instance.AmHost)
+ {
+ revived.RpcSetRole(roleToSet, true);
+ }
+
+ if (!inMeetingOrExile)
+ {
+ revived.transform.position = revivePos;
+ revived.MyPhysics.body.position = revivePos;
+ Physics2D.SyncTransforms();
+
+ if (revived.AmOwner)
+ {
+ revived.NetTransform.RpcSnapTo(revivePos);
+ }
+ }
+
+ foreach (var deadBody in Object.FindObjectsOfType())
+ {
+ if (deadBody.ParentId == revived.PlayerId)
+ {
+ Object.Destroy(deadBody.gameObject);
+ }
+ }
+
+ float elapsed = 0f;
+ while (elapsed < 1f)
+ {
+ foreach (var deadBody in Object.FindObjectsOfType())
+ {
+ if (deadBody.ParentId == revived.PlayerId)
+ {
+ Object.Destroy(deadBody.gameObject);
+ }
+ }
+
+ elapsed += 0.05f;
+ yield return new WaitForSeconds(0.05f);
+ }
}
// Thanks to: https://github.com/yanpla/yanplaRoles/blob/master/Utils.cs#L55
@@ -878,8 +864,8 @@ public static IEnumerator StartFeignDeath(PlayerControl player)
yield break;
}
}
- RpcReviveV2(body);
- player.transform.position = body.transform.position;
+ HandleRevive(player, player.PlayerId, (RoleTypes)RoleId.Get(), body.transform.position.x, body.transform.position.y);
+ yield return new WaitForSeconds(0.2f);
player.RpcShapeshift(GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected), false);
Coroutines.Start(CoroutinesHelper.CoNotify("You have been revived in a new body!"));
Revenant.HasUsedFeignDeath = true;
diff --git a/NewMod/Utilities/WraithCallerUtilities.cs b/NewMod/Utilities/WraithCallerUtilities.cs
index 8323c7c..3f8123e 100644
--- a/NewMod/Utilities/WraithCallerUtilities.cs
+++ b/NewMod/Utilities/WraithCallerUtilities.cs
@@ -1,88 +1,106 @@
- using System.Collections.Generic;
- using NewMod.Components;
- using Reactor.Networking.Attributes;
- using UnityEngine;
+using System.Collections.Generic;
+using System.Linq;
+using NewMod.Components;
+using Reactor.Networking.Attributes;
+using UnityEngine;
- namespace NewMod.Utilities
+namespace NewMod.Utilities
+{
+ public static class WraithCallerUtilities
{
- public static class WraithCallerUtilities
+ ///
+ /// A dictionary holding the number of NPCs sent by each Wraith Caller.
+ ///
+ private static readonly Dictionary Sent = [];
+
+ ///
+ /// A dictionary holding the number of kills achieved by NPCs summoned by each Wraith Caller.
+ ///
+ private static readonly Dictionary Kills = [];
+
+ ///
+ /// Returns the number of NPCs sent by the specified owner.
+ ///
+ public static int GetSentNPC(byte ownerId)
{
- ///
- /// A dictionary holding the number of NPCs sent by each Wraith Caller.
- ///
- private static readonly Dictionary Sent = [];
+ return Sent.TryGetValue(ownerId, out var v) ? v : 0;
+ }
- ///
- /// A dictionary holding the number of kills achieved by NPCs summoned by each Wraith Caller.
- ///
- private static readonly Dictionary Kills = [];
+ ///
+ /// Returns the number of successful kills credited to the owner’s wraiths.
+ ///
+ public static int GetKillsNPC(byte ownerId)
+ {
+ return Kills.TryGetValue(ownerId, out var v) ? v : 0;
+ }
- ///
- /// Returns the number of NPCs sent by the specified owner.
- ///
- public static int GetSentNPC(byte ownerId)
- {
- return Sent.TryGetValue(ownerId, out var v) ? v : 0;
- }
+ ///
+ /// Increments the number of NPCs sent by the owner.
+ ///
+ public static void AddSentNPC(byte ownerId, int amount = 1)
+ {
+ Sent[ownerId] = GetSentNPC(ownerId) + amount;
+ }
- ///
- /// Returns the number of successful kills credited to the owner’s wraiths.
- ///
- public static int GetKillsNPC(byte ownerId)
- {
- return Kills.TryGetValue(ownerId, out var v) ? v : 0;
- }
+ ///
+ /// Increments the number of kills credited to the owner’s wraiths.
+ ///
+ public static void AddKillNPC(byte ownerId, int amount = 1)
+ {
+ Kills[ownerId] = GetKillsNPC(ownerId) + amount;
+ }
- ///
- /// Increments the number of NPCs sent by the owner.
- ///
- public static void AddSentNPC(byte ownerId, int amount = 1)
- {
- Sent[ownerId] = GetSentNPC(ownerId) + amount;
- }
+ ///
+ /// Clears all counters for both NPCs sent and kills achieved.
+ ///
+ public static void ClearAll()
+ {
+ Sent.Clear();
+ Kills.Clear();
+ }
+ [MethodRpc((uint)CustomRPC.RequestSummon)]
+ public static void RpcRequestSummonNPC(PlayerControl source, PlayerControl target)
+ {
+ if (!AmongUsClient.Instance.AmHost) return;
- ///
- /// Increments the number of kills credited to the owner’s wraiths.
- ///
- public static void AddKillNPC(byte ownerId, int amount = 1)
- {
- Kills[ownerId] = GetKillsNPC(ownerId) + amount;
- }
+ var npcId = HostNPC(source);
+ RpcSummonNPC(source, target, npcId);
+ }
+ public static byte HostNPC(PlayerControl source)
+ {
+ var prefab = AmongUsClient.Instance.PlayerPrefab;
+ var npc = Object.Instantiate(prefab);
+ npc.PlayerId = (byte)GameData.Instance.GetAvailableId();
- ///
- /// Clears all counters for both NPCs sent and kills achieved.
- ///
- public static void ClearAll()
- {
- Sent.Clear();
- Kills.Clear();
- }
+ npc.isDummy = false;
+ npc.notRealPlayer = true;
- ///
- /// RPC for . Runs on all clients to keep state in sync.
- ///
- /// The Wraith Caller owner who summoned the NPC.
- /// The intended target player.
+ var host = AmongUsClient.Instance.GetHost();
+ var npcInfo = GameData.Instance.AddPlayer(npc, host);
- [MethodRpc((uint)CustomRPC.SummonNPC)]
- public static void RpcSummonNPC(PlayerControl source, PlayerControl target)
- {
- AddSentNPC(source.PlayerId);
- SummonNPC(source, target);
- }
+ AmongUsClient.Instance.Spawn(npcInfo);
+ AmongUsClient.Instance.Spawn(npc);
- ///
- /// Spawns and initializes the wraith NPC that will hunt the target.
- /// Runs locally on each client after the RPC dispatch.
- ///
- /// The Wraith Caller (owner) who summoned the NPC.
- /// The intended target player.
- public static void SummonNPC(PlayerControl wraith, PlayerControl target)
- {
- var npcObj = new GameObject("WraithNPC_Holder");
- var wraithNpc = npcObj.AddComponent();
+ npc.NetTransform.RpcSnapTo(source.transform.position);
+
+ return npc.PlayerId;
+ }
+ [MethodRpc((uint)CustomRPC.SummonNPC)]
+ public static void RpcSummonNPC(PlayerControl source, PlayerControl target, byte npcId)
+ {
+ AddSentNPC(source.PlayerId);
- wraithNpc.Initialize(wraith, target);
+ InitializeNPC(source, target, Utils.PlayerById(npcId));
+ return;
+
+ }
+ public static void InitializeNPC(PlayerControl owner, PlayerControl target, PlayerControl npc)
+ {
+ if (!npc.gameObject.TryGetComponent(out _))
+ {
+ var comp = npc.gameObject.AddComponent();
+ comp.Initialize(owner, target, npc);
}
}
}
+}