Files
EndlessHostRoles/Modules/TargetArrow.cs
T
TommyXL 7929a4b83e Optimization (Part 1?) (#589)
* Well lets try check this

* Some changes

* Change

* Well

* Change ControllerManager.Update

* Nothing

* Optimaze more?

* Fix error build

* Fix win check

* Change

* Fix

* Optimaze some roles in OnCheckPlayerPosition

* More optimaze

* Full optimaze roles in OnGlobalFixedUpdate

* Optimaze some add-ons

* ProgressText

* Change

* Optimaze NameNotifyManager

* Optimaze NotifyRoles and some roles

* Move the displayed version

* Some changes

* Optimaze OnFixedUpdate in some roles

* Fix bugs

* Optimaze IsActive & V4 version

* Optimaze MapRoom.DoorsUpdate

* Cache TryCatch in IsActive

* Change

* Fix

* More optimze TaskPanelBehaviour

* Cache GetRoleColor & ColorString & AllStringNames & Change RemoveHtmlTags

* Cache TryCast VentilationSystem & ICriticalSabotage & IUsable and more optimaze

* Fix CountAlivePlayers

* Fix exceptios

* Replace some CachedAllPlayerControls to EnumeratePlayerControls

* Replace some CachedAlivePlayerControls to EnumerateAlivePlayerControls

* Fixes

* Fix modded clients

* Fix errors

* Fix again

* Optimaze & add null check

* Fix crash in Starlight

* Use only Main.PluginVersion in LobbySharingAPI

* Optimaze code in FixedUpdate

* Fix

* Optimize GetProgressText for Roles

* Some changes in Translations

* Cache Enum.GetValues and StringBuilder

* Changes

* CustomRole.ColoredTextByRole()

* Optimaze Lobby & Meeting

* ColorStringPrefix => ColorPrefix

* Add null check

* Remove

* Fixes & optimaze

* Fixes

* Changes

* Fix

* Fix

* Fix

* Remove

* Fix

* Change

* Revert
2026-05-15 20:01:54 +02:00

199 lines
6.4 KiB
C#

using System.Collections.Generic;
using System.Linq;
using EHR.Modules;
using Hazel;
using UnityEngine;
namespace EHR;
internal static class TargetArrow
{
private static readonly Dictionary<ArrowInfo, string> TargetArrows = [];
private static readonly string[] Arrows =
[
"↑",
"↗",
"→",
"↘",
"↓",
"↙",
"←",
"↖",
"・"
];
public static void Init()
{
TargetArrows.Clear();
}
public static void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
Add(reader.ReadByte(), reader.ReadByte());
break;
case 2:
Remove(reader.ReadByte(), reader.ReadByte());
break;
case 3:
RemoveAllTarget(reader.ReadByte());
break;
}
}
/// <summary>
/// Register a new target arrow object
/// </summary>
/// <param name="seer"></param>
/// <param name="target"></param>
public static void Add(byte seer, byte target)
{
if (Main.PlayerStates.TryGetValue(seer, out var state) && state.SubRoles.Contains(CustomRoles.Blind)) return;
ArrowInfo arrowInfo = new(seer, target);
if (!TargetArrows.Any(a => a.Key.Equals(arrowInfo)))
{
TargetArrows[arrowInfo] = "・";
Utils.SendRPC(CustomRPC.Arrow, true, 1, seer, target);
Logger.Info($"New target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow");
}
}
/// <summary>
/// Delete target
/// </summary>
/// <param name="seer"></param>
/// <param name="target"></param>
public static void Remove(byte seer, byte target)
{
ArrowInfo arrowInfo = new(seer, target);
List<ArrowInfo> removeList = new(TargetArrows.Keys.Where(k => k.Equals(arrowInfo)));
if (removeList.Count == 0) return;
removeList.ForEach(a => TargetArrows.Remove(a));
Utils.SendRPC(CustomRPC.Arrow, true, 2, seer, target);
Logger.Info($"Removed target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow");
}
/// <summary>
/// Delete all targets for the specified seer
/// </summary>
/// <param name="seer"></param>
public static void RemoveAllTarget(byte seer)
{
List<ArrowInfo> removeList = new(TargetArrows.Keys.Where(k => k.From == seer));
if (removeList.Count == 0) return;
removeList.ForEach(a => TargetArrows.Remove(a));
Utils.SendRPC(CustomRPC.Arrow, true, 3, seer);
Logger.Info($"Removed all target arrows for {seer} ({seer.GetPlayer()?.GetRealName()})", "TargetArrow");
}
/// <summary>
/// Get all visible target arrows for the specified seer to the specified target(s)
/// </summary>
/// <param name="seer"></param>
/// <param name="targets"></param>
/// <returns></returns>
public static string GetArrows(PlayerControl seer, params byte[] targets)
{
return TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId && targets.Contains(ai.To)).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]);
}
/// <summary>
/// Get all visible target arrows for the specified seer
/// </summary>
/// <param name="seer"></param>
/// <returns></returns>
public static string GetAllArrows(byte seer)
{
return TargetArrows.Keys.Where(ai => ai.From == seer).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]);
}
private static readonly List<ArrowInfo> ArrowList = [];
/// <summary>
/// Check target arrow every FixedUpdate
/// Issue NotifyRoles when there are updates
/// </summary>
/// <param name="seer"></param>
public static void OnFixedUpdate(PlayerControl seer)
{
if (!GameStates.IsInTask) return;
bool seerIsDead = !seer.IsAlive();
ArrowList.Clear();
foreach (var arrowInfo in TargetArrows.Keys)
{
if (arrowInfo.From == seer.PlayerId)
ArrowList.Add(arrowInfo);
}
int arrowCount = ArrowList.Count;
if (arrowCount == 0) return;
var update = false;
for (int arrowId = 0; arrowId < arrowCount; arrowId++)
{
ArrowInfo arrowInfo = ArrowList[arrowId];
byte targetId = arrowInfo.To;
PlayerControl target = Utils.GetPlayerById(targetId);
if (seerIsDead || (!target.IsAlive() && !seer.Is(CustomRoles.Spiritualist)))
{
TargetArrows.Remove(arrowInfo);
update = true;
continue;
}
// Take the direction vector of the target
Vector3 dir = target.transform.position - seer.transform.position;
int index;
if (dir.magnitude < 2)
{
// Display a dot when close
index = 8;
}
else
{
// Convert to index with -22.5 to 22.5 degrees as 0
// Bottom is 0 degrees, left side is +180, right side is -180
// Adding 180 degrees clockwise with top being 0 degrees
// Add 45/2 to make index in 45 degree units
double angle = Vector3.SignedAngle(Vector3.down, dir, Vector3.back) + 180 + 22.5;
index = (int)(angle / 45) % 8;
}
string arrow = Arrows[index];
if (TargetArrows[arrowInfo] != arrow)
{
TargetArrows[arrowInfo] = arrow;
update = true;
}
}
if (update) Utils.NotifyRoles(SpecifySeer: seer, ForceLoop: false, SpecifyTarget: seer);
}
private class ArrowInfo(byte from, byte to)
{
public readonly byte From = from;
public readonly byte To = to;
public bool Equals(ArrowInfo obj)
{
return From == obj.From && To == obj.To;
}
public override string ToString()
{
return $"(From:{From} To:{To})";
}
}
}