mirror of
https://github.com/Gurge44/EndlessHostRoles.git
synced 2026-10-20 01:46:25 +00:00
380 lines
12 KiB
C#
380 lines
12 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using AmongUs.GameOptions;
|
|
using EHR.Crewmate;
|
|
using EHR.Modules;
|
|
using EHR.Neutral;
|
|
using Hazel;
|
|
using UnityEngine;
|
|
using static EHR.Translator;
|
|
|
|
namespace EHR.Impostor;
|
|
|
|
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;
|
|
public 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;
|
|
public int bulletCount;
|
|
public bool IsAim;
|
|
private Vector3 LastPosition;
|
|
private List<byte> shotNotify = [];
|
|
private Vector3 snipeBasePosition;
|
|
|
|
public byte snipeTarget;
|
|
public override bool IsEnable => On;
|
|
|
|
public static 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 static bool IsThisRole(byte playerId) => PlayerIdList.Contains(playerId);
|
|
|
|
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();
|
|
var 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;
|
|
}
|
|
|
|
Dictionary<PlayerControl, float> GetSnipeTargets(PlayerControl sniper)
|
|
{
|
|
var targets = new Dictionary<PlayerControl, float>();
|
|
var snipeBasePos = snipeBasePosition;
|
|
var snipePos = sniper.transform.position;
|
|
var dir = (snipePos - snipeBasePos).normalized;
|
|
|
|
snipePos -= dir;
|
|
|
|
foreach (PlayerControl target in Main.AllAlivePlayerControls)
|
|
{
|
|
if (target.PlayerId == sniper.PlayerId) continue;
|
|
var target_pos = target.transform.position - snipePos;
|
|
if (target_pos.magnitude < 1) continue;
|
|
var target_dir = target_pos.normalized;
|
|
var target_dot = Vector3.Dot(dir, target_dir);
|
|
if (target_dot < 0.995) continue;
|
|
if (precisionShooting)
|
|
{
|
|
var err = Vector3.Cross(dir, target_pos).magnitude;
|
|
if (err < 0.5)
|
|
{
|
|
targets.Add(target, err);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var err = target_pos.magnitude;
|
|
targets.Add(target, err);
|
|
}
|
|
}
|
|
|
|
return targets;
|
|
}
|
|
|
|
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
|
|
{
|
|
return Options.UseUnshiftTrigger.GetBool() ? Snipe(shapeshifter, !IsAim) : 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;
|
|
}
|
|
|
|
bool Snipe(PlayerControl sniper, bool shapeshifting, bool isPet = false)
|
|
{
|
|
if (!IsThisRole(sniper.PlayerId) || !sniper.IsAlive()) return true;
|
|
|
|
var sniperId = sniper.PlayerId;
|
|
|
|
if (bulletCount <= 0)
|
|
{
|
|
float CD = ShapeshiftDuration.GetFloat() + 1f;
|
|
if (Main.KillTimers[sniper.PlayerId] < CD && !isPet) sniper.SetKillCooldown(time: 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");
|
|
|
|
var targets = GetSnipeTargets(sniper);
|
|
|
|
if (targets.Count > 0)
|
|
{
|
|
var snipedTarget = targets.MinBy(c => c.Value).Key;
|
|
snipeTarget = snipedTarget.PlayerId;
|
|
snipedTarget.CheckMurder(snipedTarget);
|
|
sniper.SetKillCooldown();
|
|
snipeTarget = 0x7F;
|
|
|
|
targets.Remove(snipedTarget);
|
|
var snList = shotNotify;
|
|
snList.Clear();
|
|
foreach (var otherPc in targets.Keys)
|
|
{
|
|
snList.Add(otherPc.PlayerId);
|
|
Utils.NotifyRoles(SpecifySeer: otherPc);
|
|
}
|
|
|
|
SendRPC(sniperId);
|
|
LateTask.New(() =>
|
|
{
|
|
snList.Clear();
|
|
foreach (var 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.DefaultKillCooldown : 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 (!IsThisRole(sniper.PlayerId) || !sniper.IsAlive()) return;
|
|
|
|
if (!AimAssist) return;
|
|
|
|
if (!IsAim) return;
|
|
|
|
if (!GameStates.IsInTask)
|
|
{
|
|
IsAim = false;
|
|
AimTime = 0f;
|
|
return;
|
|
}
|
|
|
|
var 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;
|
|
}
|
|
|
|
public static bool TryGetSniper(byte targetId, ref PlayerControl sniper)
|
|
{
|
|
foreach (var 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 && IsThisRole(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;
|
|
var 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())
|
|
{
|
|
hud.PetButton.OverrideText(GetString(bulletCount <= 0 ? "DefaultShapeshiftText" : "SniperSnipeButtonText"));
|
|
}
|
|
else
|
|
{
|
|
if (IsThisRole(id))
|
|
hud.AbilityButton.SetUsesRemaining(bulletCount);
|
|
hud.AbilityButton.OverrideText(GetString(bulletCount <= 0 ? "DefaultShapeshiftText" : "SniperSnipeButtonText"));
|
|
}
|
|
}
|
|
} |