using System; using System.Collections.Generic; using EHR.Modules; using Hazel; using UnityEngine; namespace EHR; public static class NameNotifyManager { public static readonly Dictionary> Notifies = []; private static readonly List> NameList = []; private static readonly Comparison> CompareByValue = static (a, b) => a.Value.CompareTo(b.Value); private static readonly StringBuilder Sb = new(); public static void Reset() { Notifies.Clear(); } public static void Notify(this PlayerControl pc, string text, float time = 6f, bool overrideAll = false, bool log = true, SendOption sendOption = SendOption.Reliable) { if (!AmongUsClient.Instance.AmHost || !pc) return; if (!GameStates.IsInTask) return; text = text.Trim(); if (!text.Contains('#') && !text.Contains("" + text + ""; long expireTS = Utils.TimeStamp + (long)time; byte pcId = pc.PlayerId; bool alreadyContainsKey = false; if (overrideAll || !Notifies.TryGetValue(pcId, out Dictionary notifies)) Notifies[pc.PlayerId] = new() { { text, expireTS } }; else { alreadyContainsKey = notifies.ContainsKey(text); notifies[text] = expireTS; } if (pc.IsNonHostModdedClient()) SendRPC(pcId, text, expireTS, overrideAll, sendOption); if (alreadyContainsKey) { if (log) Logger.Info($"Extended name notify for {pc.GetNameWithRole()}: {text} ({time}s)", "Name Notify"); return; } Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc, SendOption: sendOption); if (log) Logger.Info($"New name notify for {pc.GetNameWithRole()}: {text} ({time}s)", "Name Notify"); } public static void OnFixedUpdate() { if (!GameStates.IsInTask) { Reset(); return; } if (!PerSecondUpdateScheduler.ShouldRunUpdate()) return; if (Notifies.Count == 0) return; long now = Utils.TimeStamp; var notifyEnumerator = Notifies.GetEnumerator(); while (notifyEnumerator.MoveNext()) { (byte id, Dictionary dict) = notifyEnumerator.Current; List toRemove = null; var innerEnumerator = dict.GetEnumerator(); while (innerEnumerator.MoveNext()) { var innerCurrent = innerEnumerator.Current; if (innerCurrent.Value <= now) { toRemove ??= []; toRemove.Add(innerCurrent.Key); } } if (toRemove != null) { for (int index = 0; index < toRemove.Count; index++) dict.Remove(toRemove[index]); if (!AmongUsClient.Instance.AmHost) continue; PlayerControl pc = Utils.GetPlayerById(id); Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } } } public static bool GetNameNotify(PlayerControl player, out string name) { name = string.Empty; if (!Notifies.TryGetValue(player.PlayerId, out var notifies) || notifies.Count == 0) return false; NameList.Clear(); var enumerator = notifies.GetEnumerator(); while (enumerator.MoveNext()) NameList.Add(enumerator.Current); if (NameList.Count >= 2) NameList.Sort(CompareByValue); Sb.Clear(); for (int index = 0; index < NameList.Count; index++) { if (index > 0) Sb.Append('\n'); Sb.Append(NameList[index].Key); } name = Sb.ToString(); return true; } private static void SendRPC(byte playerId, string text, long expireTS, bool overrideAll, SendOption sendOption) // Only sent when adding a new notification { if (!AmongUsClient.Instance.AmHost) return; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, sendOption); writer.Write(playerId); writer.Write(text); writer.Write(expireTS.ToString()); writer.Write(overrideAll); AmongUsClient.Instance.FinishRpcImmediately(writer); } public static void SendRPC(CustomRpcSender sender, PlayerControl player, string text, long expireTS, bool overrideAll) { if (!AmongUsClient.Instance.AmHost || player.OwnerId < 0) return; sender.AutoStartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, sender.packed || sender.currentRpcTarget >= 0 ? player.OwnerId : -1); sender.Write(player.PlayerId); sender.Write(text); sender.Write(expireTS.ToString()); sender.Write(overrideAll); sender.EndRpc(); } public static void ReceiveRPC(MessageReader reader) { if (AmongUsClient.Instance.AmHost) return; byte playerId = reader.ReadByte(); string text = reader.ReadString(); long expireTS = long.Parse(reader.ReadString()); bool overrideAll = reader.ReadBoolean(); if (overrideAll || !Notifies.TryGetValue(playerId, out Dictionary notifies)) Notifies[playerId] = new() { { text, expireTS } }; else notifies[text] = expireTS; Logger.Info($"New name notify for {Main.AllPlayerNames[playerId]}: {text} ({expireTS - Utils.TimeStamp}s)", "Name Notify"); } }