using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HoldfastGame;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace AdminHelper
{
[BepInPlugin("com.ryannlt.adminhelper", "AdminHelper", "1.0.0")]
public class AdminHelperMod : BaseUnityPlugin
{
public const string Guid = "com.ryannlt.adminhelper";
private readonly IsolationTracker _tracker = new IsolationTracker();
private readonly RingRenderer _rings = new RingRenderer();
private readonly Hotkey _hotkey = new Hotkey();
private readonly Hud _hud = new Hud();
private Driver _driver;
private float _accumulator;
private bool _wasInRound;
private void Awake()
{
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
Settings.Create(((BaseUnityPlugin)this).Config);
_hotkey.ResetToDefault();
SceneManager.sceneLoaded += OnSceneLoaded;
EnsureDriver();
Log.Info("Ready. Toggle key " + ((object)Settings.ResolveToggleKey()/*cast due to .constrained prefix*/).ToString() + ", RequireAdminLogin=" + Settings.RequireAdminLogin.Value);
}
private void OnDestroy()
{
Log.Info("plugin component destroyed");
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Log.Info("scene loaded " + ((Scene)(ref scene)).name + " driverAlive=" + ((Object)(object)_driver != (Object)null));
EnsureDriver();
GameAccess.ClearSceneCache();
_tracker.Reset();
_rings.Destroy();
_accumulator = 0f;
_wasInRound = false;
}
private void EnsureDriver()
{
if (!((Object)(object)_driver != (Object)null))
{
_driver = Driver.Attach(this);
}
}
internal void Tick()
{
Settings.PollForExternalEdits();
if (!Settings.Enabled.Value)
{
_rings.HideAll();
return;
}
_hotkey.Poll();
if (!GameAccess.InRound)
{
if (_wasInRound)
{
_tracker.Reset();
}
_wasInRound = false;
_rings.HideAll();
return;
}
_wasInRound = true;
_accumulator += Time.deltaTime;
float num = 1f / Mathf.Clamp(Settings.TickHz.Value, 1f, 30f);
if (_accumulator >= num)
{
_tracker.Tick(_accumulator);
_accumulator = 0f;
}
if (CanReveal() && _hotkey.Visible && Settings.ShowRings.Value)
{
_rings.Draw(_tracker.Watched);
}
else
{
_rings.HideAll();
}
}
internal void DrawGui()
{
if (Settings.Enabled.Value && _hotkey.Visible && GameAccess.InRound)
{
_hud.Draw(_tracker, CanReveal());
}
}
private static bool CanReveal()
{
return !Settings.RequireAdminLogin.Value || GameAccess.IsLoggedInAdmin;
}
}
internal sealed class Driver : MonoBehaviour
{
internal AdminHelperMod Owner;
internal static Driver Attach(AdminHelperMod owner)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Expected O, but got Unknown
GameObject val = new GameObject("AdminHelper_Driver");
Object.DontDestroyOnLoad((Object)(object)val);
Driver driver = val.AddComponent<Driver>();
driver.Owner = owner;
return driver;
}
private void Awake()
{
Log.Info("driver awake");
}
private void OnDestroy()
{
Log.Info("driver destroyed");
}
private void Update()
{
if (Owner != null)
{
Owner.Tick();
}
}
private void OnGUI()
{
if (Owner != null)
{
Owner.DrawGui();
}
}
}
internal static class FormationDetector
{
private const int MaxScratch = 16;
private static readonly float[] ScratchX = new float[16];
private static readonly float[] ScratchZ = new float[16];
private static readonly float[] ScratchDistanceSquared = new float[16];
public static bool IsInFormation(PlayerSnapshot self, List<PlayerSnapshot> friendlies)
{
if (GameAccess.IsInsideOfficerLine(self.Player))
{
return true;
}
int num = CollectNearest(self, friendlies, Settings.LineRadius.Value, Settings.LineMaxMates.Value);
if (CountWithin(num, Settings.ClusterFormationRadius.Value) >= Settings.ClusterMinMates.Value)
{
return true;
}
if (num < Settings.LineMinMates.Value)
{
return false;
}
return PerpendicularSpread(self, num) <= Settings.LineResidual.Value;
}
private static int CollectNearest(PlayerSnapshot self, List<PlayerSnapshot> friendlies, float radius, int max)
{
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_005b: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
//IL_012d: Unknown result type (might be due to invalid IL or missing references)
//IL_0141: Unknown result type (might be due to invalid IL or missing references)
if (max > 16)
{
max = 16;
}
float num = radius * radius;
int num2 = 0;
for (int i = 0; i < friendlies.Count; i++)
{
PlayerSnapshot playerSnapshot = friendlies[i];
if (playerSnapshot.PlayerId == self.PlayerId)
{
continue;
}
float num3 = playerSnapshot.Position.x - self.Position.x;
float num4 = playerSnapshot.Position.z - self.Position.z;
float num5 = num3 * num3 + num4 * num4;
if (!(num5 > num) && (num2 != max || !(num5 >= ScratchDistanceSquared[num2 - 1])))
{
int num6 = ((num2 < max) ? num2 : (num2 - 1));
while (num6 > 0 && ScratchDistanceSquared[num6 - 1] > num5)
{
ScratchDistanceSquared[num6] = ScratchDistanceSquared[num6 - 1];
ScratchX[num6] = ScratchX[num6 - 1];
ScratchZ[num6] = ScratchZ[num6 - 1];
num6--;
}
ScratchDistanceSquared[num6] = num5;
ScratchX[num6] = playerSnapshot.Position.x;
ScratchZ[num6] = playerSnapshot.Position.z;
if (num2 < max)
{
num2++;
}
}
}
return num2;
}
private static int CountWithin(int count, float radius)
{
float num = radius * radius;
for (int i = 0; i < count; i++)
{
if (ScratchDistanceSquared[i] > num)
{
return i;
}
}
return count;
}
private static float PerpendicularSpread(PlayerSnapshot self, int count)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_0079: Unknown result type (might be due to invalid IL or missing references)
int num = count + 1;
float num2 = self.Position.x;
float num3 = self.Position.z;
for (int i = 0; i < count; i++)
{
num2 += ScratchX[i];
num3 += ScratchZ[i];
}
float num4 = num2 / (float)num;
float num5 = num3 / (float)num;
float xx = 0f;
float xz = 0f;
float zz = 0f;
AccumulateCovariance(self.Position.x - num4, self.Position.z - num5, ref xx, ref xz, ref zz);
for (int j = 0; j < count; j++)
{
AccumulateCovariance(ScratchX[j] - num4, ScratchZ[j] - num5, ref xx, ref xz, ref zz);
}
float num6 = xx + zz;
float num7 = xx - zz;
float num8 = Mathf.Sqrt(num7 * num7 + 4f * xz * xz);
float num9 = Mathf.Max(0f, (num6 - num8) * 0.5f);
return Mathf.Sqrt(num9 / (float)num);
}
private static void AccumulateCovariance(float dx, float dz, ref float xx, ref float xz, ref float zz)
{
xx += dx * dx;
xz += dx * dz;
zz += dz * dz;
}
}
internal static class GameAccess
{
private static GameConsolePanel _consolePanel;
private static float _nextConsoleLookup;
public static ClientComponentReferenceManager Client => ClientComponentReferenceManager.ClientInstance;
public static bool IsLoggedInAdmin => ClientRemoteConsoleAccessManager.loggedOn;
public static bool InRound
{
get
{
ClientComponentReferenceManager client = Client;
return (Object)(object)client != (Object)null && (Object)(object)client.clientRoundPlayerManager != (Object)null;
}
}
public static Camera ActiveCamera
{
get
{
ClientComponentReferenceManager client = Client;
if ((Object)(object)client == (Object)null || (Object)(object)client.ownerCameraManager == (Object)null)
{
return null;
}
Camera val = client.ownerCameraManager.ActiveCamera;
if ((Object)(object)val == (Object)null)
{
val = client.ownerCameraManager.ownerCamera;
}
return val;
}
}
public static int LocalPlayerId
{
get
{
ClientComponentReferenceManager client = Client;
if ((Object)(object)client == (Object)null || (Object)(object)client.clientRoundPlayerManager == (Object)null)
{
return -1;
}
return ((RoundPlayer)(client.clientRoundPlayerManager.LocalPlayer?)).NetworkPlayerID ?? (-1);
}
}
public static bool IsTyping
{
get
{
ClientComponentReferenceManager client = Client;
if ((Object)(object)client != (Object)null && (Object)(object)client.clientChatHandler != (Object)null && client.clientChatHandler.isChatPaneOpened)
{
return true;
}
if ((Object)(object)_consolePanel == (Object)null && Time.unscaledTime >= _nextConsoleLookup)
{
_nextConsoleLookup = Time.unscaledTime + 2f;
_consolePanel = Object.FindObjectOfType<GameConsolePanel>();
}
return (Object)(object)_consolePanel != (Object)null && _consolePanel.Showing;
}
}
public static void ClearSceneCache()
{
_consolePanel = null;
_nextConsoleLookup = 0f;
}
public static void CollectPlayers(List<PlayerSnapshot> into)
{
into.Clear();
ClientComponentReferenceManager client = Client;
if ((Object)(object)client == (Object)null)
{
return;
}
ClientRoundPlayerManager clientRoundPlayerManager = client.clientRoundPlayerManager;
if ((Object)(object)clientRoundPlayerManager == (Object)null)
{
return;
}
List<ClientRoundPlayerProxy> roundPlayersList = clientRoundPlayerManager.roundPlayersList;
for (int i = 0; i < roundPlayersList.Count; i++)
{
if (TrySnapshot((RoundPlayer)(object)roundPlayersList[i], out var snapshot))
{
into.Add(snapshot);
}
}
if (TrySnapshot((RoundPlayer)(object)clientRoundPlayerManager.LocalPlayer, out var snapshot2))
{
into.Add(snapshot2);
}
}
private static bool TrySnapshot(RoundPlayer player, out PlayerSnapshot snapshot)
{
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
//IL_008f: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
snapshot = default(PlayerSnapshot);
if (player == null || (Object)(object)player.PlayerBase == (Object)null || !player.PlayerBase.SpawnedAndAlive)
{
return false;
}
if (player.PlayerTransformData == null || player.PlayerStartData == null)
{
return false;
}
PlayerBase playerBase = player.PlayerBase;
snapshot.Player = player;
snapshot.PlayerId = player.NetworkPlayerID;
snapshot.Name = ResolveName(player);
snapshot.Position = player.PlayerTransformData.position;
snapshot.Faction = player.PlayerStartData.Faction;
snapshot.Class = player.PlayerStartData.ClassType;
snapshot.IsCavalry = playerBase.IsCavalry;
snapshot.IsArtillery = playerBase.IsArty;
return true;
}
private static string ResolveName(RoundPlayer player)
{
RoundPlayerInformation playerRoundInformation = player.PlayerRoundInformation;
if (playerRoundInformation == null || playerRoundInformation.InitialDetails == null)
{
return "Player " + player.NetworkPlayerID;
}
PlayerInitialDetails initialDetails = playerRoundInformation.InitialDetails;
string text = (string.IsNullOrEmpty(initialDetails.DisplayName) ? initialDetails.Name : initialDetails.DisplayName);
if (string.IsNullOrEmpty(text))
{
return "Player " + player.NetworkPlayerID;
}
return text;
}
public static bool IsInsideOfficerLine(RoundPlayer player)
{
ClientComponentReferenceManager client = Client;
if ((Object)(object)client == (Object)null || (Object)(object)client.clientHighCommandOrderManager == (Object)null)
{
return false;
}
return ((HighCommandOrderManager)client.clientHighCommandOrderManager).IsPlayerInsideAnyOfficerLine(player);
}
}
internal sealed class Hotkey
{
private bool _visible;
public bool Visible => _visible;
public void ResetToDefault()
{
_visible = Settings.StartHudVisible.Value;
}
public void Poll()
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
if (!GameAccess.IsTyping && Input.GetKeyDown(Settings.ResolveToggleKey()))
{
_visible = !_visible;
}
}
}
internal sealed class Hud
{
private static readonly Color WatchColour = new Color(1f, 0.82f, 0.15f);
private static readonly Color FlagColour = new Color(1f, 0.35f, 0.25f);
private static readonly Color InfoColour = new Color(0.85f, 0.88f, 0.92f);
private static readonly Color PanelColour = new Color(0.05f, 0.05f, 0.06f, 0.78f);
private readonly List<ScoredPlayer> _sorted = new List<ScoredPlayer>();
private GUIStyle _labelStyle;
private GUIStyle _listStyle;
private Texture2D _panelTexture;
public void Draw(IsolationTracker tracker, bool revealOthers)
{
EnsureStyles();
_sorted.Clear();
if (revealOthers)
{
SortByHeat(tracker.Watched);
if (Settings.ShowLabels.Value)
{
DrawLabels();
}
}
if (Settings.ShowCornerList.Value)
{
DrawCornerList(revealOthers);
}
if (Settings.ShowOwnScore.Value && tracker.HasLocalScore)
{
DrawOwnScore(tracker.LocalScore);
}
}
private void DrawLabels()
{
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Unknown result type (might be due to invalid IL or missing references)
//IL_011e: Expected O, but got Unknown
//IL_0126: Unknown result type (might be due to invalid IL or missing references)
//IL_012b: Unknown result type (might be due to invalid IL or missing references)
//IL_0137: Unknown result type (might be due to invalid IL or missing references)
//IL_014a: Unknown result type (might be due to invalid IL or missing references)
//IL_0151: Unknown result type (might be due to invalid IL or missing references)
//IL_0165: Unknown result type (might be due to invalid IL or missing references)
//IL_016d: Unknown result type (might be due to invalid IL or missing references)
//IL_0175: Unknown result type (might be due to invalid IL or missing references)
//IL_017c: Unknown result type (might be due to invalid IL or missing references)
//IL_0188: Unknown result type (might be due to invalid IL or missing references)
//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
Camera activeCamera = GameAccess.ActiveCamera;
if ((Object)(object)activeCamera == (Object)null)
{
return;
}
int num = Mathf.Min(_sorted.Count, Mathf.Max(0, Settings.MaxLabels.Value));
Rect val4 = default(Rect);
for (int i = 0; i < num; i++)
{
ScoredPlayer scoredPlayer = _sorted[i];
Vector3 val = activeCamera.WorldToScreenPoint(scoredPlayer.Position + Vector3.up * 2.1f);
if (!(val.z <= 0f))
{
string text = (scoredPlayer.Flagged ? ("RAMBO: " + Mathf.FloorToInt(scoredPlayer.DwellSeconds) + "s") : "ISOLATED");
string text2 = scoredPlayer.Name + "\n" + text + "\nISO: " + scoredPlayer.Isolation + " DGR: " + scoredPlayer.Danger;
GUIContent val2 = new GUIContent(text2);
Vector2 val3 = _labelStyle.CalcSize(val2);
val3.y = _labelStyle.CalcHeight(val2, val3.x);
((Rect)(ref val4))..ctor(val.x - val3.x * 0.5f, (float)Screen.height - val.y - val3.y, val3.x, val3.y);
GUI.DrawTexture(val4, (Texture)(object)_panelTexture);
_labelStyle.normal.textColor = (scoredPlayer.Flagged ? FlagColour : WatchColour);
GUI.Label(val4, text2, _labelStyle);
}
}
}
private void DrawCornerList(bool revealOthers)
{
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
//IL_0123: Unknown result type (might be due to invalid IL or missing references)
//IL_0128: Unknown result type (might be due to invalid IL or missing references)
//IL_0129: Unknown result type (might be due to invalid IL or missing references)
//IL_0183: Unknown result type (might be due to invalid IL or missing references)
//IL_017c: Unknown result type (might be due to invalid IL or missing references)
//IL_018e: Unknown result type (might be due to invalid IL or missing references)
Camera activeCamera = GameAccess.ActiveCamera;
Vector3 val = (((Object)(object)activeCamera != (Object)null) ? ((Component)activeCamera).transform.position : Vector3.zero);
string text = (revealOthers ? ("AdminHelper " + _sorted.Count + " watched, " + CountFlagged() + " flagged") : "AdminHelper admin login required");
float num = 260f;
float num2 = 18f;
Rect val2 = default(Rect);
((Rect)(ref val2))..ctor(12f, 12f, num, num2 * (float)(_sorted.Count + 1) + 10f);
GUI.DrawTexture(val2, (Texture)(object)_panelTexture);
_listStyle.normal.textColor = InfoColour;
GUI.Label(new Rect(((Rect)(ref val2)).x + 6f, ((Rect)(ref val2)).y + 5f, num - 12f, num2), text, _listStyle);
Rect val3 = default(Rect);
for (int i = 0; i < _sorted.Count; i++)
{
ScoredPlayer scoredPlayer = _sorted[i];
float num3 = Horizontal(scoredPlayer.Position - val);
((Rect)(ref val3))..ctor(((Rect)(ref val2)).x + 6f, ((Rect)(ref val2)).y + 5f + num2 * (float)(i + 1), num - 12f, num2);
_listStyle.normal.textColor = (scoredPlayer.Flagged ? FlagColour : WatchColour);
GUI.Label(val3, scoredPlayer.Isolation + "/" + scoredPlayer.Danger + " " + Mathf.RoundToInt(num3) + "m " + scoredPlayer.Name, _listStyle);
}
}
private void DrawOwnScore(ScoredPlayer local)
{
//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
//IL_010f: Unknown result type (might be due to invalid IL or missing references)
//IL_0108: Unknown result type (might be due to invalid IL or missing references)
//IL_011a: Unknown result type (might be due to invalid IL or missing references)
string text = "ISO: " + local.Isolation + " DGR: " + local.Danger + "\nmates " + Metres(local.MateDistance) + " enemy " + Metres(local.EnemyDistance) + " x" + local.EnemyCount + (local.InFormation ? "\nin formation" : "\nout of formation") + " dwell " + local.DwellSeconds.ToString("0.0") + "s";
Rect val = default(Rect);
((Rect)(ref val))..ctor((float)Screen.width - 232f, (float)Screen.height - 76f, 220f, 64f);
GUI.DrawTexture(val, (Texture)(object)_panelTexture);
_labelStyle.normal.textColor = (local.Flagged ? FlagColour : InfoColour);
GUI.Label(val, text, _labelStyle);
}
private static string Metres(float distance)
{
if (distance >= 9000f)
{
return "none";
}
return distance.ToString("0.0") + "m";
}
private void SortByHeat(List<ScoredPlayer> watched)
{
_sorted.Clear();
_sorted.AddRange(watched);
}
private int CountFlagged()
{
int num = 0;
for (int i = 0; i < _sorted.Count; i++)
{
if (_sorted[i].Flagged)
{
num++;
}
}
return num;
}
private void EnsureStyles()
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Expected O, but got Unknown
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Expected O, but got Unknown
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Expected O, but got Unknown
//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
//IL_00e7: Expected O, but got Unknown
//IL_010c: Unknown result type (might be due to invalid IL or missing references)
//IL_0116: Expected O, but got Unknown
//IL_0122: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)_panelTexture == (Object)null)
{
_panelTexture = new Texture2D(1, 1);
_panelTexture.SetPixel(0, 0, PanelColour);
_panelTexture.Apply();
((Object)_panelTexture).hideFlags = (HideFlags)61;
}
if (_labelStyle == null)
{
_labelStyle = new GUIStyle(GUI.skin.label);
_labelStyle.alignment = (TextAnchor)4;
_labelStyle.fontSize = 13;
_labelStyle.fontStyle = (FontStyle)1;
_labelStyle.padding = new RectOffset(6, 6, 3, 3);
_labelStyle.normal.textColor = FlagColour;
}
if (_listStyle == null)
{
_listStyle = new GUIStyle(GUI.skin.label);
_listStyle.alignment = (TextAnchor)3;
_listStyle.fontSize = 12;
_listStyle.padding = new RectOffset(0, 0, 0, 0);
_listStyle.normal.textColor = FlagColour;
}
}
private static float Horizontal(Vector3 delta)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
return Mathf.Sqrt(delta.x * delta.x + delta.z * delta.z);
}
}
internal sealed class IsolationTracker
{
private sealed class State
{
public float Isolation;
public float Dwell;
public int LastSeenTick;
}
private const float SoloEnemyFloor = 0.5f;
private const int MaxWatched = 32;
private readonly Dictionary<int, State> _states = new Dictionary<int, State>();
private readonly List<PlayerSnapshot> _players = new List<PlayerSnapshot>();
private readonly Dictionary<FactionCountry, List<PlayerSnapshot>> _byFaction = new Dictionary<FactionCountry, List<PlayerSnapshot>>();
private readonly List<int> _stale = new List<int>();
private int _tick;
public readonly List<ScoredPlayer> Watched = new List<ScoredPlayer>();
public ScoredPlayer LocalScore;
public bool HasLocalScore;
public void Reset()
{
_states.Clear();
_byFaction.Clear();
Watched.Clear();
HasLocalScore = false;
}
public void Tick(float dt)
{
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
_tick++;
Watched.Clear();
HasLocalScore = false;
GameAccess.CollectPlayers(_players);
if (_players.Count == 0)
{
DropStaleStates();
return;
}
BucketByFaction();
int localPlayerId = GameAccess.LocalPlayerId;
ScoredPlayer scoredPlayer = default(ScoredPlayer);
for (int i = 0; i < _players.Count; i++)
{
PlayerSnapshot self = _players[i];
List<PlayerSnapshot> list = _byFaction[self.Faction];
if (list.Count >= 3)
{
float num = MateDistance(self, list);
bool flag = FormationDetector.IsInFormation(self, list);
float num2 = Curve(num);
if (flag)
{
num2 *= 1f - Mathf.Clamp01(Settings.FormationSuppression.Value);
}
State state = ResolveState(self.PlayerId);
Integrate(state, num2, dt);
MeasureEnemies(self, out var nearestDistance, out var countInRadius);
int num3 = Mathf.RoundToInt(state.Isolation * 100f);
int danger = Mathf.RoundToInt((float)num3 * EnemyThreat(nearestDistance, countInRadius));
bool flag2 = IsScorable(self);
if (flag2 && num3 >= Settings.RamboThreshold.Value)
{
state.Dwell += dt;
}
else
{
state.Dwell = Mathf.Max(0f, state.Dwell - dt * Mathf.Max(1f, Settings.RecoverMultiplier.Value));
}
scoredPlayer.PlayerId = self.PlayerId;
scoredPlayer.Name = self.Name;
scoredPlayer.Position = self.Position;
scoredPlayer.Isolation = num3;
scoredPlayer.Danger = danger;
scoredPlayer.DwellSeconds = state.Dwell;
scoredPlayer.Flagged = flag2 && state.Dwell >= Settings.RamboHoldSeconds.Value;
scoredPlayer.MateDistance = num;
scoredPlayer.EnemyDistance = nearestDistance;
scoredPlayer.EnemyCount = countInRadius;
scoredPlayer.InFormation = flag;
if (flag2 && num3 >= Settings.RingThreshold.Value)
{
Watched.Add(scoredPlayer);
}
if (self.PlayerId == localPlayerId)
{
LocalScore = scoredPlayer;
HasLocalScore = true;
}
}
}
SortAndCapWatched();
DropStaleStates();
}
private void SortAndCapWatched()
{
Watched.Sort((ScoredPlayer a, ScoredPlayer b) => b.Isolation.CompareTo(a.Isolation));
if (Watched.Count > 32)
{
Watched.RemoveRange(32, Watched.Count - 32);
}
}
private bool IsScorable(PlayerSnapshot self)
{
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
if (self.IsCavalry && !Settings.ScoreCavalry.Value)
{
return false;
}
if (self.IsArtillery)
{
return false;
}
return !Settings.IsExempt(self.Class);
}
private static float Curve(float distance)
{
float value = Settings.ClusterNearMetres.Value;
float num = Mathf.Max(value + 1f, Settings.ClusterFarMetres.Value);
return Mathf.Clamp01((distance - value) / (num - value));
}
private float MateDistance(PlayerSnapshot self, List<PlayerSnapshot> friendlies)
{
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
//IL_0100: Unknown result type (might be due to invalid IL or missing references)
//IL_0106: Unknown result type (might be due to invalid IL or missing references)
//IL_0116: Unknown result type (might be due to invalid IL or missing references)
//IL_0124: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_0099: Unknown result type (might be due to invalid IL or missing references)
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
float num = float.MaxValue;
float num2 = float.MaxValue;
Vector3 val = Vector3.zero;
Vector3 val2 = Vector3.zero;
for (int i = 0; i < friendlies.Count; i++)
{
PlayerSnapshot playerSnapshot = friendlies[i];
if (playerSnapshot.PlayerId != self.PlayerId)
{
float num3 = playerSnapshot.Position.x - self.Position.x;
float num4 = playerSnapshot.Position.z - self.Position.z;
float num5 = num3 * num3 + num4 * num4;
if (num5 < num)
{
num2 = num;
val2 = val;
num = num5;
val = playerSnapshot.Position;
}
else if (num5 < num2)
{
num2 = num5;
val2 = playerSnapshot.Position;
}
}
}
if (num2 == float.MaxValue)
{
return float.MaxValue;
}
float num6 = (val.x + val2.x) * 0.5f;
float num7 = (val.z + val2.z) * 0.5f;
return Horizontal(self.Position.x - num6, self.Position.z - num7);
}
private void MeasureEnemies(PlayerSnapshot self, out float nearestDistance, out int countInRadius)
{
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
float value = Settings.EnemyRadius.Value;
float num = value * value;
float num2 = float.MaxValue;
countInRadius = 0;
for (int i = 0; i < _players.Count; i++)
{
PlayerSnapshot playerSnapshot = _players[i];
if (playerSnapshot.Faction != self.Faction)
{
float num3 = playerSnapshot.Position.x - self.Position.x;
float num4 = playerSnapshot.Position.z - self.Position.z;
float num5 = num3 * num3 + num4 * num4;
if (num5 < num2)
{
num2 = num5;
}
if (num5 <= num)
{
countInRadius++;
}
}
}
nearestDistance = ((num2 == float.MaxValue) ? float.MaxValue : Mathf.Sqrt(num2));
}
private static float EnemyThreat(float nearestDistance, int countInRadius)
{
float value = Settings.EnemyRadius.Value;
if (value <= 0f || nearestDistance > value)
{
return 0f;
}
float num = Mathf.Clamp01((value - nearestDistance) / value);
float num2 = Mathf.Clamp01((float)countInRadius / Mathf.Max(1f, (float)Settings.EnemyCrowd.Value));
return num * (0.5f + 0.5f * num2);
}
private static void Integrate(State state, float target, float dt)
{
float num = Mathf.Max(0.05f, Settings.RiseSeconds.Value);
float num2 = 1f / num;
if (target < state.Isolation)
{
num2 *= Mathf.Max(1f, Settings.RecoverMultiplier.Value);
}
state.Isolation += (target - state.Isolation) * (1f - Mathf.Exp((0f - num2) * dt));
}
private void BucketByFaction()
{
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
foreach (KeyValuePair<FactionCountry, List<PlayerSnapshot>> item in _byFaction)
{
item.Value.Clear();
}
for (int i = 0; i < _players.Count; i++)
{
FactionCountry faction = _players[i].Faction;
if (!_byFaction.TryGetValue(faction, out var value))
{
value = new List<PlayerSnapshot>(150);
_byFaction[faction] = value;
}
value.Add(_players[i]);
}
}
private State ResolveState(int playerId)
{
if (!_states.TryGetValue(playerId, out var value))
{
value = new State();
_states[playerId] = value;
}
value.LastSeenTick = _tick;
return value;
}
private void DropStaleStates()
{
_stale.Clear();
foreach (KeyValuePair<int, State> state in _states)
{
if (_tick - state.Value.LastSeenTick > 60)
{
_stale.Add(state.Key);
}
}
for (int i = 0; i < _stale.Count; i++)
{
_states.Remove(_stale[i]);
}
}
private static float Horizontal(float dx, float dz)
{
return Mathf.Sqrt(dx * dx + dz * dz);
}
}
internal static class Log
{
private static readonly ManualLogSource Source = Logger.CreateLogSource("AdminHelper");
public static void Info(string message)
{
Source.LogInfo((object)message);
}
public static void Warn(string message)
{
Source.LogWarning((object)message);
}
public static void Error(string message)
{
Source.LogError((object)message);
}
}
internal struct PlayerSnapshot
{
public RoundPlayer Player;
public int PlayerId;
public string Name;
public Vector3 Position;
public FactionCountry Faction;
public PlayerClass Class;
public bool IsCavalry;
public bool IsArtillery;
}
internal struct ScoredPlayer
{
public int PlayerId;
public string Name;
public Vector3 Position;
public int Isolation;
public int Danger;
public float DwellSeconds;
public bool Flagged;
public float MateDistance;
public float EnemyDistance;
public int EnemyCount;
public bool InFormation;
}
internal sealed class RingRenderer
{
private const int Segments = 48;
private const float Radius = 1.6f;
private const float GroundOffset = 0.05f;
private readonly List<LineRenderer> _pool = new List<LineRenderer>();
private readonly Vector3[] _points = (Vector3[])(object)new Vector3[49];
private GameObject _root;
private Shader _shader;
public void Draw(List<ScoredPlayer> flagged)
{
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
EnsureRoot();
for (int i = 0; i < flagged.Count; i++)
{
LineRenderer val = Resolve(i);
((Component)val).gameObject.SetActive(true);
Color val2 = ColourFor(flagged[i].Isolation);
Color startColor = (val.endColor = val2);
val.startColor = startColor;
if ((Object)(object)((Renderer)val).sharedMaterial != (Object)null)
{
((Renderer)val).sharedMaterial.color = val2;
}
BuildCircle(flagged[i].Position);
val.SetPositions(_points);
}
for (int j = flagged.Count; j < _pool.Count; j++)
{
if ((Object)(object)_pool[j] != (Object)null)
{
((Component)_pool[j]).gameObject.SetActive(false);
}
}
}
public void HideAll()
{
for (int i = 0; i < _pool.Count; i++)
{
if ((Object)(object)_pool[i] != (Object)null)
{
((Component)_pool[i]).gameObject.SetActive(false);
}
}
}
public void Destroy()
{
_pool.Clear();
if ((Object)(object)_root != (Object)null)
{
Object.Destroy((Object)(object)_root);
}
_root = null;
}
private void BuildCircle(Vector3 centre)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_005b: Unknown result type (might be due to invalid IL or missing references)
float num = centre.y + 0.05f;
for (int i = 0; i <= 48; i++)
{
float num2 = (float)i * (float)Math.PI * 2f / 48f;
_points[i] = new Vector3(centre.x + Mathf.Cos(num2) * 1.6f, num, centre.z + Mathf.Sin(num2) * 1.6f);
}
}
private static Color ColourFor(int isolation)
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
float num = Settings.RingThreshold.Value;
float num2 = Mathf.Max(num + 1f, (float)Settings.RamboThreshold.Value);
float num3 = Mathf.Clamp01(((float)isolation - num) / (num2 - num));
return Color.Lerp(new Color(1f, 0.82f, 0.15f), new Color(1f, 0.25f, 0.1f), num3);
}
private void EnsureRoot()
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_0023: Expected O, but got Unknown
if (!((Object)(object)_root != (Object)null))
{
_root = new GameObject("AdminHelperRings");
Object.DontDestroyOnLoad((Object)(object)_root);
_pool.Clear();
}
}
private LineRenderer Resolve(int index)
{
while (_pool.Count <= index)
{
_pool.Add(Create());
}
LineRenderer val = _pool[index];
if ((Object)(object)val == (Object)null)
{
val = Create();
_pool[index] = val;
}
return val;
}
private LineRenderer Create()
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Expected O, but got Unknown
//IL_0079: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Expected O, but got Unknown
GameObject val = new GameObject("Ring");
val.transform.SetParent(_root.transform, false);
LineRenderer val2 = val.AddComponent<LineRenderer>();
val2.useWorldSpace = true;
val2.loop = false;
val2.positionCount = 49;
float startWidth = (val2.endWidth = 0.14f);
val2.startWidth = startWidth;
val2.numCapVertices = 0;
((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0;
((Renderer)val2).receiveShadows = false;
((Renderer)val2).sharedMaterial = new Material(ResolveShader());
return val2;
}
private Shader ResolveShader()
{
if ((Object)(object)_shader != (Object)null)
{
return _shader;
}
_shader = Shader.Find("Universal Render Pipeline/Unlit");
if ((Object)(object)_shader == (Object)null)
{
_shader = Shader.Find("Sprites/Default");
}
if ((Object)(object)_shader == (Object)null)
{
_shader = Shader.Find("Hidden/Internal-Colored");
}
return _shader;
}
}
internal static class Settings
{
public static ConfigEntry<bool> Enabled;
public static ConfigEntry<float> TickHz;
public static ConfigEntry<bool> RequireAdminLogin;
public static ConfigEntry<float> ClusterNearMetres;
public static ConfigEntry<float> ClusterFarMetres;
public static ConfigEntry<float> RiseSeconds;
public static ConfigEntry<float> RecoverMultiplier;
public static ConfigEntry<float> EnemyRadius;
public static ConfigEntry<int> EnemyCrowd;
public static ConfigEntry<float> FormationSuppression;
public static ConfigEntry<float> LineRadius;
public static ConfigEntry<int> LineMinMates;
public static ConfigEntry<int> LineMaxMates;
public static ConfigEntry<float> LineResidual;
public static ConfigEntry<int> ClusterMinMates;
public static ConfigEntry<float> ClusterFormationRadius;
public static ConfigEntry<int> RingThreshold;
public static ConfigEntry<int> RamboThreshold;
public static ConfigEntry<float> RamboHoldSeconds;
public static ConfigEntry<bool> ScoreCavalry;
public static ConfigEntry<string> ExemptClasses;
public static ConfigEntry<bool> ShowRings;
public static ConfigEntry<bool> ShowLabels;
public static ConfigEntry<bool> ShowCornerList;
public static ConfigEntry<bool> ShowOwnScore;
public static ConfigEntry<int> MaxLabels;
public static ConfigEntry<string> ToggleKey;
public static ConfigEntry<bool> StartHudVisible;
private static readonly HashSet<PlayerClass> ExemptSet = new HashSet<PlayerClass>();
private static string _exemptSource;
private static string _toggleKeySource;
private static KeyCode _toggleKey = (KeyCode)287;
private static ConfigFile _config;
private static DateTime _stamp;
private static float _nextCheck;
public static void Create(ConfigFile config)
{
_config = config;
Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch. Turning this off stops the scorer as well as the HUD.");
TickHz = config.Bind<float>("General", "TickHz", 5f, "Scoring ticks per second. Higher costs more and buys nothing.");
RequireAdminLogin = config.Bind<bool>("General", "RequireAdminLogin", true, "Only show other players once the server has authenticated an 'rc login'. Turning this off makes the mod a wallhack.");
ClusterNearMetres = config.Bind<float>("Isolation", "ClusterNearMetres", 10f, "Distance from the midpoint of your two nearest mates at which isolation starts counting.");
ClusterFarMetres = config.Bind<float>("Isolation", "ClusterFarMetres", 30f, "Distance at which isolation is fully saturated.");
RiseSeconds = config.Bind<float>("Scoring", "RiseSeconds", 6f, "Time constant for the score climbing. Larger is slower to flag.");
RecoverMultiplier = config.Bind<float>("Scoring", "RecoverMultiplier", 3f, "How much faster the score falls than it climbs when a player rejoins.");
EnemyRadius = config.Bind<float>("Danger", "EnemyRadius", 30f, "How close an enemy has to be before any isolation counts as danger.");
EnemyCrowd = config.Bind<int>("Danger", "EnemyCrowd", 3, "Enemies inside the radius that count as being fully inside their formation.");
FormationSuppression = config.Bind<float>("Formation", "FormationSuppression", 0.9f, "Fraction of the raw isolation signal removed while a player is in formation.");
LineRadius = config.Bind<float>("Formation", "LineRadius", 10f, "Radius searched for formation mates.");
LineMinMates = config.Bind<int>("Formation", "LineMinMates", 2, "Mates needed before the line fit is attempted.");
LineMaxMates = config.Bind<int>("Formation", "LineMaxMates", 6, "Most mates fed into the line fit.");
LineResidual = config.Bind<float>("Formation", "LineResidual", 2f, "Metres of spread either side of the best-fit line still counted as a line.");
ClusterMinMates = config.Bind<int>("Formation", "ClusterMinMates", 3, "Mates within ClusterFormationRadius that count as a square or skirmisher knot.");
ClusterFormationRadius = config.Bind<float>("Formation", "ClusterFormationRadius", 8f, "Radius for the tight cluster test.");
RingThreshold = config.Bind<int>("Flagging", "RingThreshold", 40, "Isolation score at which a marker appears. Tracks the live score, so it clears as a player returns.");
RamboThreshold = config.Bind<int>("Flagging", "RamboThreshold", 75, "Isolation score at which the dwell timer starts running.");
RamboHoldSeconds = config.Bind<float>("Flagging", "RamboHoldSeconds", 5f, "Seconds above the threshold before a player is flagged.");
ScoreCavalry = config.Bind<bool>("Flagging", "ScoreCavalry", false, "Score cavalry too. Off by default since cavalry operating apart is not a rambo.");
ExemptClasses = config.Bind<string>("Flagging", "ExemptClasses", "", "Comma-separated PlayerClass names that are never flagged, e.g. Surgeon,Sapper.");
ShowRings = config.Bind<bool>("Display", "ShowRings", true, "Draw a ground ring under each watched player.");
ShowLabels = config.Bind<bool>("Display", "ShowLabels", true, "Draw the floating name and score label over each watched player.");
ShowCornerList = config.Bind<bool>("Display", "ShowCornerList", true, "List watched players in the corner with their scores and distance.");
ShowOwnScore = config.Bind<bool>("Display", "ShowOwnScore", true, "Your own scores plus the raw distances behind them. The fastest way to pick thresholds.");
MaxLabels = config.Bind<int>("Display", "MaxLabels", 12, "Most floating labels drawn at once, nearest first.");
ToggleKey = config.Bind<string>("Display", "ToggleKey", "F6", "Key that hides and shows the HUD. Any UnityEngine.KeyCode name.");
StartHudVisible = config.Bind<bool>("Display", "StartHudVisible", false, "Whether the HUD starts visible when the game launches. After that the toggle sticks until you quit.");
_stamp = Stamp();
}
public static void PollForExternalEdits()
{
if (_config != null && !(Time.unscaledTime < _nextCheck))
{
_nextCheck = Time.unscaledTime + 1f;
DateTime dateTime = Stamp();
if (!(dateTime == _stamp))
{
_stamp = dateTime;
_config.Reload();
}
}
}
private static DateTime Stamp()
{
try
{
return File.GetLastWriteTimeUtc(_config.ConfigFilePath);
}
catch (Exception)
{
return _stamp;
}
}
public static bool IsExempt(PlayerClass playerClass)
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
RefreshExemptSet();
return ExemptSet.Contains(playerClass);
}
private static void RefreshExemptSet()
{
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
string text = ExemptClasses.Value ?? string.Empty;
if (text == _exemptSource)
{
return;
}
_exemptSource = text;
ExemptSet.Clear();
string[] array = text.Split(',');
for (int i = 0; i < array.Length; i++)
{
string text2 = array[i].Trim();
if (text2.Length != 0)
{
try
{
ExemptSet.Add((PlayerClass)Enum.Parse(typeof(PlayerClass), text2, ignoreCase: true));
}
catch (Exception)
{
Log.Warn("Unknown PlayerClass in ExemptClasses: " + text2);
}
}
}
}
public static KeyCode ResolveToggleKey()
{
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_009a: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_009d: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
string text = ToggleKey.Value ?? string.Empty;
if (text == _toggleKeySource)
{
return _toggleKey;
}
_toggleKeySource = text;
_toggleKey = (KeyCode)287;
string text2 = text.Trim();
if (text2.Length == 0)
{
return _toggleKey;
}
try
{
_toggleKey = (KeyCode)Enum.Parse(typeof(KeyCode), text2, ignoreCase: true);
}
catch (Exception)
{
Log.Warn("ToggleKey '" + text2 + "' is not a KeyCode name. Falling back to F6.");
}
return _toggleKey;
}
}
}