mirror of
https://github.com/Gurge44/EndlessHostRoles.git
synced 2026-10-20 01:46:25 +00:00
Add Ambusher
This commit is contained in:
1 parent
5cda61610c
commit
0995d7c966
15 files changed
+358
-84
No files matched your search
@@ -20,6 +20,7 @@ public enum CustomRoles
|
||||
// Impostors
|
||||
|
||||
Abyssbringer,
|
||||
Ambusher,
|
||||
Anonymous,
|
||||
AntiAdminer,
|
||||
Sans, // Arrogance
|
||||
|
||||
+15
-2
@@ -735,7 +735,7 @@ public static class BedWars
|
||||
|
||||
if (Health <= 0 && pc != null && pc.IsAlive())
|
||||
{
|
||||
killer.KillFlash();
|
||||
if (killer != null) killer.KillFlash();
|
||||
if (Main.GM.Value && AmongUsClient.Instance.AmHost) PlayerControl.LocalPlayer.KillFlash();
|
||||
ChatCommands.Spectators.ToValidPlayers().Do(x => x.KillFlash());
|
||||
|
||||
@@ -1833,7 +1833,20 @@ public static class BedWars
|
||||
break;
|
||||
}
|
||||
|
||||
if (didDamage) bed.UpdateStatus();
|
||||
if (didDamage)
|
||||
{
|
||||
foreach (byte id in bed.Breaking)
|
||||
{
|
||||
var pc = id.GetPlayer();
|
||||
if (pc == null) continue;
|
||||
|
||||
NameNotifyManager.Notifies.Remove(pc.PlayerId);
|
||||
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
|
||||
}
|
||||
|
||||
bed.Breaking.Clear();
|
||||
bed.UpdateStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -210,7 +210,7 @@ internal static class SoloPVP
|
||||
|
||||
if (!target.SoloAlive())
|
||||
{
|
||||
OnPlayerDead(target);
|
||||
Main.Instance.StartCoroutine(OnPlayerDead(target));
|
||||
OnPlayerKill(killer);
|
||||
}
|
||||
|
||||
@@ -238,10 +238,12 @@ internal static class SoloPVP
|
||||
Utils.NotifyRoles(SpecifyTarget: pc, SendOption: SendOption.None);
|
||||
}
|
||||
|
||||
private static void OnPlayerDead(PlayerControl target)
|
||||
private static System.Collections.IEnumerator OnPlayerDead(PlayerControl target)
|
||||
{
|
||||
target.ExileTemporarily();
|
||||
BackCountdown.TryAdd(target.PlayerId, KB_ResurrectionWaitingTime.GetInt());
|
||||
if (target.inVent || target.MyPhysics.Animations.IsPlayingEnterVentAnimation()) LateTask.New(() => target.MyPhysics.RpcExitVent(target.GetClosestVent().Id), 0.6f, log: false);
|
||||
while (target.inVent || target.inMovingPlat || target.onLadder || target.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || target.MyPhysics.Animations.IsPlayingEnterVentAnimation()) yield return null;
|
||||
if (BackCountdown.ContainsKey(target.PlayerId)) target.ExileTemporarily();
|
||||
}
|
||||
|
||||
private static void OnPlayerKill(PlayerControl killer)
|
||||
|
||||
@@ -692,34 +692,11 @@ namespace EHR
|
||||
|
||||
internal sealed class ShapeshiftMenuElement : CustomNetObject
|
||||
{
|
||||
private readonly byte VisibleTo;
|
||||
|
||||
public ShapeshiftMenuElement(string display, string namePlate, byte visibleTo)
|
||||
public ShapeshiftMenuElement(byte visibleTo)
|
||||
{
|
||||
VisibleTo = visibleTo;
|
||||
CreateNetObject(string.Empty, new Vector2(0f, 0f));
|
||||
LateTask.New(() => SetData(display, namePlate), 1f, log: false);
|
||||
Main.AllPlayerControls.DoIf(x => x.PlayerId != visibleTo, Hide);
|
||||
}
|
||||
|
||||
public void SetData(string display, string namePlate)
|
||||
{
|
||||
Logger.Warn(playerControl.Data == null ? "playerControl.Data is null" : "playerControl.Data is not null", "ShapeshiftMenuElement");
|
||||
playerControl.Data.DefaultOutfit.NamePlateId = namePlate;
|
||||
playerControl.Data.PlayerName = $"{Translator.GetString(display).ToUpper()}<size=0>{display}";
|
||||
|
||||
MessageWriter writer = MessageWriter.Get(SendOption.Reliable);
|
||||
writer.StartMessage(6);
|
||||
writer.Write(AmongUsClient.Instance.GameId);
|
||||
writer.WritePacked(VisibleTo.GetPlayer().OwnerId);
|
||||
writer.StartMessage(1);
|
||||
writer.WritePacked(playerControl.Data.NetId);
|
||||
playerControl.Data.Serialize(writer, false);
|
||||
writer.EndMessage();
|
||||
writer.EndMessage();
|
||||
AmongUsClient.Instance.SendOrDisconnect(writer);
|
||||
writer.Recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,9 +108,9 @@ internal static class CustomRolesHelper
|
||||
if (role.IsVanilla()) return role;
|
||||
if (role is CustomRoles.GM) return CustomRoles.Crewmate;
|
||||
if (checkDesyncRole && role.IsDesyncRole()) return Enum.Parse<CustomRoles>(role.GetDYRole() + "EHR");
|
||||
if ((Options.UsePhantomBasis.GetBool() || role.AlwaysUsesPhantomBase()) && role.SimpleAbilityTrigger()) return CustomRoles.Phantom;
|
||||
|
||||
bool UsePets = Options.UsePets.GetBool();
|
||||
if ((Options.UsePhantomBasis.GetBool() || role.AlwaysUsesPhantomBase()) && role.SimpleAbilityTrigger() && (!UsePets || role is CustomRoles.Swooper or CustomRoles.Wraith)) return CustomRoles.Phantom;
|
||||
|
||||
return role switch
|
||||
{
|
||||
@@ -153,8 +153,9 @@ internal static class CustomRolesHelper
|
||||
CustomRoles.ClockBlocker => CustomRoles.Impostor,
|
||||
CustomRoles.Psychopath => CustomRoles.Impostor,
|
||||
CustomRoles.Postponer => CustomRoles.Impostor,
|
||||
CustomRoles.Venerer => CustomRoles.Shapeshifter,
|
||||
CustomRoles.Venerer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
|
||||
CustomRoles.Kidnapper => CustomRoles.Shapeshifter,
|
||||
CustomRoles.Ambusher => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
|
||||
CustomRoles.Stasis => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter,
|
||||
CustomRoles.Wasp => CustomRoles.Impostor,
|
||||
CustomRoles.Assumer => CustomRoles.Impostor,
|
||||
@@ -433,8 +434,9 @@ internal static class CustomRolesHelper
|
||||
|
||||
public static RoleTypes GetDYRole(this CustomRoles role, bool load = false)
|
||||
{
|
||||
if (!load && ((Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) || role.AlwaysUsesPhantomBase()) && !role.IsImpostor() && role.SimpleAbilityTrigger()) return RoleTypes.Phantom;
|
||||
|
||||
bool UsePets = !load && Options.UsePets.GetBool();
|
||||
if (!load && ((Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) || role.AlwaysUsesPhantomBase()) && !role.IsImpostor() && role.SimpleAbilityTrigger() && (!UsePets || role is CustomRoles.Swooper or CustomRoles.Wraith)) return RoleTypes.Phantom;
|
||||
|
||||
return role switch
|
||||
{
|
||||
@@ -713,6 +715,7 @@ internal static class CustomRolesHelper
|
||||
CustomRoles.Inhibitor or
|
||||
CustomRoles.Wiper or
|
||||
CustomRoles.Psychopath or
|
||||
CustomRoles.Ambusher or
|
||||
CustomRoles.Kidnapper or
|
||||
CustomRoles.Postponer or
|
||||
CustomRoles.Venerer or
|
||||
@@ -848,6 +851,8 @@ internal static class CustomRolesHelper
|
||||
|
||||
if (role.UsesPetInsteadOfKill()) return true;
|
||||
|
||||
if (Options.UsePhantomBasis.GetBool() && (!role.IsNK() || Options.UsePhantomBasisForNKs.GetBool()) && role.SimpleAbilityTrigger()) return false;
|
||||
|
||||
Type type = role.GetRoleClass().GetType();
|
||||
return type.GetMethod("OnPet")?.DeclaringType == type;
|
||||
}
|
||||
@@ -1024,6 +1029,7 @@ internal static class CustomRolesHelper
|
||||
CustomRoles.Swiftclaw or
|
||||
CustomRoles.Undertaker or
|
||||
CustomRoles.Abyssbringer or
|
||||
CustomRoles.Ambusher or
|
||||
CustomRoles.Bomber or
|
||||
CustomRoles.Nuker or
|
||||
CustomRoles.Camouflager or
|
||||
@@ -1039,9 +1045,9 @@ internal static class CustomRolesHelper
|
||||
CustomRoles.Sapper or
|
||||
CustomRoles.Sniper or
|
||||
CustomRoles.Twister or
|
||||
CustomRoles.Swooper or //!
|
||||
CustomRoles.Swooper or
|
||||
CustomRoles.Venerer or
|
||||
CustomRoles.Wraith or //!
|
||||
CustomRoles.Wraith or
|
||||
CustomRoles.RouleteGrandeur or
|
||||
CustomRoles.Enderman or
|
||||
CustomRoles.Explosivist or
|
||||
@@ -1244,7 +1250,41 @@ internal static class CustomRolesHelper
|
||||
CustomRoles.Eraser when Eraser.EraseMethod.GetValue() == 0 => true,
|
||||
CustomRoles.Silencer when Silencer.SilenceMode.GetValue() == 0 => true,
|
||||
CustomRoles.Sniper when !Sniper.CanKillWithBullets.GetBool() => true,
|
||||
CustomRoles.Augmenter or CustomRoles.BallLightning or CustomRoles.Blackmailer or CustomRoles.Cantankerous or CustomRoles.Capitalism or CustomRoles.Consort or CustomRoles.Echo or CustomRoles.EvilDiviner or CustomRoles.FireWorks or CustomRoles.Framer or CustomRoles.Gangster or CustomRoles.Generator or CustomRoles.Hangman or CustomRoles.Inhibitor or CustomRoles.Kamikaze or CustomRoles.Mastermind or CustomRoles.Morphling or CustomRoles.Ninja or CustomRoles.Nullifier or CustomRoles.Penguin or CustomRoles.Puppeteer or CustomRoles.Saboteur or CustomRoles.Sapper or CustomRoles.Scavenger or CustomRoles.Swooper or CustomRoles.Undertaker or CustomRoles.Vampire or CustomRoles.Warlock or CustomRoles.Wasp or CustomRoles.Wiper or CustomRoles.Witch or CustomRoles.YinYanger => true,
|
||||
|
||||
CustomRoles.Augmenter or
|
||||
CustomRoles.BallLightning or
|
||||
CustomRoles.Blackmailer or
|
||||
CustomRoles.Cantankerous or
|
||||
CustomRoles.Capitalism or
|
||||
CustomRoles.Consort or
|
||||
CustomRoles.Echo or
|
||||
CustomRoles.EvilDiviner or
|
||||
CustomRoles.FireWorks or
|
||||
CustomRoles.Framer or
|
||||
CustomRoles.Gangster or
|
||||
CustomRoles.Generator or
|
||||
CustomRoles.Hangman or
|
||||
CustomRoles.Inhibitor or
|
||||
CustomRoles.Kamikaze or
|
||||
CustomRoles.Mastermind or
|
||||
CustomRoles.Morphling or
|
||||
CustomRoles.Ninja or
|
||||
CustomRoles.Nullifier or
|
||||
CustomRoles.Penguin or
|
||||
CustomRoles.Postponer or
|
||||
CustomRoles.Puppeteer or
|
||||
CustomRoles.Saboteur or
|
||||
CustomRoles.Sapper or
|
||||
CustomRoles.Scavenger or
|
||||
CustomRoles.Swooper or
|
||||
CustomRoles.Undertaker or
|
||||
CustomRoles.Vampire or
|
||||
CustomRoles.Warlock or
|
||||
CustomRoles.Wasp or
|
||||
CustomRoles.Wiper or
|
||||
CustomRoles.Witch or
|
||||
CustomRoles.YinYanger => true,
|
||||
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
@@ -1621,6 +1661,7 @@ internal static class CustomRolesHelper
|
||||
CustomRoles.Ventriloquist => RoleOptionType.Impostor_Support,
|
||||
CustomRoles.Vindicator => RoleOptionType.Impostor_Support,
|
||||
CustomRoles.YinYanger => RoleOptionType.Impostor_Support,
|
||||
CustomRoles.Ambusher => RoleOptionType.Impostor_Concealing,
|
||||
CustomRoles.Anonymous => RoleOptionType.Impostor_Concealing,
|
||||
CustomRoles.Duellist => RoleOptionType.Impostor_Concealing,
|
||||
CustomRoles.Echo => RoleOptionType.Impostor_Concealing,
|
||||
|
||||
@@ -733,7 +733,7 @@ internal static class ExtendedPlayerControl
|
||||
|
||||
public static void KillFlash(this PlayerControl player)
|
||||
{
|
||||
if (GameStates.IsLobby) return;
|
||||
if (GameStates.IsLobby || player == null) return;
|
||||
|
||||
// Kill flash (blackout + reactor flash) processing
|
||||
|
||||
|
||||
+19
-26
@@ -1267,7 +1267,7 @@ public static class GuessManager
|
||||
bool TryGetDisplay(out string display)
|
||||
{
|
||||
if (!PlayerIdToRawDisplay.TryGetValue(target.PlayerId, out display)) //TODO: ANY WAY TO FIND CNOS FROM PLAYERCONTROL?
|
||||
display = ExistingCNOs.FindFirst(x => x.playerControl.name == target.name, out ShapeshiftMenuElement cno) ? cno.playerControl.tag : string.Empty;
|
||||
display = ExistingCNOs.FindFirst(x => x.playerControl.NetId == target.NetId, out ShapeshiftMenuElement cno) ? cno.playerControl.tag : string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(display))
|
||||
{
|
||||
@@ -1315,6 +1315,8 @@ public static class GuessManager
|
||||
// First, use living players to show choices by changing their names
|
||||
// The local player can't be used to show a choice (-1)
|
||||
|
||||
StringBuilder sb = new();
|
||||
|
||||
var skipped = false;
|
||||
PlayerControl guesser = GuesserId.GetPlayer();
|
||||
MessageWriter writer = MessageWriter.Get(SendOption.Reliable);
|
||||
@@ -1338,9 +1340,11 @@ public static class GuessManager
|
||||
|
||||
string originalNamePlateId = pc.Data.DefaultOutfit.NamePlateId;
|
||||
string originalPlayerName = pc.Data.DefaultOutfit.PlayerName;
|
||||
pc.Data.DefaultOutfit.NamePlateSequenceId += 10;
|
||||
pc.Data.DefaultOutfit.NamePlateId = namePlateId;
|
||||
pc.SetNamePlate(namePlateId);
|
||||
pc.Data.DefaultOutfit.NamePlateSequenceId += 10;
|
||||
pc.Data.DefaultOutfit.PlayerName = GetString(choice).ToUpper();
|
||||
sb.Append(pc.Data.DefaultOutfit.PlayerName.PadRightV2(10));
|
||||
|
||||
if (writer.Length > 500)
|
||||
{
|
||||
@@ -1357,6 +1361,13 @@ public static class GuessManager
|
||||
pc.Data.Serialize(writer, false);
|
||||
writer.EndMessage();
|
||||
|
||||
writer.StartMessage(2);
|
||||
writer.WritePacked(pc.NetId);
|
||||
writer.Write((byte)RpcCalls.SetNamePlateStr);
|
||||
writer.Write(namePlateId);
|
||||
writer.Write(pc.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr));
|
||||
writer.EndMessage();
|
||||
|
||||
pc.Data.DefaultOutfit.NamePlateId = originalNamePlateId;
|
||||
pc.Data.DefaultOutfit.PlayerName = originalPlayerName;
|
||||
}
|
||||
@@ -1369,40 +1380,22 @@ public static class GuessManager
|
||||
ExistingCNOs.Clear();
|
||||
|
||||
// If there aren't enough living players, spawn new CNOs to show the rest of choices
|
||||
|
||||
// Since CNOs use the local player's NetworkedPlayerInfo, add AU reads the player's name directly from it,
|
||||
// it's impossible to show vanilla players all choices accurately with CNOs.
|
||||
// No workaround found yet....
|
||||
// So we send the remaining choices in chat so the player can identify them
|
||||
|
||||
if (data.Length >= alivePlayerControlsLength)
|
||||
{
|
||||
string originalNamePlateId = guesser.Data.DefaultOutfit.NamePlateId;
|
||||
string originalPlayerName = guesser.Data.DefaultOutfit.PlayerName;
|
||||
|
||||
for (int i = alivePlayerControlsLength; i < data.Length; i++)
|
||||
{
|
||||
string choice = data[i].choice;
|
||||
string namePlateId = data[i].namePlateId;
|
||||
|
||||
guesser.Data.DefaultOutfit.NamePlateSequenceId += 10;
|
||||
guesser.Data.DefaultOutfit.NamePlateId = namePlateId;
|
||||
guesser.Data.DefaultOutfit.PlayerName = $"{GetString(choice).ToUpper()}<size=0>{choice}";
|
||||
|
||||
writer = MessageWriter.Get(SendOption.Reliable);
|
||||
writer.StartMessage(6);
|
||||
writer.Write(AmongUsClient.Instance.GameId);
|
||||
writer.WritePacked(guesser.OwnerId);
|
||||
writer.StartMessage(1);
|
||||
writer.WritePacked(guesser.Data.NetId);
|
||||
guesser.Data.Serialize(writer, false);
|
||||
writer.EndMessage();
|
||||
writer.EndMessage();
|
||||
AmongUsClient.Instance.SendOrDisconnect(writer);
|
||||
writer.Recycle();
|
||||
|
||||
var cno = new ShapeshiftMenuElement(choice, namePlateId, GuesserId);
|
||||
var cno = new ShapeshiftMenuElement(GuesserId);
|
||||
ExistingCNOs.Add(cno);
|
||||
}
|
||||
|
||||
guesser.Data.DefaultOutfit.NamePlateSequenceId += 10;
|
||||
guesser.Data.DefaultOutfit.NamePlateId = originalNamePlateId;
|
||||
guesser.Data.DefaultOutfit.PlayerName = originalPlayerName;
|
||||
}
|
||||
|
||||
Logger.Info($"Spawned {ExistingCNOs.Count} CNOs, Used {alivePlayerControlsLength} Living Players, Showing {data.Length} Choices", "Meeting Shapeshift For Guessing");
|
||||
|
||||
@@ -3258,6 +3258,7 @@ public static class Utils
|
||||
CustomRoles.Catcher => Catcher.AbilityCooldown.GetInt(),
|
||||
CustomRoles.Sentry => Crewmate.Sentry.ShowInfoCooldown.GetInt(),
|
||||
CustomRoles.ToiletMaster => ToiletMaster.AbilityCooldown.GetInt(),
|
||||
CustomRoles.Ambusher => Ambusher.AbilityCooldown.GetInt(),
|
||||
CustomRoles.AntiAdminer => AntiAdminer.AbilityCooldown.GetInt(),
|
||||
CustomRoles.Sniper => Options.DefaultShapeshiftCooldown.GetInt(),
|
||||
CustomRoles.Ninja => Ninja.AssassinateCooldownOpt.GetInt(),
|
||||
|
||||
@@ -351,7 +351,7 @@ internal static class CheckMurderPatch
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckMurder() => Main.PlayerStates[killer.PlayerId].Role.OnCheckMurder(killer, target) || target.Is(CustomRoles.Fragile);
|
||||
bool CheckMurder() => Main.PlayerStates[killer.PlayerId].Role.OnCheckMurder(killer, target) || target.Is(CustomRoles.Fragile) || Ambusher.FragilePlayers.ContainsKey(target.PlayerId);
|
||||
}
|
||||
|
||||
if (!killer.RpcCheckAndMurder(target, true)) return false;
|
||||
@@ -1300,6 +1300,20 @@ internal static class ReportDeadBodyPatch
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PlayerControl pc in Main.AllPlayerControls)
|
||||
{
|
||||
if (Main.CheckShapeshift.ContainsKey(pc.PlayerId) && !Doppelganger.DoppelVictim.ContainsKey(pc.PlayerId))
|
||||
Camouflage.RpcSetSkin(pc, revertToDefault: true);
|
||||
|
||||
if (Main.CurrentMap == MapNames.Fungle && (pc.IsMushroomMixupActive() || IsActive(SystemTypes.MushroomMixupSabotage)))
|
||||
pc.FixMixedUpOutfit();
|
||||
|
||||
if (Main.Invisible.Contains(pc.PlayerId))
|
||||
pc.RpcMakeVisible();
|
||||
|
||||
PhantomRolePatch.OnReportDeadBody(pc);
|
||||
}
|
||||
|
||||
EAC.TimeSinceLastTaskCompletion.Clear();
|
||||
|
||||
Enigma.OnReportDeadBody(player, target);
|
||||
@@ -1342,20 +1356,6 @@ internal static class ReportDeadBodyPatch
|
||||
|
||||
Stressed.OnMeetingStart();
|
||||
|
||||
foreach (PlayerControl pc in Main.AllPlayerControls)
|
||||
{
|
||||
if (Main.CheckShapeshift.ContainsKey(pc.PlayerId) && !Doppelganger.DoppelVictim.ContainsKey(pc.PlayerId))
|
||||
Camouflage.RpcSetSkin(pc, revertToDefault: true);
|
||||
|
||||
if (Main.CurrentMap == MapNames.Fungle && (pc.IsMushroomMixupActive() || IsActive(SystemTypes.MushroomMixupSabotage)))
|
||||
pc.FixMixedUpOutfit();
|
||||
|
||||
if (Main.Invisible.Contains(pc.PlayerId))
|
||||
pc.RpcMakeVisible();
|
||||
|
||||
PhantomRolePatch.OnReportDeadBody(pc);
|
||||
}
|
||||
|
||||
MeetingTimeManager.OnReportDeadBody();
|
||||
|
||||
NameNotifyManager.Reset();
|
||||
|
||||
@@ -9262,6 +9262,15 @@
|
||||
"Listener.FullMessageHearChance": "Full Message Hear Chance",
|
||||
"Listener.FullMessage": "{0}\\n{1} whispers to {2}:\\n{3}",
|
||||
"Listener.FromTo": "{0}\\n{1} whispers to {2}",
|
||||
"Ambusher": "Ambusher",
|
||||
"AmbusherInfo": "Ambush Players to Make Them Fragile",
|
||||
"AmbusherInfoLong": "(Impostors):\\nAs the Ambusher, use your ability to become invisible. Follow someone for a few seconds to ambush them. If you succeed, the player gets Fragile effects. Otherwise, you become visible once your invisibility duration runs out and nothing happens.",
|
||||
"Ambusher.FollowDuration": "Time Needed to Follow Someone For",
|
||||
"Ambusher.FollowRadius": "Following Radius",
|
||||
"Ambusher.InvisDurAfterSuccessfulAmbush": "Extra Invisibility Duration After Successful Ambushes",
|
||||
"Ambusher.FragileDuration": "Fragile Effect Duration",
|
||||
"Ambusher.Success": "<#00ff00>Ambush successful</color>",
|
||||
"Ambusher.InProgress": "Ambushing {0}.... {1}s left",
|
||||
"Psychopath": "Psychopath",
|
||||
"PsychopathInfo": "You're Forced to Kill Off Cooldown",
|
||||
"PsychopathInfoLong": "(Impostors):\\nAs the Psychopath, as soon as your kill cooldown is up, you'll kill instantly if someone is inside your kill-range.",
|
||||
|
||||
@@ -123,6 +123,8 @@ public class Siren : Coven
|
||||
{
|
||||
if (!Stages.TryGetValue(player.PlayerId, out var stage))
|
||||
Stages[player.PlayerId] = stage = HasNecronomicon ? 2 : 1;
|
||||
else
|
||||
Stages[player.PlayerId]++;
|
||||
|
||||
switch (stage)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using EHR.Modules;
|
||||
using Hazel;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EHR.Impostor;
|
||||
|
||||
public class Ambusher : RoleBase
|
||||
{
|
||||
public static bool On;
|
||||
|
||||
public override bool IsEnable => On;
|
||||
|
||||
public static OptionItem AbilityCooldown;
|
||||
private static OptionItem AbilityDuration;
|
||||
private static OptionItem KillCooldown;
|
||||
private static OptionItem FollowDuration;
|
||||
private static OptionItem FollowRadius;
|
||||
private static OptionItem InvisDurAfterSuccessfulAmbush;
|
||||
private static OptionItem FragileDuration;
|
||||
|
||||
private float AbilityEndTimer;
|
||||
private byte TargetId;
|
||||
private float TargetTimer;
|
||||
private bool DontCheck;
|
||||
private int Count;
|
||||
private long LastRPCTS;
|
||||
private byte AmbusherId;
|
||||
|
||||
public static Dictionary<byte, long> FragilePlayers = [];
|
||||
|
||||
public override void SetupCustomOption()
|
||||
{
|
||||
StartSetup(655200)
|
||||
.AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0.5f, 120f, 0.5f), OptionFormat.Seconds)
|
||||
.AutoSetupOption(ref AbilityDuration, 20f, new FloatValueRule(0.5f, 60f, 0.5f), OptionFormat.Seconds)
|
||||
.AutoSetupOption(ref KillCooldown, 30f, new FloatValueRule(0.5f, 120f, 0.5f), OptionFormat.Seconds)
|
||||
.AutoSetupOption(ref FollowDuration, 5f, new FloatValueRule(0.5f, 30f, 0.5f), OptionFormat.Seconds)
|
||||
.AutoSetupOption(ref FollowRadius, 1.5f, new FloatValueRule(0.1f, 10f, 0.1f), OptionFormat.Multiplier)
|
||||
.AutoSetupOption(ref InvisDurAfterSuccessfulAmbush, 5f, new FloatValueRule(0.5f, 60f, 0.5f), OptionFormat.Seconds)
|
||||
.AutoSetupOption(ref FragileDuration, 40, new IntegerValueRule(1, 300, 1), OptionFormat.Seconds);
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
On = false;
|
||||
FragilePlayers = [];
|
||||
}
|
||||
|
||||
public override void Add(byte playerId)
|
||||
{
|
||||
On = true;
|
||||
AbilityEndTimer = AbilityDuration.GetFloat();
|
||||
TargetId = byte.MaxValue;
|
||||
TargetTimer = FollowDuration.GetFloat();
|
||||
DontCheck = false;
|
||||
Count = 0;
|
||||
LastRPCTS = 0;
|
||||
AmbusherId = playerId;
|
||||
}
|
||||
|
||||
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
|
||||
{
|
||||
if (Options.UsePhantomBasis.GetBool())
|
||||
{
|
||||
AURoleOptions.PhantomCooldown = AbilityCooldown.GetFloat();
|
||||
AURoleOptions.PhantomDuration = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat();
|
||||
AURoleOptions.ShapeshifterDuration = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanUseKillButton(PlayerControl pc)
|
||||
{
|
||||
return !Main.Invisible.Contains(pc.PlayerId);
|
||||
}
|
||||
|
||||
public override void SetKillCooldown(byte id)
|
||||
{
|
||||
Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
|
||||
}
|
||||
|
||||
public override void OnMurder(PlayerControl killer, PlayerControl target)
|
||||
{
|
||||
killer.RpcResetAbilityCooldown();
|
||||
}
|
||||
|
||||
public override bool OnVanish(PlayerControl pc)
|
||||
{
|
||||
pc.RpcMakeInvisible(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
|
||||
{
|
||||
if (!shapeshifting) return true;
|
||||
shapeshifter.RpcMakeInvisible(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnPet(PlayerControl pc)
|
||||
{
|
||||
pc.RpcMakeInvisible(true);
|
||||
}
|
||||
|
||||
public override void OnFixedUpdate(PlayerControl pc)
|
||||
{
|
||||
if (!GameStates.IsInTask || !Main.IntroDestroyed || ExileController.Instance || AntiBlackout.SkipTasks || !Main.Invisible.Contains(pc.PlayerId)) return;
|
||||
|
||||
if (!DontCheck)
|
||||
{
|
||||
var pos = pc.Pos();
|
||||
var radius = FollowRadius.GetFloat();
|
||||
var closestPlayer = Main.AllAlivePlayerControls.Without(pc).Select(x => (pc: x, distance: Vector2.Distance(x.Pos(), pos))).Where(x => x.distance <= radius).MinBy(x => x.distance).pc;
|
||||
|
||||
if (closestPlayer == null)
|
||||
{
|
||||
TargetId = byte.MaxValue;
|
||||
TargetTimer = FollowDuration.GetFloat();
|
||||
}
|
||||
else if (closestPlayer.PlayerId != TargetId)
|
||||
{
|
||||
TargetId = closestPlayer.PlayerId;
|
||||
TargetTimer = FollowDuration.GetFloat();
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetTimer -= Time.fixedDeltaTime;
|
||||
|
||||
if (TargetTimer <= 0)
|
||||
{
|
||||
DontCheck = true;
|
||||
AbilityEndTimer = InvisDurAfterSuccessfulAmbush.GetFloat();
|
||||
FragilePlayers[TargetId] = Utils.TimeStamp + FragileDuration.GetInt();
|
||||
TargetId = byte.MaxValue;
|
||||
TargetTimer = FollowDuration.GetFloat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AbilityEndTimer -= Time.fixedDeltaTime;
|
||||
|
||||
if (AbilityEndTimer <= 0)
|
||||
{
|
||||
pc.SetKillCooldown(KillCooldown.GetFloat());
|
||||
pc.RpcMakeVisible(true);
|
||||
AbilityEndTimer = AbilityDuration.GetFloat();
|
||||
LastRPCTS = 0;
|
||||
Count = 30;
|
||||
DontCheck = false;
|
||||
}
|
||||
|
||||
if (LastRPCTS != Utils.TimeStamp)
|
||||
{
|
||||
pc.RpcResetAbilityCooldown();
|
||||
Utils.SendRPC(CustomRPC.SyncRoleData, AmbusherId, TargetId, TargetTimer, AbilityEndTimer, DontCheck, AmbusherId, Main.Invisible.Contains(AmbusherId));
|
||||
LastRPCTS = Utils.TimeStamp;
|
||||
}
|
||||
|
||||
if (Count++ < (DontCheck ? 30 : 10)) return;
|
||||
Count = 0;
|
||||
|
||||
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
|
||||
if (pc.AmOwner) Utils.DirtyName.Add(pc.PlayerId);
|
||||
}
|
||||
|
||||
public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad)
|
||||
{
|
||||
if (!lowLoad && FragilePlayers.TryGetValue(pc.PlayerId, out var endTS) && Utils.TimeStamp >= endTS)
|
||||
FragilePlayers.Remove(pc.PlayerId);
|
||||
}
|
||||
|
||||
public override void OnReportDeadBody()
|
||||
{
|
||||
TargetId = byte.MaxValue;
|
||||
TargetTimer = FollowDuration.GetFloat();
|
||||
AbilityEndTimer = AbilityDuration.GetFloat();
|
||||
DontCheck = false;
|
||||
Utils.SendRPC(CustomRPC.SyncRoleData, AmbusherId, TargetId, TargetTimer, AbilityEndTimer, DontCheck, AmbusherId, Main.Invisible.Contains(AmbusherId));
|
||||
}
|
||||
|
||||
public void ReceiveRPC(MessageReader reader)
|
||||
{
|
||||
TargetId = reader.ReadByte();
|
||||
TargetTimer = reader.ReadSingle();
|
||||
AbilityEndTimer = reader.ReadSingle();
|
||||
DontCheck = reader.ReadBoolean();
|
||||
byte ambusherId = reader.ReadByte();
|
||||
if (reader.ReadBoolean()) Main.Invisible.Add(ambusherId);
|
||||
else Main.Invisible.Remove(ambusherId);
|
||||
}
|
||||
|
||||
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
|
||||
{
|
||||
if (seer.PlayerId != AmbusherId || seer.PlayerId != target.PlayerId || hud || meeting || !Main.Invisible.Contains(AmbusherId)) return string.Empty;
|
||||
string str = DontCheck ? Translator.GetString("Ambusher.Success") : string.Format(Translator.GetString("Ambusher.InProgress"), TargetId.ColoredPlayerName(), Math.Round(TargetTimer, 1));
|
||||
return $"{str}\n{string.Format(Translator.GetString("Ambusher.InvisTimeLeft"), (int)Math.Round(AbilityEndTimer))}";
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public class BountyHunter : RoleBase
|
||||
|
||||
public override void OnFixedUpdate(PlayerControl player)
|
||||
{
|
||||
if (!GameStates.IsInTask || float.IsNaN(ChangeTimer)) return;
|
||||
if (!GameStates.IsInTask || float.IsNaN(ChangeTimer) || !Main.IntroDestroyed || ExileController.Instance || AntiBlackout.SkipTasks) return;
|
||||
|
||||
if (!player.IsAlive())
|
||||
ChangeTimer = float.NaN;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using EHR.Modules;
|
||||
using Hazel;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EHR.Impostor;
|
||||
@@ -15,6 +16,7 @@ public class Mercenary : RoleBase
|
||||
|
||||
private float SuicideTimer;
|
||||
private int Timer;
|
||||
private byte MercenaryId;
|
||||
|
||||
public override bool IsEnable => PlayerIdList.Count > 0;
|
||||
|
||||
@@ -37,7 +39,7 @@ public class Mercenary : RoleBase
|
||||
public override void Init()
|
||||
{
|
||||
PlayerIdList = [];
|
||||
SuicideTimer = TimeLimit.GetFloat();
|
||||
SuicideTimer = 0f;
|
||||
Timer = TimeLimit.GetInt();
|
||||
}
|
||||
|
||||
@@ -45,7 +47,8 @@ public class Mercenary : RoleBase
|
||||
{
|
||||
PlayerIdList.Add(serial);
|
||||
Timer = TimeLimit.GetInt();
|
||||
SuicideTimer = TimeLimit.GetFloat();
|
||||
SuicideTimer = 0f;
|
||||
MercenaryId = serial;
|
||||
}
|
||||
|
||||
public override void Remove(byte playerId)
|
||||
@@ -67,7 +70,7 @@ public class Mercenary : RoleBase
|
||||
{
|
||||
if (!killer.Is(CustomRoles.Mercenary)) return true;
|
||||
|
||||
SuicideTimer = float.NaN;
|
||||
SuicideTimer = 0f;
|
||||
Timer = TimeLimit.GetInt();
|
||||
killer.MarkDirtySettings();
|
||||
return true;
|
||||
@@ -81,7 +84,7 @@ public class Mercenary : RoleBase
|
||||
|
||||
public override void OnFixedUpdate(PlayerControl player)
|
||||
{
|
||||
if (!GameStates.IsInTask) return;
|
||||
if (!GameStates.IsInTask || !Main.IntroDestroyed || ExileController.Instance || AntiBlackout.SkipTasks) return;
|
||||
|
||||
if (!HasKilled(player))
|
||||
{
|
||||
@@ -106,7 +109,12 @@ public class Mercenary : RoleBase
|
||||
SuicideTimer += Time.fixedDeltaTime;
|
||||
int tempTimer = Timer;
|
||||
Timer = TimeLimit.GetInt() - (int)SuicideTimer;
|
||||
if (Timer != tempTimer && Timer <= 20 && !player.IsModdedClient()) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
|
||||
|
||||
if (Timer != tempTimer && Timer <= 20 && !player.IsModdedClient())
|
||||
{
|
||||
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
|
||||
Utils.SendRPC(CustomRPC.SyncRoleData, MercenaryId, Timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,4 +123,15 @@ public class Mercenary : RoleBase
|
||||
SuicideTimer = 0f;
|
||||
Timer = TimeLimit.GetInt();
|
||||
}
|
||||
|
||||
public void ReceiveRPC(MessageReader reader)
|
||||
{
|
||||
Timer = reader.ReadPackedInt32();
|
||||
}
|
||||
|
||||
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
|
||||
{
|
||||
if (seer.PlayerId != MercenaryId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting || Timer - 1 > 20) return string.Empty;
|
||||
return string.Format(Translator.GetString("SerialKillerTimeLeft"), Timer - 1);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,13 @@ public class Slenderman : RoleBase
|
||||
|
||||
public override void OnCheckPlayerPosition(PlayerControl pc)
|
||||
{
|
||||
if (MeetingCooldownEndTS > Utils.TimeStamp || pc.Is(CustomRoles.Slenderman) || SlendermanPC == null || !SlendermanPC.IsAlive()) return;
|
||||
if (pc.Is(CustomRoles.Slenderman)) return;
|
||||
|
||||
if (MeetingCooldownEndTS > Utils.TimeStamp || SlendermanPC == null || !SlendermanPC.IsAlive())
|
||||
{
|
||||
Blinded.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
bool inRange = Vector2.Distance(pc.Pos(), SlendermanPC.Pos()) <= BlindRange.GetFloat();
|
||||
|
||||
@@ -105,6 +111,11 @@ public class Slenderman : RoleBase
|
||||
MeetingCooldownEndTS = Utils.TimeStamp + AfterMeetingBlindCooldown.GetInt();
|
||||
}
|
||||
|
||||
public override void OnReportDeadBody()
|
||||
{
|
||||
Blinded.Clear();
|
||||
}
|
||||
|
||||
public override string GetProgressText(byte playerId, bool comms)
|
||||
{
|
||||
return base.GetProgressText(playerId, comms) + $" ({Blinded.Count})";
|
||||
|
||||
Reference in new issue
Block a user