mirror of
https://github.com/Gurge44/EndlessHostRoles.git
synced 2026-10-19 15:51:58 +00:00
379 lines
11 KiB
C#
379 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using AmongUs.GameOptions;
|
|
using EHR.Modules;
|
|
using Hazel;
|
|
using UnityEngine;
|
|
using static EHR.Translator;
|
|
|
|
#if IL2CPP
|
|
using System.Linq;
|
|
#endif
|
|
|
|
namespace EHR.Roles;
|
|
|
|
public class Sniper : RoleBase
|
|
{
|
|
private const int Id = 1900;
|
|
private static List<byte> PlayerIdList = [];
|
|
|
|
private static OptionItem SniperBulletCount;
|
|
private static OptionItem SniperPrecisionShooting;
|
|
private static OptionItem SniperAimAssist;
|
|
private static OptionItem SniperAimAssistOnshot;
|
|
private static OptionItem ShapeshiftDuration;
|
|
public static OptionItem CanKillWithBullets;
|
|
|
|
private static bool MeetingReset;
|
|
private static int MaxBulletCount;
|
|
private static bool PrecisionShooting;
|
|
private static bool AimAssist;
|
|
private static bool AimAssistOneshot;
|
|
|
|
public static bool On;
|
|
private float AimTime;
|
|
private int bulletCount;
|
|
public bool IsAim;
|
|
private Vector3 LastPosition;
|
|
private Vector3 snipeBasePosition;
|
|
private List<byte> shotNotify = [];
|
|
private readonly Dictionary<PlayerControl, float> Targets = [];
|
|
|
|
private byte snipeTarget;
|
|
public override bool IsEnable => On;
|
|
|
|
public override void SetupCustomOption()
|
|
{
|
|
Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Sniper);
|
|
|
|
SniperBulletCount = new IntegerOptionItem(Id + 10, "SniperBulletCount", new(1, 10, 1), 2, TabGroup.ImpostorRoles)
|
|
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper])
|
|
.SetValueFormat(OptionFormat.Pieces);
|
|
|
|
SniperPrecisionShooting = new BooleanOptionItem(Id + 11, "SniperPrecisionShooting", false, TabGroup.ImpostorRoles)
|
|
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]);
|
|
|
|
SniperAimAssist = new BooleanOptionItem(Id + 12, "SniperAimAssist", true, TabGroup.ImpostorRoles)
|
|
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]);
|
|
|
|
SniperAimAssistOnshot = new BooleanOptionItem(Id + 13, "SniperAimAssistOneshot", false, TabGroup.ImpostorRoles)
|
|
.SetParent(SniperAimAssist);
|
|
|
|
CanKillWithBullets = new BooleanOptionItem(Id + 14, "SniperCanKill", true, TabGroup.ImpostorRoles)
|
|
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]);
|
|
|
|
ShapeshiftDuration = new FloatOptionItem(Id + 15, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles)
|
|
.SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper])
|
|
.SetValueFormat(OptionFormat.Seconds);
|
|
}
|
|
|
|
public override void Init()
|
|
{
|
|
Logger.Disable("Sniper");
|
|
|
|
PlayerIdList = [];
|
|
On = false;
|
|
|
|
snipeBasePosition = new();
|
|
LastPosition = new();
|
|
snipeTarget = 0x7F;
|
|
bulletCount = SniperBulletCount.GetInt();
|
|
shotNotify = [];
|
|
IsAim = false;
|
|
AimTime = 0f;
|
|
MeetingReset = false;
|
|
}
|
|
|
|
public override void Add(byte playerId)
|
|
{
|
|
PlayerIdList.Add(playerId);
|
|
On = true;
|
|
|
|
MaxBulletCount = SniperBulletCount.GetInt();
|
|
PrecisionShooting = SniperPrecisionShooting.GetBool();
|
|
AimAssist = SniperAimAssist.GetBool();
|
|
AimAssistOneshot = SniperAimAssistOnshot.GetBool();
|
|
|
|
snipeBasePosition = new();
|
|
LastPosition = new();
|
|
snipeTarget = 0x7F;
|
|
bulletCount = MaxBulletCount;
|
|
shotNotify = [];
|
|
IsAim = false;
|
|
AimTime = 0f;
|
|
MeetingReset = false;
|
|
}
|
|
|
|
public override void Remove(byte playerId)
|
|
{
|
|
PlayerIdList.Remove(playerId);
|
|
}
|
|
|
|
private static bool IsSniper(byte playerId)
|
|
{
|
|
return PlayerIdList.Contains(playerId);
|
|
}
|
|
|
|
private void SendRPC(byte sniperId)
|
|
{
|
|
if (!On || !Utils.DoRPC) return;
|
|
|
|
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SniperSync, SendOption.Reliable);
|
|
writer.Write(sniperId);
|
|
writer.Write(shotNotify.Count);
|
|
foreach (byte sn in shotNotify) writer.Write(sn);
|
|
|
|
AmongUsClient.Instance.FinishRpcImmediately(writer);
|
|
}
|
|
|
|
public void ReceiveRPC(MessageReader msg)
|
|
{
|
|
shotNotify.Clear();
|
|
int count = msg.ReadInt32();
|
|
|
|
while (count > 0)
|
|
{
|
|
shotNotify.Add(msg.ReadByte());
|
|
count--;
|
|
}
|
|
}
|
|
|
|
public override bool CanUseKillButton(PlayerControl pc)
|
|
{
|
|
if (!pc.IsAlive()) return false;
|
|
|
|
var canUse = false;
|
|
if (pc.IsShifted()) return false;
|
|
|
|
if (bulletCount <= 0) canUse = true;
|
|
|
|
if (CanKillWithBullets.GetBool()) canUse = true;
|
|
|
|
return canUse;
|
|
}
|
|
|
|
private Dictionary<PlayerControl, float> GetSnipeTargets(PlayerControl sniper)
|
|
{
|
|
Targets.Clear();
|
|
Vector3 snipeBasePos = snipeBasePosition;
|
|
Vector3 snipePos = sniper.transform.position;
|
|
Vector3 dir = (snipePos - snipeBasePos).normalized;
|
|
|
|
snipePos -= dir;
|
|
|
|
foreach (PlayerControl target in Main.CachedAlivePlayerControls())
|
|
{
|
|
if (target.PlayerId == sniper.PlayerId) continue;
|
|
|
|
Vector3 targetPos = target.transform.position - snipePos;
|
|
if (targetPos.magnitude < 1) continue;
|
|
|
|
Vector3 targetDir = targetPos.normalized;
|
|
float targetDot = Vector3.Dot(dir, targetDir);
|
|
if (targetDot < 0.995) continue;
|
|
|
|
if (PrecisionShooting)
|
|
{
|
|
float err = Vector3.Cross(dir, targetPos).magnitude;
|
|
if (err < 0.5) Targets[target] = err;
|
|
}
|
|
else
|
|
{
|
|
float err = targetPos.magnitude;
|
|
Targets[target] = err;
|
|
}
|
|
}
|
|
|
|
return Targets;
|
|
}
|
|
|
|
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
|
|
{
|
|
return Snipe(shapeshifter, shapeshifting);
|
|
}
|
|
|
|
public override void OnPet(PlayerControl pc)
|
|
{
|
|
Snipe(pc, !IsAim, true);
|
|
}
|
|
|
|
public override bool OnVanish(PlayerControl pc)
|
|
{
|
|
Snipe(pc, !IsAim, true);
|
|
return false;
|
|
}
|
|
|
|
private bool Snipe(PlayerControl sniper, bool shapeshifting, bool isPet = false)
|
|
{
|
|
if (!IsSniper(sniper.PlayerId) || !sniper.IsAlive()) return true;
|
|
|
|
byte sniperId = sniper.PlayerId;
|
|
|
|
if (bulletCount <= 0)
|
|
{
|
|
float cd = ShapeshiftDuration.GetFloat() + 1f;
|
|
if (Main.KillTimers[sniper.PlayerId] < cd && !isPet) sniper.SetKillCooldown(cd);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (shapeshifting)
|
|
{
|
|
MeetingReset = false;
|
|
|
|
snipeBasePosition = sniper.transform.position;
|
|
|
|
LastPosition = sniper.transform.position;
|
|
IsAim = true;
|
|
AimTime = 0f;
|
|
|
|
return false;
|
|
}
|
|
|
|
IsAim = false;
|
|
AimTime = 0f;
|
|
|
|
if (MeetingReset)
|
|
{
|
|
MeetingReset = false;
|
|
return false;
|
|
}
|
|
|
|
bulletCount--;
|
|
|
|
if (!AmongUsClient.Instance.AmHost || Pelican.IsEaten(sniperId) || Medic.ProtectList.Contains(sniperId)) return false;
|
|
|
|
sniper.RPCPlayCustomSound("AWP");
|
|
|
|
Dictionary<PlayerControl, float> targets = GetSnipeTargets(sniper);
|
|
|
|
if (targets.Count > 0)
|
|
{
|
|
PlayerControl snipedTarget = targets.MinBy(c => c.Value).Key;
|
|
snipeTarget = snipedTarget.PlayerId;
|
|
snipedTarget.CheckMurder(snipedTarget);
|
|
sniper.SetKillCooldown();
|
|
snipeTarget = 0x7F;
|
|
|
|
targets.Remove(snipedTarget);
|
|
List<byte> snList = shotNotify;
|
|
snList.Clear();
|
|
|
|
foreach (PlayerControl otherPc in targets.Keys)
|
|
{
|
|
snList.Add(otherPc.PlayerId);
|
|
Utils.NotifyRoles(SpecifySeer: otherPc);
|
|
}
|
|
|
|
SendRPC(sniperId);
|
|
|
|
LateTask.New(() =>
|
|
{
|
|
snList.Clear();
|
|
foreach (PlayerControl otherPc in targets.Keys) Utils.NotifyRoles(SpecifySeer: otherPc);
|
|
|
|
SendRPC(sniperId);
|
|
}, 0.5f, "Sniper shot Notify");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
|
|
{
|
|
if (Options.UsePhantomBasis.GetBool())
|
|
AURoleOptions.PhantomCooldown = bulletCount > 0 ? Options.AdjustedDefaultKillCooldown : 255f;
|
|
else
|
|
{
|
|
if (Options.UsePets.GetBool()) return;
|
|
|
|
try
|
|
{
|
|
if (bulletCount > 0)
|
|
AURoleOptions.ShapeshifterDuration = ShapeshiftDuration.GetFloat();
|
|
else
|
|
{
|
|
AURoleOptions.ShapeshifterDuration = 1f;
|
|
AURoleOptions.ShapeshifterCooldown = 255f;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
public override void OnFixedUpdate(PlayerControl sniper)
|
|
{
|
|
if (!AimAssist || !IsAim || !GameStates.IsInTask) return;
|
|
if (!sniper.IsAlive()) return;
|
|
|
|
Vector3 pos = sniper.transform.position;
|
|
|
|
if (pos != LastPosition)
|
|
{
|
|
AimTime = 0f;
|
|
LastPosition = pos;
|
|
}
|
|
else
|
|
{
|
|
AimTime += Time.fixedDeltaTime;
|
|
Utils.NotifyRoles(SpecifySeer: sniper, SpecifyTarget: sniper);
|
|
}
|
|
}
|
|
|
|
public override void OnReportDeadBody()
|
|
{
|
|
MeetingReset = true;
|
|
IsAim = false;
|
|
AimTime = 0f;
|
|
}
|
|
|
|
public static bool TryGetSniper(byte targetId, ref PlayerControl sniper)
|
|
{
|
|
foreach (KeyValuePair<byte, PlayerState> state in Main.PlayerStates)
|
|
{
|
|
if (state.Value.Role is Sniper { IsEnable: true } sp)
|
|
{
|
|
if (sp.snipeTarget == targetId)
|
|
{
|
|
sniper = Utils.GetPlayerById(state.Key);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static string GetShotNotify(byte seerId)
|
|
{
|
|
if (AimAssist && IsSniper(seerId))
|
|
{
|
|
if (Main.PlayerStates[seerId].Role is not Sniper sp) return string.Empty;
|
|
|
|
if (0.5f < sp.AimTime && (!AimAssistOneshot || sp.AimTime < 1.0f))
|
|
{
|
|
if (sp.GetSnipeTargets(Utils.GetPlayerById(seerId)).Count > 0)
|
|
return "<size=200%>" + Utils.ColorString(Palette.ImpostorRed, "◎") + "</size>";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (byte sniperId in PlayerIdList)
|
|
{
|
|
if (Main.PlayerStates[sniperId].Role is not Sniper sp) continue;
|
|
|
|
List<byte> snList = sp.shotNotify;
|
|
if (snList.Count > 0 && snList.Contains(seerId)) return "<size=200%>" + Utils.ColorString(Palette.ImpostorRed, "!") + "</size>";
|
|
}
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
public override void SetButtonTexts(HudManager hud, byte id)
|
|
{
|
|
if (Options.UsePets.GetBool() && !Options.UsePhantomBasis.GetBool())
|
|
hud.PetButton.OverrideText(GetString(bulletCount <= 0 ? "DefaultShapeshiftText" : "SniperSnipeButtonText"));
|
|
else
|
|
hud.AbilityButton.OverrideText(GetString(bulletCount <= 0 ? "DefaultShapeshiftText" : "SniperSnipeButtonText"));
|
|
}
|
|
} |