using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Managing.Client;
using FishNet.Managing.Server;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace HowToFishStats;
internal enum DeduplicationResult
{
NewEvent,
Duplicate,
PromoteToExact
}
internal sealed class CrossSourceDeduplicator
{
private sealed class RecentObservation
{
internal float Time;
internal Confidence Confidence;
internal readonly HashSet<string> Sources = new HashSet<string>(StringComparer.Ordinal);
}
private readonly float _windowSeconds;
private readonly Dictionary<string, List<RecentObservation>> _recent = new Dictionary<string, List<RecentObservation>>(StringComparer.Ordinal);
internal CrossSourceDeduplicator(float windowSeconds)
{
_windowSeconds = windowSeconds;
}
internal DeduplicationResult Observe(string signature, string source, Confidence confidence, float now)
{
if (!_recent.TryGetValue(signature, out var value))
{
value = new List<RecentObservation>();
_recent.Add(signature, value);
}
value.RemoveAll((RecentObservation observation) => now - observation.Time > _windowSeconds);
foreach (RecentObservation item in value)
{
if (!item.Sources.Contains(source) && CanRepresentSameEvent(item.Sources, source))
{
item.Sources.Add(source);
if (item.Confidence == Confidence.Inferred && confidence == Confidence.Exact)
{
item.Confidence = confidence;
return DeduplicationResult.PromoteToExact;
}
return DeduplicationResult.Duplicate;
}
}
RecentObservation recentObservation = new RecentObservation
{
Time = now,
Confidence = confidence
};
recentObservation.Sources.Add(source);
value.Add(recentObservation);
if (_recent.Count > 1024)
{
List<string> list = new List<string>();
foreach (KeyValuePair<string, List<RecentObservation>> item2 in _recent)
{
item2.Value.RemoveAll((RecentObservation observation) => now - observation.Time > 10f);
if (item2.Value.Count == 0)
{
list.Add(item2.Key);
}
}
foreach (string item3 in list)
{
_recent.Remove(item3);
}
}
return DeduplicationResult.NewEvent;
}
private static bool CanRepresentSameEvent(IEnumerable<string> existingSources, string incomingSource)
{
string text = Channel(incomingSource);
foreach (string existingSource in existingSources)
{
string text2 = Channel(existingSource);
if ((text2 == "server-hit" && text == "observer-hit") || (text2 == "observer-hit" && text == "server-hit") || (text2 == "money-authority" && text == "money-observer") || (text2 == "money-observer" && text == "money-authority") || (text2 == "bait" && text == "rod-state") || (text2 == "rod-state" && text == "bait"))
{
return true;
}
}
return false;
}
private static string Channel(string source)
{
switch (source)
{
case "Server.HitCreature":
case "Server.HitPlayer":
return "server-hit";
case "Creature.ObserverHit":
case "Creature.ObserverExplosionHit":
case "PlayerVitals.ObserverHit":
return "observer-hit";
case "MoneyManager.AddMoney":
case "MoneyManager.RemoveMoney":
case "MoneyManager.SellItem":
return "money-authority";
case "money_correlation":
return "money-observer";
case "Bait.AddItemOnBait":
return "bait";
case "Item.OnAttachedRodChange":
return "rod-state";
default:
return source ?? string.Empty;
}
}
}
internal sealed class PendingFishingCatch
{
internal object Player;
internal object Item;
internal bool Authoritative;
}
internal sealed class FishingCatchTracker
{
private readonly Dictionary<string, PendingFishingCatch> _pending = new Dictionary<string, PendingFishingCatch>(StringComparer.Ordinal);
internal void Begin(string rodKey, object player, object item, bool authoritative)
{
if (!string.IsNullOrEmpty(rodKey))
{
_pending[rodKey] = new PendingFishingCatch
{
Player = player,
Item = item,
Authoritative = authoritative
};
}
}
internal bool TryConfirm(string rodKey, out PendingFishingCatch pending)
{
pending = null;
if (string.IsNullOrEmpty(rodKey) || !_pending.TryGetValue(rodKey, out pending))
{
return false;
}
_pending.Remove(rodKey);
return true;
}
internal bool Cancel(string rodKey)
{
if (!string.IsNullOrEmpty(rodKey))
{
return _pending.Remove(rodKey);
}
return false;
}
}
internal static class FishingLandingRule
{
internal const float RequiredSecondsAboveWater = 0.2f;
internal static bool IsReached(float currentSeconds, float fixedDeltaSeconds)
{
return currentSeconds + fixedDeltaSeconds >= 0.2f;
}
}
internal sealed class GameIntrospection
{
private readonly Type _playerType = AccessTools.TypeByName("Player");
internal bool HasPlayerType => _playerType != null;
internal IList<object> FindPlayers()
{
List<object> list = new List<object>();
if (_playerType == null)
{
return list;
}
try
{
Object[] array = Object.FindObjectsOfType(_playerType);
foreach (Object val in array)
{
if (val != (Object)null)
{
list.Add(val);
}
}
}
catch (Exception ex)
{
Plugin.LogWarning("Player discovery failed: " + ex.Message);
}
return list;
}
internal PlayerIdentity GetPlayerIdentity(object player)
{
if (IsNull(player))
{
return null;
}
ulong num = ToUInt64(GetMember(player, "SteamID"));
string text = Convert.ToString(GetMember(player, "SteamName"), CultureInfo.InvariantCulture);
string objectId = GetObjectId(player);
if (string.IsNullOrWhiteSpace(text))
{
text = ((num != 0L) ? ("Steam " + num.ToString(CultureInfo.InvariantCulture)) : ("Player " + objectId));
}
return new PlayerIdentity
{
Key = ((num != 0L) ? ("steam:" + num.ToString(CultureInfo.InvariantCulture)) : ("network:" + objectId)),
SteamId = num,
Name = text
};
}
internal object GetPlayerFromVitals(object playerVitals)
{
return GetMember(playerVitals, "_player") ?? GetMember(playerVitals, "Player");
}
internal object GetPlayerFromDying(object playerDying)
{
return GetMember(playerDying, "_player") ?? GetMember(playerDying, "Player");
}
internal object GetFishingRodFromBait(object bait)
{
return GetMember(bait, "FishingRod") ?? GetMember(bait, "_fishingRod");
}
internal object GetPlayerFromBait(object bait)
{
return GetPlayerFromRod(GetFishingRodFromBait(bait));
}
internal object GetPlayerFromRod(object rod)
{
return GetMember(rod, "Holder") ?? GetMember(rod, "SyncedHolder") ?? GetMember(rod, "LastHolder") ?? GetMember(rod, "LastPlayer");
}
internal object GetAttachedRod(object item)
{
return GetMember(item, "AttachedRod") ?? GetMember(item, "_rodAttachedTo");
}
internal object GetItemFromBait(object bait)
{
return GetMember(bait, "ServerItemOnBait") ?? GetMember(bait, "ItemOnBait");
}
internal object GetItemFromRod(object rod)
{
return GetItemFromBait(GetMember(rod, "Bait") ?? GetMember(rod, "_bait"));
}
internal bool IsBaitAtLandingThreshold(object bait)
{
object member = GetMember(bait, "_timeAboveWater");
float currentSeconds;
try
{
currentSeconds = ((member == null) ? 0f : Convert.ToSingle(member, CultureInfo.InvariantCulture));
}
catch
{
return false;
}
return FishingLandingRule.IsReached(currentSeconds, Time.fixedDeltaTime);
}
internal bool IsSameObject(object first, object second)
{
if (IsNull(first) || IsNull(second))
{
return false;
}
if (first != second)
{
return string.Equals(GetObjectId(first), GetObjectId(second), StringComparison.Ordinal);
}
return true;
}
internal bool IsFishItem(object item)
{
if (IsNull(item))
{
return false;
}
return !IsNull(GetMember(item, "Fish"));
}
internal int GetItemWorth(object item)
{
object member = GetMember(item, "TotalWorth");
try
{
return (member != null) ? Convert.ToInt32(member, CultureInfo.InvariantCulture) : 0;
}
catch
{
return 0;
}
}
internal object GetItemLastHolder(object item)
{
return GetMember(item, "LastHolder") ?? GetMember(item, "LastPlayer") ?? GetMember(item, "Holder");
}
internal object GetDeadPlayerHolder(object deadPlayer)
{
return GetMember(deadPlayer, "Holder") ?? GetMember(deadPlayer, "_holder") ?? GetMember(deadPlayer, "SyncedHolder");
}
internal bool IsPlayerDead(object player)
{
if (IsNull(player))
{
return false;
}
object target = GetMember(player, "Vitals") ?? GetMember(player, "_vitals");
object obj = GetMember(target, "Health") ?? GetMember(target, "_health") ?? GetMember(target, "_syncedHealth");
try
{
return obj != null && Convert.ToInt32(obj, CultureInfo.InvariantCulture) <= 0;
}
catch
{
return false;
}
}
internal bool IsPlayerAlive(object player)
{
if (IsNull(player))
{
return false;
}
object target = GetMember(player, "Vitals") ?? GetMember(player, "_vitals");
object obj = GetMember(target, "Health") ?? GetMember(target, "_health") ?? GetMember(target, "_syncedHealth");
try
{
return obj != null && Convert.ToInt32(obj, CultureInfo.InvariantCulture) > 0;
}
catch
{
return false;
}
}
internal string GetItemName(object item)
{
if (IsNull(item))
{
return string.Empty;
}
try
{
MethodInfo methodInfo = AccessTools.Method(item.GetType(), "GetName", Type.EmptyTypes, (Type[])null);
if (methodInfo != null)
{
string text = Convert.ToString(methodInfo.Invoke(item, null), CultureInfo.InvariantCulture);
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}
}
}
catch
{
}
Object val = (Object)((item is Object) ? item : null);
if (val != (Object)null && !string.IsNullOrWhiteSpace(val.name))
{
return val.name;
}
return item.GetType().Name;
}
internal string GetObjectId(object value)
{
if (IsNull(value))
{
return string.Empty;
}
object member = GetMember(value, "ObjectId");
if (member != null)
{
return "net:" + Convert.ToString(member, CultureInfo.InvariantCulture);
}
Object val = (Object)((value is Object) ? value : null);
if (val != (Object)null)
{
return "unity:" + val.GetInstanceID().ToString(CultureInfo.InvariantCulture);
}
return "runtime:" + value.GetHashCode().ToString(CultureInfo.InvariantCulture);
}
internal bool IsServerObject(object value)
{
object obj = GetMember(value, "IsServerStarted") ?? GetMember(value, "IsServerInitialized");
if (obj is bool)
{
return (bool)obj;
}
Type type = AccessTools.TypeByName("FishNet.InstanceFinder");
object obj2 = GetStaticMember(type, "IsServerStarted") ?? GetStaticMember(type, "IsServer");
if (obj2 is bool)
{
return (bool)obj2;
}
return false;
}
internal bool IsOwnedByLocalClient(object player)
{
object member = GetMember(GetMember(player, "Owner"), "IsLocalClient");
if (member is bool)
{
return (bool)member;
}
return false;
}
internal static bool IsNull(object value)
{
if (value == null)
{
return true;
}
Object val = (Object)((value is Object) ? value : null);
if (val != null)
{
return val == (Object)null;
}
return false;
}
private static object GetMember(object target, string name)
{
return ReflectionMemberAccessor.GetValue(target, name);
}
private static object GetStaticMember(Type type, string name)
{
return ReflectionMemberAccessor.GetStaticValue(type, name);
}
private static ulong ToUInt64(object value)
{
if (value == null)
{
return 0uL;
}
try
{
return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
}
catch
{
return 0uL;
}
}
}
internal sealed class HudPanelState
{
internal MetricKind Metric;
internal float X;
internal float Y;
internal float Width;
internal float Height;
}
internal sealed class HudPanelCollection
{
internal const int MaximumPanels = 5;
internal const float CompactDefaultWidthPixels = 320f;
internal const float CompactDefaultHeightPixels = 158f;
internal const float CompactDefaultMarginPixels = 12f;
private const float ScreenEdge = 0.002f;
private const float MinimumPanelSize = 0.07f;
private readonly List<HudPanelState> _panels = new List<HudPanelState>();
internal IList<HudPanelState> Panels => _panels;
internal int SelectedIndex { get; private set; }
internal bool NeedsCompactDefault { get; private set; }
internal HudPanelCollection(string serialized)
{
Parse(serialized);
if (_panels.Count == 0)
{
_panels.Add(DefaultPanel());
NeedsCompactDefault = true;
return;
}
NeedsCompactDefault = IsLegacyDefault(_panels) || IsBrokenStartupDefault(_panels);
if (_panels.Count > 1)
{
PackAttached();
}
}
internal bool ApplyCompactDefault(float screenWidth, float screenHeight)
{
if (!NeedsCompactDefault || _panels.Count != 1)
{
return false;
}
if (screenWidth < 320f || screenHeight < 158f)
{
return false;
}
HudPanelState hudPanelState = _panels[0];
hudPanelState.Width = Math.Max(0.07f, Math.Min(0.8f, 320f / screenWidth));
hudPanelState.Height = Math.Max(0.07f, Math.Min(0.8f, 158f / screenHeight));
hudPanelState.X = Math.Max(0.002f, 1f - hudPanelState.Width - 12f / screenWidth);
hudPanelState.Y = Math.Max(0.002f, 12f / screenHeight);
NeedsCompactDefault = false;
return true;
}
internal bool Select(int index)
{
if (index < 0 || index >= _panels.Count)
{
return false;
}
SelectedIndex = index;
return true;
}
internal bool SetMetric(int index, MetricKind metric)
{
if (!Select(index))
{
return false;
}
_panels[index].Metric = MetricCatalog.NormalizeVisible(metric);
return true;
}
internal bool MoveAll(float deltaX, float deltaY)
{
if (_panels.Count == 0)
{
return false;
}
float num = _panels.Min((HudPanelState panel) => panel.X);
float num2 = _panels.Min((HudPanelState panel) => panel.Y);
float num3 = _panels.Max((HudPanelState panel) => panel.X + panel.Width);
float num4 = _panels.Max((HudPanelState panel) => panel.Y + panel.Height);
deltaX = Math.Max(0.002f - num, Math.Min(0.998f - num3, deltaX));
deltaY = Math.Max(0.002f - num2, Math.Min(0.998f - num4, deltaY));
if (Math.Abs(deltaX) < 1E-06f && Math.Abs(deltaY) < 1E-06f)
{
return false;
}
foreach (HudPanelState panel in _panels)
{
panel.X += deltaX;
panel.Y += deltaY;
}
return true;
}
internal bool ResizeAll(float requestedWidth, float requestedHeight)
{
if (_panels.Count == 0)
{
return false;
}
HudPanelState hudPanelState = _panels[0];
float val = 0.998f - hudPanelState.X;
float val2 = (0.998f - hudPanelState.Y) / (float)_panels.Count;
float width = Math.Max(0.07f, Math.Min(Math.Min(0.8f, val), requestedWidth));
float height = Math.Max(0.07f, Math.Min(Math.Min(0.8f, val2), requestedHeight));
foreach (HudPanelState panel in _panels)
{
panel.Width = width;
panel.Height = height;
}
PackAttached();
return true;
}
internal bool AddAfterSelected()
{
return AddAfter(SelectedIndex);
}
internal bool AddAfter(int index)
{
if (_panels.Count >= 5 || index < 0 || index >= _panels.Count)
{
return false;
}
HudPanelState hudPanelState = _panels[index];
HudPanelState item = new HudPanelState
{
Metric = MetricCatalog.Cycle(hudPanelState.Metric, 1),
X = hudPanelState.X,
Y = hudPanelState.Y + hudPanelState.Height,
Width = hudPanelState.Width,
Height = hudPanelState.Height
};
_panels.Insert(index + 1, item);
SelectedIndex = index + 1;
FitHeightAndPackAttached();
return true;
}
internal bool RemoveSelected()
{
return RemoveNewest();
}
internal bool RemoveNewest()
{
if (_panels.Count <= 1)
{
return false;
}
_panels.RemoveAt(_panels.Count - 1);
SelectedIndex = Math.Min(SelectedIndex, _panels.Count - 1);
PackAttached();
return true;
}
internal bool Remove(int index)
{
if (_panels.Count <= 1 || index < 0 || index >= _panels.Count)
{
return false;
}
_panels.RemoveAt(index);
SelectedIndex = Math.Min(index, _panels.Count - 1);
PackAttached();
return true;
}
internal string Serialize()
{
return string.Join(";", _panels.Select(delegate(HudPanelState panel)
{
string[] array = new string[5];
int metric = (int)panel.Metric;
array[0] = metric.ToString(CultureInfo.InvariantCulture);
array[1] = panel.X.ToString("0.#####", CultureInfo.InvariantCulture);
array[2] = panel.Y.ToString("0.#####", CultureInfo.InvariantCulture);
array[3] = panel.Width.ToString("0.#####", CultureInfo.InvariantCulture);
array[4] = panel.Height.ToString("0.#####", CultureInfo.InvariantCulture);
return string.Join(",", array);
}).ToArray());
}
private void Parse(string serialized)
{
if (string.IsNullOrWhiteSpace(serialized))
{
return;
}
string[] array = serialized.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < array.Length; i++)
{
string[] array2 = array[i].Split(new char[1] { ',' });
if ((array2.Length == 5 || array2.Length == 7) && int.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && float.TryParse(array2[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3) && float.TryParse(array2[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4) && float.TryParse(array2[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result5) && MetricCatalog.IsDefined(result))
{
MetricKind metric = MetricCatalog.NormalizeVisible((MetricKind)result);
_panels.Add(new HudPanelState
{
Metric = metric,
X = Clamp01(result2),
Y = Clamp01(result3),
Width = Math.Max(0.07f, Math.Min(0.8f, result4)),
Height = Math.Max(0.07f, Math.Min(0.8f, result5))
});
if (_panels.Count == 5)
{
break;
}
}
}
}
private static HudPanelState DefaultPanel()
{
return new HudPanelState
{
Metric = MetricKind.CreatureDamage,
X = 0.765f,
Y = 0.02f,
Width = 0.22f,
Height = 0.3f
};
}
private void FitHeightAndPackAttached()
{
if (_panels.Count == 0)
{
return;
}
float val = (0.998f - _panels[0].Y) / (float)_panels.Count;
float height = Math.Max(0.07f, Math.Min(_panels[0].Height, val));
foreach (HudPanelState panel in _panels)
{
panel.Height = height;
}
PackAttached();
}
private void PackAttached()
{
if (_panels.Count != 0)
{
float x = _panels[0].X;
float num = _panels.Sum((HudPanelState panel) => panel.Height);
float num2 = Math.Max(0.002f, Math.Min(_panels[0].Y, 0.998f - num));
for (int num3 = 0; num3 < _panels.Count; num3++)
{
_panels[num3].X = x;
_panels[num3].Y = num2;
num2 += _panels[num3].Height;
}
}
}
private static bool IsLegacyDefault(IList<HudPanelState> panels)
{
if (panels.Count != 1)
{
return false;
}
HudPanelState hudPanelState = panels[0];
if (hudPanelState.Metric == MetricKind.CreatureDamage && Approximately(hudPanelState.X, 0.765f) && Approximately(hudPanelState.Y, 0.02f) && Approximately(hudPanelState.Width, 0.22f))
{
return Approximately(hudPanelState.Height, 0.3f);
}
return false;
}
private static bool IsBrokenStartupDefault(IList<HudPanelState> panels)
{
if (panels.Count != 1)
{
return false;
}
HudPanelState hudPanelState = panels[0];
if (hudPanelState.Metric == MetricKind.CreatureDamage && Approximately(hudPanelState.X, 0.002f) && Approximately(hudPanelState.Y, 1f) && Approximately(hudPanelState.Width, 0.8f))
{
return Approximately(hudPanelState.Height, 0.8f);
}
return false;
}
private static bool Approximately(float first, float second)
{
return Math.Abs(first - second) < 1E-05f;
}
private static float Clamp01(float value)
{
return Math.Max(0f, Math.Min(1f, value));
}
}
internal static class MetricCatalog
{
internal static readonly MetricKind[] Visible = new MetricKind[8]
{
MetricKind.CreatureDamage,
MetricKind.PlayerDamage,
MetricKind.MoneyEarned,
MetricKind.MoneySpent,
MetricKind.FishCaught,
MetricKind.TeammatesRevived,
MetricKind.PlayerKills,
MetricKind.PlayerDeaths
};
internal static MetricKind NormalizeVisible(MetricKind metric)
{
if (IndexOf(metric) < 0)
{
return MetricKind.PlayerDeaths;
}
return metric;
}
internal static MetricKind Cycle(MetricKind current, int delta)
{
int num = IndexOf(NormalizeVisible(current));
int num2 = Visible.Length;
return Visible[(num + delta % num2 + num2) % num2];
}
internal static bool IsDefined(int value)
{
return Enum.IsDefined(typeof(MetricKind), value);
}
private static int IndexOf(MetricKind metric)
{
for (int i = 0; i < Visible.Length; i++)
{
if (Visible[i] == metric)
{
return i;
}
}
return -1;
}
}
internal enum MetricKind
{
CreatureDamage,
PlayerDamage,
MoneyEarned,
MoneySpent,
FishCaught,
TimesRevived,
TeammatesRevived,
PlayerKills,
PlayerDeaths
}
internal enum Confidence
{
Exact,
Inferred,
Corrected,
Unknown
}
internal sealed class PlayerIdentity
{
internal string Key;
internal ulong SteamId;
internal string Name;
}
internal sealed class MetricValue
{
internal long Exact;
internal long Inferred;
internal long Total => Exact + Inferred;
}
internal sealed class PlayerStats
{
internal string Key;
internal ulong SteamId;
internal string Name;
internal bool IsPresent;
internal DateTime LastSeenUtc;
internal readonly Dictionary<MetricKind, MetricValue> Metrics = new Dictionary<MetricKind, MetricValue>();
internal PlayerStats()
{
foreach (MetricKind value in Enum.GetValues(typeof(MetricKind)))
{
Metrics[value] = new MetricValue();
}
}
}
internal sealed class PlayerMetricRow
{
internal string Key;
internal ulong SteamId;
internal string Name;
internal bool IsPresent;
internal long Exact;
internal long Inferred;
internal long Total => Exact + Inferred;
}
internal sealed class PatchRegistrar
{
private readonly Harmony _harmony;
private readonly List<string> _missing = new List<string>();
private int _patched;
internal PatchRegistrar(Harmony harmony)
{
_harmony = harmony;
}
internal string ApplyAll()
{
PatchPrefix("Creature", "RpcLogic___ObserverHit___", "CreatureObserverHit", required: true);
PatchPrefix("Creature", "RpcLogic___ObserverExplosionHit___", "CreatureObserverExplosionHit", required: true);
PatchPrefix("PlayerVitals", "RpcLogic___ObserverHit___", "PlayerObserverHit", required: true);
PatchPrefix("Server", "RpcLogic___HitCreature___", "ServerHitCreature", required: false);
PatchNamePrefixWithState("Server", "RpcLogic___HitPlayer___", "ServerHitPlayerPrefix", "ServerHitPlayerPostfix", required: false);
PatchExact("MoneyManager", "AddMoney", 2, "AddMoney", required: false);
PatchExact("MoneyManager", "RemoveMoney", 2, "RemoveMoney", required: false);
PatchExact("MoneyManager", "SellItem", 1, "SellItem", required: true);
PatchExact("MoneyManager", "OnChangeMoney", 3, "MoneyChanged", required: false);
PatchPrefix("MoneyManager", "RpcLogic___ObserverMoneySound___", "MoneyCue", required: false);
PatchExact("CreatureManager", "HookItem", 2, "CreatureManagerHookItem", required: true);
PatchExact("Item", "InitializeBait", 1, "ItemInitializeBait", required: false);
PatchExactPrefix("Bait", "IncreaseAboveWaterTime", 0, "BaitIncreaseAboveWaterTime", required: true);
PatchExactPrefix("Item", "PickUp", 3, "ItemPickUp", required: false);
PatchExactPrefix("FishingRod", "ReleaseItem", 1, "FishingRodReleaseItem", required: true);
PatchNamePrefixAsPrefix("Server", "RpcLogic___ResurrectPlayer___", "ServerResurrectPlayer", required: true);
PatchExact("PlayerDying", "ServerDie", 1, "PlayerDied", required: true);
if (_missing.Count != 0)
{
return _patched + " collectors active; missing: " + string.Join(", ", _missing.ToArray());
}
return "all " + _patched + " collectors active";
}
private void PatchPrefix(string typeName, string methodPrefix, string callback, bool required)
{
Type type = AccessTools.TypeByName(typeName);
MethodInfo[] array = ((type == null) ? new MethodInfo[0] : (from method in type.GetMethods(AccessTools.all)
where method.Name.StartsWith(methodPrefix, StringComparison.Ordinal)
select method).ToArray());
if (array.Length == 0)
{
Missing(typeName + "." + methodPrefix, required);
return;
}
MethodInfo[] array2 = array;
foreach (MethodInfo target in array2)
{
Patch(target, callback);
}
}
private void PatchExact(string typeName, string methodName, int parameterCount, string callback, bool required)
{
Type type = AccessTools.TypeByName(typeName);
MethodInfo methodInfo = ((type == null) ? null : type.GetMethods(AccessTools.all).FirstOrDefault((MethodInfo candidate) => candidate.Name == methodName && candidate.GetParameters().Length == parameterCount));
if (methodInfo == null)
{
Missing(typeName + "." + methodName, required);
}
else
{
Patch(methodInfo, callback);
}
}
private void PatchExactPrefix(string typeName, string methodName, int parameterCount, string callback, bool required)
{
//IL_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Expected O, but got Unknown
Type type = AccessTools.TypeByName(typeName);
MethodInfo methodInfo = ((type == null) ? null : type.GetMethods(AccessTools.all).FirstOrDefault((MethodInfo candidate) => candidate.Name == methodName && candidate.GetParameters().Length == parameterCount));
if (methodInfo == null)
{
Missing(typeName + "." + methodName, required);
return;
}
try
{
MethodInfo methodInfo2 = AccessTools.Method(typeof(PatchCallbacks), callback, (Type[])null, (Type[])null);
_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_patched++;
Plugin.LogDebug("Patched " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + " (prefix)");
}
catch (Exception ex)
{
_missing.Add(methodInfo.DeclaringType.FullName + "." + methodInfo.Name);
Plugin.LogWarning("Patch failed for " + methodInfo.Name + ": " + ex.Message);
}
}
private void PatchNamePrefixAsPrefix(string typeName, string methodPrefix, string callback, bool required)
{
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_00a1: Expected O, but got Unknown
Type type = AccessTools.TypeByName(typeName);
MethodInfo[] array = ((type == null) ? new MethodInfo[0] : (from method in type.GetMethods(AccessTools.all)
where method.Name.StartsWith(methodPrefix, StringComparison.Ordinal)
select method).ToArray());
if (array.Length == 0)
{
Missing(typeName + "." + methodPrefix, required);
return;
}
MethodInfo[] array2 = array;
foreach (MethodInfo methodInfo in array2)
{
try
{
MethodInfo methodInfo2 = AccessTools.Method(typeof(PatchCallbacks), callback, (Type[])null, (Type[])null);
_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_patched++;
Plugin.LogDebug("Patched " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + " (prefix)");
}
catch (Exception ex)
{
_missing.Add(methodInfo.DeclaringType.FullName + "." + methodInfo.Name);
Plugin.LogWarning("Patch failed for " + methodInfo.Name + ": " + ex.Message);
}
}
}
private void PatchNamePrefixWithState(string typeName, string methodPrefix, string prefixCallback, string postfixCallback, bool required)
{
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00af: Unknown result type (might be due to invalid IL or missing references)
//IL_00bc: Expected O, but got Unknown
//IL_00bc: Expected O, but got Unknown
Type type = AccessTools.TypeByName(typeName);
MethodInfo[] array = ((type == null) ? new MethodInfo[0] : (from method in type.GetMethods(AccessTools.all)
where method.Name.StartsWith(methodPrefix, StringComparison.Ordinal)
select method).ToArray());
if (array.Length == 0)
{
Missing(typeName + "." + methodPrefix, required);
return;
}
MethodInfo[] array2 = array;
foreach (MethodInfo methodInfo in array2)
{
try
{
MethodInfo methodInfo2 = AccessTools.Method(typeof(PatchCallbacks), prefixCallback, (Type[])null, (Type[])null);
MethodInfo methodInfo3 = AccessTools.Method(typeof(PatchCallbacks), postfixCallback, (Type[])null, (Type[])null);
_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_patched++;
Plugin.LogDebug("Patched " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + " (state transition)");
}
catch (Exception ex)
{
_missing.Add(methodInfo.DeclaringType.FullName + "." + methodInfo.Name);
Plugin.LogWarning("Patch failed for " + methodInfo.Name + ": " + ex.Message);
}
}
}
private void Patch(MethodInfo target, string callback)
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Expected O, but got Unknown
try
{
MethodInfo methodInfo = AccessTools.Method(typeof(PatchCallbacks), callback, (Type[])null, (Type[])null);
_harmony.Patch((MethodBase)target, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_patched++;
Plugin.LogDebug("Patched " + target.DeclaringType.FullName + "." + target.Name);
}
catch (Exception ex)
{
_missing.Add(target.DeclaringType.FullName + "." + target.Name);
Plugin.LogWarning("Patch failed for " + target.Name + ": " + ex.Message);
}
}
private void Missing(string name, bool required)
{
_missing.Add(name + (required ? " (required)" : string.Empty));
Plugin.LogWarning("Collector entry not found: " + name);
}
}
internal static class PatchCallbacks
{
internal static void CreatureObserverHit(object __instance, object[] __args)
{
Safe(delegate
{
Plugin.Current.Ledger.RecordCreatureDamage(Arg(__args, 0), __instance, IntArg(__args, 3), "Creature.ObserverHit", Confidence.Exact);
});
}
internal static void CreatureObserverExplosionHit(object __instance, object[] __args)
{
Safe(delegate
{
Plugin.Current.Ledger.RecordCreatureDamage(Arg(__args, 0), __instance, IntArg(__args, 2), "Creature.ObserverExplosionHit", Confidence.Exact);
});
}
internal static void PlayerObserverHit(object __instance, object[] __args)
{
Safe(delegate
{
object playerFromVitals = Plugin.Current.Game.GetPlayerFromVitals(__instance);
Plugin.Current.Ledger.RecordPlayerDamage(Arg(__args, 0), playerFromVitals, IntArg(__args, 3), "PlayerVitals.ObserverHit", Confidence.Exact, Convert.ToString(Arg(__args, 4)));
});
}
internal static void ServerHitCreature(object __instance, object[] __args)
{
Safe(delegate
{
Plugin.Current.Ledger.RecordCreatureDamage(Arg(__args, 1), Arg(__args, 0), IntArg(__args, 2), "Server.HitCreature", Confidence.Exact);
});
}
internal static void ServerHitPlayerPrefix(object[] __args, ref PlayerHitState __state)
{
try
{
object obj = Arg(__args, 0);
__state = new PlayerHitState
{
Victim = obj,
Attacker = Arg(__args, 5),
WasAlive = ((Object)(object)Plugin.Current != (Object)null && Plugin.Current.Game != null && Plugin.Current.Game.IsPlayerAlive(obj))
};
}
catch (Exception ex)
{
__state = null;
Plugin.LogWarning("Player kill transition prefix failed: " + ex.Message);
}
}
internal static void ServerHitPlayerPostfix(object __instance, object[] __args, PlayerHitState __state)
{
Safe(delegate
{
Plugin.Current.Ledger.RecordPlayerDamage(Arg(__args, 5), Arg(__args, 0), IntArg(__args, 1), "Server.HitPlayer", Confidence.Exact, Convert.ToString(Arg(__args, 4)));
bool hasAttacker = __state != null && !GameIntrospection.IsNull(__state.Attacker);
bool isDeadAfter = __state != null && Plugin.Current.Game.IsPlayerDead(__state.Victim);
if (__state != null && PlayerKillTransitionRule.IsPlayerKill(__state.WasAlive, isDeadAfter, hasAttacker))
{
Plugin.Current.Ledger.RecordPlayerKill(__state.Attacker, __state.Victim, "Server.HitPlayerLethalTransition");
}
});
}
internal static void AddMoney(object __instance, object[] __args)
{
Safe(delegate
{
if (Plugin.Current.Game.IsServerObject(__instance))
{
Plugin.Current.Ledger.RecordAuthoritativeMoney(Arg(__args, 1), Math.Abs(IntArg(__args, 0)), increase: true, "MoneyManager.AddMoney");
}
});
}
internal static void RemoveMoney(object __instance, object[] __args)
{
Safe(delegate
{
if (Plugin.Current.Game.IsServerObject(__instance))
{
Plugin.Current.Ledger.RecordAuthoritativeMoney(Arg(__args, 1), Math.Abs(IntArg(__args, 0)), increase: false, "MoneyManager.RemoveMoney");
}
});
}
internal static void SellItem(object __instance, object[] __args)
{
Safe(delegate
{
object item = Arg(__args, 0);
if (Plugin.Current.Game.IsServerObject(__instance))
{
Plugin.Current.Ledger.RecordAuthoritativeMoney(Plugin.Current.Game.GetItemLastHolder(item), Math.Abs(Plugin.Current.Game.GetItemWorth(item)), increase: true, "MoneyManager.SellItem");
}
});
}
internal static void MoneyChanged(object __instance, object[] __args)
{
Safe(delegate
{
Plugin.Current.Ledger.ObserveBalanceChange(IntArg(__args, 0), IntArg(__args, 1), BoolArg(__args, 2));
});
}
internal static void MoneyCue(object __instance, object[] __args)
{
Safe(delegate
{
Plugin.Current.Ledger.ObserveMoneyCue(BoolArg(__args, 0), Arg(__args, 1));
});
}
internal static void CreatureManagerHookItem(object __instance, object[] __args)
{
Safe(delegate
{
object bait = Arg(__args, 1);
if (Plugin.Current.Game.IsServerObject(__instance))
{
object fishingRodFromBait = Plugin.Current.Game.GetFishingRodFromBait(bait);
object item = Plugin.Current.Game.GetItemFromBait(bait) ?? Arg(__args, 0);
Plugin.Current.Ledger.BeginFishingCatch(Plugin.Current.Game.GetPlayerFromRod(fishingRodFromBait), item, fishingRodFromBait, authoritative: true);
}
});
}
internal static void ItemInitializeBait(object __instance, object[] __args)
{
Safe(delegate
{
object rod = Arg(__args, 0);
object playerFromRod = Plugin.Current.Game.GetPlayerFromRod(rod);
if (!Plugin.Current.Game.IsServerObject(__instance) && Plugin.Current.Game.IsOwnedByLocalClient(playerFromRod))
{
Plugin.Current.Ledger.BeginFishingCatch(playerFromRod, __instance, rod, authoritative: false);
}
});
}
internal static void BaitIncreaseAboveWaterTime(object __instance)
{
Safe(delegate
{
if (Plugin.Current.Game.IsBaitAtLandingThreshold(__instance))
{
object fishingRodFromBait = Plugin.Current.Game.GetFishingRodFromBait(__instance);
Plugin.Current.Ledger.ConfirmFishingCatch(fishingRodFromBait, Plugin.Current.Game.GetItemFromBait(__instance), "Bait.AboveWaterLanding");
}
});
}
internal static void ItemPickUp(object __instance, object[] __args)
{
Safe(delegate
{
object attachedRod = Plugin.Current.Game.GetAttachedRod(__instance);
if (!GameIntrospection.IsNull(attachedRod))
{
Plugin.Current.Ledger.ConfirmFishingCatch(attachedRod, __instance, "Item.PickUpWhileHooked");
}
});
}
internal static void FishingRodReleaseItem(object __instance, object[] __args)
{
Safe(delegate
{
object first = Arg(__args, 0);
object itemFromRod = Plugin.Current.Game.GetItemFromRod(__instance);
if (Plugin.Current.Game.IsSameObject(first, itemFromRod))
{
Plugin.Current.Ledger.CancelFishingCatch(__instance);
}
});
}
internal static void ServerResurrectPlayer(object __instance, object[] __args)
{
Safe(delegate
{
if (Plugin.Current.Game.IsServerObject(__instance))
{
Plugin.Current.Ledger.RecordResurrection(Arg(__args, 0), Arg(__args, 1), "Server.ResurrectPlayer");
}
});
}
internal static void PlayerDied(object __instance)
{
Safe(delegate
{
if (Plugin.Current.Game.IsServerObject(__instance))
{
Plugin.Current.Ledger.RecordDeath(Plugin.Current.Game.GetPlayerFromDying(__instance), "PlayerDying.ServerDie");
}
});
}
private static object Arg(object[] args, int index)
{
if (args == null || index < 0 || index >= args.Length)
{
return null;
}
return args[index];
}
private static int IntArg(object[] args, int index)
{
object obj = Arg(args, index);
try
{
return (obj != null) ? Convert.ToInt32(obj) : 0;
}
catch
{
return 0;
}
}
private static bool BoolArg(object[] args, int index)
{
object obj = Arg(args, index);
if (obj is bool)
{
return (bool)obj;
}
return false;
}
private static void Safe(Action action)
{
try
{
if ((Object)(object)Plugin.Current != (Object)null && Plugin.Current.Ledger != null)
{
action();
}
}
catch (Exception ex)
{
Plugin.LogWarning("Collector callback failed: " + ex.Message);
}
}
}
internal sealed class PlayerHitState
{
internal object Victim;
internal object Attacker;
internal bool WasAlive;
}
internal sealed class PlayerColorAssigner
{
private readonly Dictionary<string, int> _assignments = new Dictionary<string, int>(StringComparer.Ordinal);
private int _nextIndex;
internal int GetIndex(string playerKey)
{
playerKey = playerKey ?? string.Empty;
if (_assignments.TryGetValue(playerKey, out var value))
{
return value;
}
int num = _nextIndex++;
_assignments[playerKey] = num;
return num;
}
}
internal static class PlayerKillTransitionRule
{
internal static bool IsPlayerKill(bool wasAlive, bool isDeadAfter, bool hasAttacker)
{
return wasAlive && isDeadAfter && hasAttacker;
}
}
[BepInPlugin("com.corntaxi.howtofish.checkmystats", "CheckMyStats", "1.0.0")]
[BepInProcess("How to Fish.exe")]
public sealed class Plugin : BaseUnityPlugin
{
internal const string PluginGuid = "com.corntaxi.howtofish.checkmystats";
internal const string PluginName = "CheckMyStats";
internal const string PluginVersion = "1.0.0";
private const string ExpectedAssemblyHash = "FA8C6F47874E69FE07B9C978F35CC05372DF2BDD3535DE5F5FAC355F999A5762";
private Harmony _harmony;
private StatsHud _hud;
private ConfigEntry<KeyboardShortcut> _toggleHud;
private ConfigEntry<KeyboardShortcut> _toggleMouseEdit;
private ConfigEntry<KeyboardShortcut> _addPanel;
private ConfigEntry<KeyboardShortcut> _removePanel;
private ConfigEntry<bool> _hudEnabled;
private ConfigEntry<string> _language;
private ConfigEntry<float> _hudScale;
private ConfigEntry<string> _panelLayout;
private ConfigFile _settings;
private float _nextDiscovery;
private bool _shutdown;
private StatsSyncService _sync;
internal static Plugin Current { get; private set; }
internal static ManualLogSource LogSource { get; private set; }
internal GameIntrospection Game { get; private set; }
internal StatsLedger Ledger { get; private set; }
internal string CompatibilitySummary { get; private set; }
internal bool IsFullyCompatible { get; private set; }
private void Awake()
{
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
//IL_0098: Expected O, but got Unknown
Current = this;
LogSource = ((BaseUnityPlugin)this).Logger;
BindConfiguration();
string sessionId = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N").Substring(0, 8);
Game = new GameIntrospection();
Ledger = new StatsLedger(Game, sessionId);
_sync = new StatsSyncService(Ledger);
string text = VerifyGameAssembly();
_harmony = new Harmony("com.corntaxi.howtofish.checkmystats");
string text2 = new PatchRegistrar(_harmony).ApplyAll();
CompatibilitySummary = text + "; " + text2;
IsFullyCompatible = text.StartsWith("verified", StringComparison.OrdinalIgnoreCase) && text2.StartsWith("all", StringComparison.OrdinalIgnoreCase);
_hud = new StatsHud(Ledger, UseChinese, () => _hudScale.Value, _panelLayout.Value, SaveHudLayout);
((BaseUnityPlugin)this).Logger.LogInfo((object)("CheckMyStats 1.0.0 loaded. " + CompatibilitySummary));
}
private void BindConfiguration()
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Expected O, but got Unknown
//IL_0036: 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_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
_settings = new ConfigFile(Path.Combine(Paths.ConfigPath, "CheckMyStats.cfg"), true);
_toggleMouseEdit = _settings.Bind<KeyboardShortcut>("Controls", "MouseEditKey", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Toggle mouse editing, dragging, and resizing.");
_toggleHud = _settings.Bind<KeyboardShortcut>("Controls", "ToggleHudKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Show or hide all HUD panels.");
_addPanel = _settings.Bind<KeyboardShortcut>("Controls", "AddPanelKey", new KeyboardShortcut((KeyCode)270, Array.Empty<KeyCode>()), "Add a HUD panel. Shift+= is always also supported.");
_removePanel = _settings.Bind<KeyboardShortcut>("Controls", "RemovePanelKey", new KeyboardShortcut((KeyCode)269, Array.Empty<KeyCode>()), "Remove the newest HUD panel. Main keyboard minus is also supported.");
_hudEnabled = _settings.Bind<bool>("HUD", "Enabled", true, "Show the multiplayer statistics HUD.");
_hudScale = _settings.Bind<float>("HUD", "Scale", 1f, "HUD content scale from 0.75 to 2.0.");
_language = _settings.Bind<string>("HUD", "Language", "Auto", "Auto, English, or Chinese.");
_panelLayout = _settings.Bind<string>("HUD", "PanelLayout", string.Empty, "Saved metric, position, and size for up to five panels.");
_settings.Save();
}
private void Update()
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//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_0057: 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_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
KeyboardShortcut value = _addPanel.Value;
if (((KeyboardShortcut)(ref value)).IsDown() || MainKeyboardPlusDown())
{
_hud.AddPanel();
}
value = _removePanel.Value;
if (((KeyboardShortcut)(ref value)).IsDown() || Input.GetKeyDown((KeyCode)45))
{
_hud.RemoveSelectedPanel();
}
value = _toggleMouseEdit.Value;
if (((KeyboardShortcut)(ref value)).IsDown() && _hudEnabled.Value)
{
_hud.SetEditMode(!_hud.EditMode);
}
value = _toggleHud.Value;
if (((KeyboardShortcut)(ref value)).IsDown())
{
_hudEnabled.Value = !_hudEnabled.Value;
if (!_hudEnabled.Value)
{
_hud.SetEditMode(enabled: false);
}
}
Ledger.Tick();
if (_sync != null)
{
_sync.Tick();
}
if (Time.unscaledTime >= _nextDiscovery)
{
_nextDiscovery = Time.unscaledTime + 1f;
Ledger.RefreshPlayers(Game.FindPlayers());
}
}
private void LateUpdate()
{
if (_hud != null)
{
_hud.MaintainCursor();
}
}
private void OnGUI()
{
if (_hudEnabled != null && _hudEnabled.Value && _hud != null)
{
_hud.Draw(IsFullyCompatible, CompatibilitySummary);
}
}
private void OnApplicationQuit()
{
Shutdown();
}
private void OnDestroy()
{
Shutdown();
}
private void Shutdown()
{
if (!_shutdown)
{
_shutdown = true;
if (_hud != null)
{
_hud.Shutdown();
}
if (_sync != null)
{
_sync.Shutdown();
}
if (Ledger != null)
{
Ledger.FlushPending();
}
if (_harmony != null)
{
_harmony.UnpatchSelf();
}
}
}
private bool MainKeyboardPlusDown()
{
if (Input.GetKeyDown((KeyCode)61))
{
if (!Input.GetKey((KeyCode)304))
{
return Input.GetKey((KeyCode)303);
}
return true;
}
return false;
}
private void SaveHudLayout(string serialized)
{
if (_panelLayout != null && !string.Equals(_panelLayout.Value, serialized, StringComparison.Ordinal))
{
_panelLayout.Value = serialized;
if (_settings != null)
{
_settings.Save();
}
}
}
private string VerifyGameAssembly()
{
try
{
using FileStream inputStream = File.OpenRead(Path.Combine(Paths.GameRootPath, "How to Fish_Data", "Managed", "Assembly-CSharp.dll"));
using SHA256 sHA = SHA256.Create();
string text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", string.Empty);
return string.Equals(text, "FA8C6F47874E69FE07B9C978F35CC05372DF2BDD3535DE5F5FAC355F999A5762", StringComparison.OrdinalIgnoreCase) ? ("verified Assembly-CSharp " + text.Substring(0, 12)) : ("unverified Assembly-CSharp " + text.Substring(0, 12));
}
catch (Exception ex)
{
return "assembly verification failed: " + ex.Message;
}
}
private bool UseChinese()
{
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Invalid comparison between Unknown and I4
//IL_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Invalid comparison between Unknown and I4
string text = ((_language == null) ? "Auto" : _language.Value) ?? "Auto";
if (text.Equals("Chinese", StringComparison.OrdinalIgnoreCase) || text.StartsWith("zh", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (text.Equals("English", StringComparison.OrdinalIgnoreCase) || text.StartsWith("en", StringComparison.OrdinalIgnoreCase))
{
return false;
}
if ((int)Application.systemLanguage != 40)
{
return (int)Application.systemLanguage == 41;
}
return true;
}
internal static void LogWarning(string message)
{
if (LogSource != null)
{
LogSource.LogWarning((object)message);
}
}
internal static void LogInfo(string message)
{
if (LogSource != null)
{
LogSource.LogInfo((object)message);
}
}
internal static void LogDebug(string message)
{
if (LogSource != null)
{
LogSource.LogDebug((object)message);
}
}
}
internal static class ReflectionMemberAccessor
{
private const BindingFlags InstanceFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags StaticFlags = BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly Dictionary<string, MemberInfo> Cache = new Dictionary<string, MemberInfo>(StringComparer.Ordinal);
private static readonly object CacheLock = new object();
internal static object GetValue(object target, string name)
{
if (target != null)
{
return Read(target.GetType(), target, name, isStatic: false, unwrapSyncVar: true);
}
return null;
}
internal static object GetStaticValue(Type type, string name)
{
if (!(type == null))
{
return Read(type, null, name, isStatic: true, unwrapSyncVar: false);
}
return null;
}
private static object Read(Type type, object target, string name, bool isStatic, bool unwrapSyncVar)
{
try
{
MemberInfo memberInfo = Find(type, name, isStatic);
PropertyInfo propertyInfo = memberInfo as PropertyInfo;
object obj;
if (propertyInfo != null)
{
obj = propertyInfo.GetValue(target, null);
}
else
{
FieldInfo fieldInfo = memberInfo as FieldInfo;
obj = ((fieldInfo == null) ? null : fieldInfo.GetValue(target));
}
return unwrapSyncVar ? UnwrapSyncVar(obj) : obj;
}
catch
{
return null;
}
}
private static MemberInfo Find(Type type, string name, bool isStatic)
{
string key = type.AssemblyQualifiedName + "|" + (isStatic ? "S|" : "I|") + name;
lock (CacheLock)
{
if (Cache.TryGetValue(key, out var value))
{
return value;
}
}
BindingFlags bindingAttr = (isStatic ? (BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : (BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
MemberInfo memberInfo = null;
Type type2 = type;
while (type2 != null && memberInfo == null)
{
PropertyInfo property = type2.GetProperty(name, bindingAttr);
memberInfo = ((!(property != null) || property.GetIndexParameters().Length != 0) ? ((MemberInfo)type2.GetField(name, bindingAttr)) : ((MemberInfo)property));
type2 = type2.BaseType;
}
lock (CacheLock)
{
Cache[key] = memberInfo;
return memberInfo;
}
}
private static object UnwrapSyncVar(object value)
{
if (value == null)
{
return null;
}
Type type = value.GetType();
if (!type.IsGenericType || !type.Name.StartsWith("SyncVar`", StringComparison.Ordinal) || !string.Equals(type.Namespace, "FishNet.Object.Synchronizing", StringComparison.Ordinal))
{
return value;
}
return Read(type, value, "Value", isStatic: false, unwrapSyncVar: false);
}
}
internal sealed class StatsHud
{
private readonly StatsLedger _ledger;
private readonly Func<bool> _useChinese;
private readonly Func<float> _scale;
private readonly Action<string> _saveLayout;
private readonly HudPanelCollection _panels;
private readonly PlayerColorAssigner _playerColors = new PlayerColorAssigner();
private GUIStyle _panel;
private GUIStyle _selectedPanel;
private GUIStyle _title;
private GUIStyle _label;
private GUIStyle _value;
private GUIStyle _small;
private Texture2D _background;
private Texture2D _selectedBackground;
private int _dragging = -1;
private int _resizing = -1;
private int _metricMenuPanel = -1;
private Vector2 _metricMenuPosition;
private CursorLockMode _previousCursorLock;
private bool _previousCursorVisible;
private bool _cursorCaptured;
internal bool EditMode { get; private set; }
internal StatsHud(StatsLedger ledger, Func<bool> useChinese, Func<float> scale, string layout, Action<string> saveLayout)
{
_ledger = ledger;
_useChinese = useChinese;
_scale = scale;
_saveLayout = saveLayout;
_panels = new HudPanelCollection(layout);
if (_panels.ApplyCompactDefault(Screen.width, Screen.height) || _panels.Panels.Count > 1)
{
SaveLayout();
}
}
internal void AddPanel()
{
if (_panels.AddAfterSelected())
{
SaveLayout();
}
}
internal void RemoveSelectedPanel()
{
if (_panels.RemoveSelected())
{
SaveLayout();
}
}
internal void SetEditMode(bool enabled)
{
//IL_002a: 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)
if (EditMode != enabled)
{
EditMode = enabled;
_dragging = -1;
_resizing = -1;
_metricMenuPanel = -1;
if (enabled)
{
_previousCursorLock = Cursor.lockState;
_previousCursorVisible = Cursor.visible;
_cursorCaptured = true;
MaintainCursor();
}
else
{
RestoreCursor();
SaveLayout();
}
}
}
internal void MaintainCursor()
{
if (EditMode)
{
Cursor.lockState = (CursorLockMode)0;
Cursor.visible = true;
}
}
internal void Shutdown()
{
SetEditMode(enabled: false);
}
internal void Draw(bool compatible, string compatibilitySummary)
{
//IL_007d: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: 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)
if (_panels.ApplyCompactDefault(Screen.width, Screen.height))
{
SaveLayout();
}
EnsureStyles();
float scale = Mathf.Clamp(_scale(), 0.75f, 2f);
UpdateFontSizes(scale);
HandleActiveManipulation();
int addAfter = -1;
int remove = -1;
int count = _panels.Panels.Count;
for (int i = 0; i < count; i++)
{
HudPanelState hudPanelState = _panels.Panels[i];
Rect rect = ToPixels(hudPanelState);
ClampRect(ref rect);
FromPixels(hudPanelState, rect);
DrawPanel(i, hudPanelState, rect, scale, compatible, compatibilitySummary, ref addAfter, ref remove);
}
if (remove >= 0 && _panels.Remove(remove))
{
SaveLayout();
}
if (addAfter >= 0 && _panels.AddAfter(addAfter))
{
SaveLayout();
}
DrawMetricMenu(scale);
}
private void DrawPanel(int index, HudPanelState state, Rect rect, float scale, bool compatible, string compatibilitySummary, ref int addAfter, ref int remove)
{
//IL_005f: 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_0117: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_0156: Unknown result type (might be due to invalid IL or missing references)
//IL_019f: Unknown result type (might be due to invalid IL or missing references)
//IL_0160: Unknown result type (might be due to invalid IL or missing references)
//IL_0172: Unknown result type (might be due to invalid IL or missing references)
//IL_0218: Unknown result type (might be due to invalid IL or missing references)
//IL_0184: Unknown result type (might be due to invalid IL or missing references)
//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
//IL_0382: Unknown result type (might be due to invalid IL or missing references)
//IL_0324: Unknown result type (might be due to invalid IL or missing references)
//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
//IL_03ae: Unknown result type (might be due to invalid IL or missing references)
//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
//IL_03b5: Unknown result type (might be due to invalid IL or missing references)
//IL_03c9: Unknown result type (might be due to invalid IL or missing references)
//IL_03ca: Unknown result type (might be due to invalid IL or missing references)
//IL_03cc: Unknown result type (might be due to invalid IL or missing references)
if (EditMode && index == _panels.SelectedIndex)
{
GUI.Box(new Rect(((Rect)(ref rect)).x - 2f, ((Rect)(ref rect)).y - 2f, ((Rect)(ref rect)).width + 4f, ((Rect)(ref rect)).height + 4f), GUIContent.none, _selectedPanel);
}
GUI.Box(rect, GUIContent.none, _panel);
float num = 12f * scale;
float num2 = 30f * scale;
float num3 = 27f * scale;
Rect val = default(Rect);
((Rect)(ref val))..ctor(((Rect)(ref rect)).x + num, ((Rect)(ref rect)).y + 8f * scale, ((Rect)(ref rect)).width - num * 2f, num2);
Rect val2 = default(Rect);
((Rect)(ref val2))..ctor(((Rect)(ref val)).xMax - num3 * 2f - 3f * scale, ((Rect)(ref val)).y, num3, ((Rect)(ref val)).height);
Rect val3 = default(Rect);
((Rect)(ref val3))..ctor(((Rect)(ref val2)).xMax + 3f * scale, ((Rect)(ref val)).y, num3, ((Rect)(ref val)).height);
Rect val4 = (Rect)(EditMode ? new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).y, Math.Max(40f, ((Rect)(ref val2)).x - ((Rect)(ref val)).x - 5f * scale), ((Rect)(ref val)).height) : val);
if (EditMode)
{
if (GUI.Button(val2, "+"))
{
addAfter = index;
}
if (GUI.Button(val3, "−"))
{
remove = index;
}
GUI.Label(val4, MetricName(state.Metric), _title);
}
else
{
GUI.Label(val, MetricName(state.Metric), _title);
}
IList<PlayerMetricRow> rows = _ledger.GetRows(state.Metric);
float num4 = 27f * scale;
float num5 = ((Rect)(ref val)).yMax + 4f * scale;
if (!compatible)
{
Rect val5 = default(Rect);
((Rect)(ref val5))..ctor(((Rect)(ref rect)).x + num, ((Rect)(ref val)).yMax, ((Rect)(ref rect)).width - num * 2f, 20f * scale);
GUI.Label(val5, WarningText(compatibilitySummary), WarningStyle());
num5 = ((Rect)(ref val5)).yMax + 4f * scale;
}
float num6 = (EditMode ? (22f * scale) : (8f * scale));
int num7 = Math.Max(0, Mathf.FloorToInt((((Rect)(ref rect)).yMax - num6 - num5) / num4));
if (rows.Count == 0 && num7 > 0)
{
GUI.Label(new Rect(((Rect)(ref rect)).x + num, num5, ((Rect)(ref rect)).width - num * 2f, num4), _useChinese() ? "等待玩家对象……" : "Waiting for player objects…", _label);
}
else
{
long max = ((rows.Count == 0) ? 1 : Math.Max(1L, rows[0].Total));
for (int i = 0; i < rows.Count && i < num7; i++)
{
DrawRow(rows[i], max, new Rect(((Rect)(ref rect)).x + num, num5 + (float)i * num4, ((Rect)(ref rect)).width - num * 2f, num4), scale);
}
}
if (EditMode)
{
GUI.Label(new Rect(((Rect)(ref rect)).x + num, ((Rect)(ref rect)).yMax - 20f * scale, ((Rect)(ref rect)).width - num * 2f, 17f * scale), _useChinese() ? "右键标题选指标|拖动/缩放全部框" : "Right-click title | move/resize all", _small);
Rect val6 = ResizeHandle(rect, scale);
GUI.Label(val6, "◢", _value);
HandlePointerDown(index, rect, val4, val6);
}
}
private void DrawRow(PlayerMetricRow row, long max, Rect rowRect, float scale)
{
//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
//IL_0102: Unknown result type (might be due to invalid IL or missing references)
//IL_010c: Unknown result type (might be due to invalid IL or missing references)
//IL_0144: 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_0184: 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)
float num = ((Rect)(ref rowRect)).width * 0.4f;
float num2 = 76f * scale;
Rect val = default(Rect);
((Rect)(ref val))..ctor(((Rect)(ref rowRect)).x, ((Rect)(ref rowRect)).y, num, ((Rect)(ref rowRect)).height);
Rect val2 = default(Rect);
((Rect)(ref val2))..ctor(((Rect)(ref rowRect)).xMax - num2, ((Rect)(ref rowRect)).y, num2, ((Rect)(ref rowRect)).height);
Rect val3 = default(Rect);
((Rect)(ref val3))..ctor(((Rect)(ref val)).xMax + 5f * scale, ((Rect)(ref rowRect)).y + 8f * scale, Math.Max(10f, ((Rect)(ref val2)).x - ((Rect)(ref val)).xMax - 10f * scale), 11f * scale);
string text = (row.IsPresent ? string.Empty : (_useChinese() ? "(已离开)" : " (left)"));
GUI.Label(val, Trim(row.Name, 18) + text, _label);
GUI.color = new Color(0.14f, 0.18f, 0.24f, 0.95f);
GUI.DrawTexture(val3, (Texture)(object)Texture2D.whiteTexture);
GUI.color = Color.HSVToRGB(Mathf.Repeat((float)_playerColors.GetIndex(row.Key) * 0.618034f, 1f), 0.68f, 1f);
GUI.DrawTexture(new Rect(((Rect)(ref val3)).x, ((Rect)(ref val3)).y, ((Rect)(ref val3)).width * ((float)row.Total / (float)max), ((Rect)(ref val3)).height), (Texture)(object)Texture2D.whiteTexture);
GUI.color = Color.white;
GUI.Label(val2, row.Total.ToString("N0", CultureInfo.InvariantCulture), _value);
}
private void HandlePointerDown(int index, Rect panelRect, Rect titleRect, Rect resizeHandle)
{
//IL_0007: 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_0015: 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_0039: 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_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
Event current = Event.current;
if ((int)current.type != 0)
{
return;
}
Vector2 mousePosition = current.mousePosition;
if (!((Rect)(ref panelRect)).Contains(mousePosition))
{
return;
}
_panels.Select(index);
if (current.button == 1 && ((Rect)(ref titleRect)).Contains(mousePosition))
{
_metricMenuPanel = index;
_metricMenuPosition = mousePosition;
_dragging = -1;
_resizing = -1;
current.Use();
}
else if (current.button == 0)
{
if (((Rect)(ref resizeHandle)).Contains(mousePosition))
{
_resizing = index;
_dragging = -1;
_metricMenuPanel = -1;
current.Use();
}
else if (((Rect)(ref titleRect)).Contains(mousePosition))
{
_dragging = index;
_resizing = -1;
_metricMenuPanel = -1;
current.Use();
}
}
}
private void HandleActiveManipulation()
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Invalid comparison between Unknown and I4
//IL_011f: Unknown result type (might be due to invalid IL or missing references)
//IL_0125: Invalid comparison between Unknown and I4
//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ea: 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)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
if (!EditMode)
{
return;
}
Event current = Event.current;
if ((int)current.type == 3 && (_dragging >= 0 || _resizing >= 0))
{
int num = ((_dragging >= 0) ? _dragging : _resizing);
if (num < _panels.Panels.Count)
{
HudPanelState hudPanelState = _panels.Panels[num];
if (_dragging >= 0)
{
_panels.MoveAll(current.delta.x / Math.Max(1f, Screen.width), current.delta.y / Math.Max(1f, Screen.height));
}
else
{
float requestedWidth = hudPanelState.Width + current.delta.x / Math.Max(1f, Screen.width);
float requestedHeight = hudPanelState.Height + current.delta.y / Math.Max(1f, Screen.height);
_panels.ResizeAll(requestedWidth, requestedHeight);
}
current.Use();
}
}
else if ((int)current.rawType == 1 && (_dragging >= 0 || _resizing >= 0))
{
_dragging = -1;
_resizing = -1;
SaveLayout();
current.Use();
}
}
private void DrawMetricMenu(float scale)
{
//IL_00b0: 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_016b: Unknown result type (might be due to invalid IL or missing references)
//IL_0176: Unknown result type (might be due to invalid IL or missing references)
if (!EditMode || _metricMenuPanel < 0 || _metricMenuPanel >= _panels.Panels.Count)
{
return;
}
float num = 190f * scale;
float num2 = 30f * scale;
float num3 = num2 * (float)MetricCatalog.Visible.Length + 8f * scale;
Rect val = default(Rect);
((Rect)(ref val))..ctor(Mathf.Clamp(_metricMenuPosition.x, 4f, Math.Max(4f, (float)Screen.width - num - 4f)), Mathf.Clamp(_metricMenuPosition.y, 4f, Math.Max(4f, (float)Screen.height - num3 - 4f)), num, num3);
GUI.Box(val, GUIContent.none, _panel);
int num4 = 0;
MetricKind[] visible = MetricCatalog.Visible;
foreach (MetricKind metric in visible)
{
if (GUI.Button(new Rect(((Rect)(ref val)).x + 4f * scale, ((Rect)(ref val)).y + 4f * scale + (float)num4 * num2, ((Rect)(ref val)).width - 8f * scale, num2 - 2f * scale), MetricName(metric)))
{
_panels.SetMetric(_metricMenuPanel, metric);
_metricMenuPanel = -1;
SaveLayout();
}
num4++;
}
Event current = Event.current;
if ((int)current.type == 0 && !((Rect)(ref val)).Contains(current.mousePosition))
{
_metricMenuPanel = -1;
current.Use();
}
}
private static Rect ResizeHandle(Rect rect, float scale)
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
float num = 22f * scale;
return new Rect(((Rect)(ref rect)).xMax - num, ((Rect)(ref rect)).yMax - num, num, num);
}
private static Rect ToPixels(HudPanelState panel)
{
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
return new Rect(panel.X * (float)Screen.width, panel.Y * (float)Screen.height, panel.Width * (float)Screen.width, panel.Height * (float)Screen.height);
}
private static void FromPixels(HudPanelState panel, Rect rect)
{
panel.X = ((Rect)(ref rect)).x / Math.Max(1f, Screen.width);
panel.Y = ((Rect)(ref rect)).y / Math.Max(1f, Screen.height);
panel.Width = ((Rect)(ref rect)).width / Math.Max(1f, Screen.width);
panel.Height = ((Rect)(ref rect)).height / Math.Max(1f, Screen.height);
}
private static void ClampRect(ref Rect rect)
{
float num = Math.Min(300f, (float)Screen.width * 0.6f);
float num2 = Math.Min(150f, (float)Screen.height * 0.5f);
((Rect)(ref rect)).width = Mathf.Clamp(((Rect)(ref rect)).width, num, Math.Max(num, (float)Screen.width - 8f));
((Rect)(ref rect)).height = Mathf.Clamp(((Rect)(ref rect)).height, num2, Math.Max(num2, (float)Screen.height - 8f));
((Rect)(ref rect)).x = Mathf.Clamp(((Rect)(ref rect)).x, 4f, Math.Max(4f, (float)Screen.width - ((Rect)(ref rect)).width - 4f));
((Rect)(ref rect)).y = Mathf.Clamp(((Rect)(ref rect)).y, 4f, Math.Max(4f, (float)Screen.height - ((Rect)(ref rect)).height - 4f));
}
private string MetricName(MetricKind metric)
{
if (!_useChinese())
{
return metric switch
{
MetricKind.CreatureDamage => "Creature damage",
MetricKind.PlayerDamage => "Friendly fire",
MetricKind.MoneyEarned => "Money earned",
MetricKind.MoneySpent => "Money spent",
MetricKind.TeammatesRevived => "Teammates revived",
MetricKind.PlayerKills => "Teammates killed",
MetricKind.PlayerDeaths => "Deaths",
_ => "Fishing catches",
};
}
return metric switch
{
MetricKind.CreatureDamage => "对鱼造成伤害",
MetricKind.PlayerDamage => "友军伤害",
MetricKind.MoneyEarned => "赚钱数量",
MetricKind.MoneySpent => "花钱数量",
MetricKind.TeammatesRevived => "复活队友次数",
MetricKind.PlayerKills => "击杀次数",
MetricKind.PlayerDeaths => "死亡次数",
_ => "钓鱼数量",
};
}
private string WarningText(string detail)
{
return (_useChinese() ? "兼容性降级:部分采集器缺失" : "Compatibility degraded: collectors missing") + (string.IsNullOrEmpty(detail) ? string.Empty : (" — " + Trim(detail, 46)));
}
private GUIStyle WarningStyle()
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Expected O, but got Unknown
GUIStyle val = new GUIStyle(_small);
val.normal.textColor = new Color(1f, 0.55f, 0.35f, 1f);
return val;
}
private void EnsureStyles()
{
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: 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_0077: Expected O, but got Unknown
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
//IL_009d: Expected O, but got Unknown
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_00c0: Expected O, but got Unknown
//IL_00df: Unknown result type (might be due to invalid IL or missing references)
//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
//IL_0105: Expected O, but got Unknown
//IL_0110: Unknown result type (might be due to invalid IL or missing references)
//IL_0121: Unknown result type (might be due to invalid IL or missing references)
//IL_0126: 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_0139: Expected O, but got Unknown
//IL_0144: Unknown result type (might be due to invalid IL or missing references)
//IL_0149: Unknown result type (might be due to invalid IL or missing references)
//IL_0155: Expected O, but got Unknown
//IL_0174: Unknown result type (might be due to invalid IL or missing references)
if (_panel == null)
{
_background = SolidTexture(new Color(0.035f, 0.055f, 0.09f, 0.58f));
_selectedBackground = SolidTexture(new Color(0.95f, 0.68f, 0.18f, 0.82f));
GUIStyle val = new GUIStyle(GUI.skin.box);
val.normal.background = _background;
_panel = val;
GUIStyle val2 = new GUIStyle(GUI.skin.box);
val2.normal.background = _selectedBackground;
_selectedPanel = val2;
_title = new GUIStyle(GUI.skin.label)
{
alignment = (TextAnchor)4,
fontStyle = (FontStyle)1
};
_title.normal.textColor = new Color(0.86f, 0.98f, 1f, 1f);
_label = new GUIStyle(GUI.skin.label)
{
alignment = (TextAnchor)3
};
_label.normal.textColor = Color.white;
_value = new GUIStyle(_label)
{
alignment = (TextAnchor)5,
fontStyle = (FontStyle)1
};
_small = new GUIStyle(GUI.skin.label)
{
alignment = (TextAnchor)4
};
_small.normal.textColor = new Color(0.65f, 0.74f, 0.82f, 1f);
}
}
private void UpdateFontSizes(float scale)
{
_title.fontSize = Mathf.RoundToInt(17f * scale);
_label.fontSize = Mathf.RoundToInt(13f * scale);
_value.fontSize = Mathf.RoundToInt(13f * scale);
_small.fontSize = Mathf.RoundToInt(11f * scale);
}
private static Texture2D SolidTexture(Color color)
{
//IL_0004: Unknown result type (might be due to invalid IL or missing references)
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: 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_0019: Expected O, but got Unknown
Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false);
val.SetPixel(0, 0, color);
val.Apply();
return val;
}
private void SaveLayout()
{
if (_saveLayout != null)
{
_saveLayout(_panels.Serialize());
}
}
private void RestoreCursor()
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
if (_cursorCaptured)
{
Cursor.lockState = _previousCursorLock;
Cursor.visible = _previousCursorVisible;
_cursorCaptured = false;
}
}
private static string Trim(string value, int max)
{
if (string.IsNullOrEmpty(value) || value.Length <= max)
{
return value ?? string.Empty;
}
return value.Substring(0, max - 1) + "…";
}
}
internal sealed class StatsLedger
{
private sealed class MoneyCue
{
internal float Time;
internal bool Increase;
internal object Player;
}
private sealed class MoneyDelta
{
internal float Time;
internal int Amount;
}
private const float DuplicateWindowSeconds = 0.45f;
private const float CorrelationWindowSeconds = 1.5f;
private readonly GameIntrospection _game;
private readonly Dictionary<string, PlayerStats> _players = new Dictionary<string, PlayerStats>(StringComparer.Ordinal);
private readonly Dictionary<string, PlayerStats> _serverPlayers = new Dictionary<string, PlayerStats>(StringComparer.Ordinal);
private readonly CrossSourceDeduplicator _deduplicator = new CrossSourceDeduplicator(0.45f);
private readonly FishingCatchTracker _catchTracker = new FishingCatchTracker();
private readonly List<MoneyCue> _moneyCues = new List<MoneyCue>();
private readonly List<MoneyDelta> _moneyDeltas = new List<MoneyDelta>();
internal string SessionId { get; private set; }
internal DateTime StartedUtc { get; private set; }
internal long UnassignedMoneyEarned { get; private set; }
internal long UnassignedMoneySpent { get; private set; }
internal long Revision { get; private set; }
internal bool UsingServerSnapshot { get; private set; }
internal StatsLedger(GameIntrospection game, string sessionId)
{
_game = game;
SessionId = sessionId;
StartedUtc = DateTime.UtcNow;
}
internal void RefreshPlayers(IList<object> livePlayers)
{
HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
foreach (object livePlayer in livePlayers)
{
PlayerIdentity playerIdentity = _game.GetPlayerIdentity(livePlayer);
if (playerIdentity != null)
{
PlayerStats playerStats = EnsurePlayer(playerIdentity);
playerStats.IsPresent = true;
playerStats.LastSeenUtc = DateTime.UtcNow;
hashSet.Add(playerStats.Key);
}
}
foreach (PlayerStats value in _players.Values)
{
if (!hashSet.Contains(value.Key))
{
value.IsPresent = false;
}
}
Revision++;
}
internal void RecordCreatureDamage(object player, object creature, int damage, string source, Confidence confidence)
{
if (damage > 0)
{
Record("creature_damage", MetricKind.CreatureDamage, player, damage, creature, source, confidence, string.Empty);
}
}
internal void RecordPlayerDamage(object attacker, object target, int damage, string source, Confidence confidence, string damageType)
{
if (damage > 0)
{
Record("player_damage", MetricKind.PlayerDamage, attacker, damage, target, source, confidence, damageType ?? string.Empty);
}
}
internal void RecordAuthoritativeMoney(object player, int amount, bool increase, string source)
{
if (amount > 0)
{
Record(increase ? "money_earned" : "money_spent", increase ? MetricKind.MoneyEarned : MetricKind.MoneySpent, player, amount, null, source, Confidence.Exact, string.Empty);
}
}
internal void ObserveMoneyCue(bool increase, object player)
{
float now = Time.realtimeSinceStartup;
MoneyDelta moneyDelta = (from d in _moneyDeltas
where Math.Sign(d.Amount) == (increase ? 1 : (-1)) && Math.Abs(now - d.Time) <= 1.5f
orderby d.Time descending
select d).FirstOrDefault();
if (moneyDelta != null)
{
_moneyDeltas.Remove(moneyDelta);
RecordAuthoritativeOrInferredMoney(player, Math.Abs(moneyDelta.Amount), increase, "money_correlation");
return;
}
_moneyCues.Add(new MoneyCue
{
Time = now,
Increase = increase,
Player = player
});
}
internal void ObserveBalanceChange(int previous, int next, bool asServer)
{
int num = next - previous;
if (!(num == 0 || asServer))
{
float now = Time.realtimeSinceStartup;
bool increase = num > 0;
List<MoneyCue> list = _moneyCues.Where((MoneyCue c) => c.Increase == increase && Math.Abs(now - c.Time) <= 1.5f).ToList();
if (list.Count == 1)
{
_moneyCues.Remove(list[0]);
RecordAuthoritativeOrInferredMoney(list[0].Player, Math.Abs(num), increase, "money_correlation");
}
else
{
_moneyDeltas.Add(new MoneyDelta
{
Time = now,
Amount = num
});
}
}
}
internal void BeginFishingCatch(object player, object item, object rod, bool authoritative)
{
_catchTracker.Begin(_game.GetObjectId(rod), player, item, authoritative);
}
internal void ConfirmFishingCatch(object rod, object landedItem, string source)
{
if (_catchTracker.TryConfirm(_game.GetObjectId(rod), out var pending))
{
object obj = (GameIntrospection.IsNull(landedItem) ? pending.Item : landedItem);
Record("fish_caught", MetricKind.FishCaught, pending.Player, 1L, obj, source, (!pending.Authoritative) ? Confidence.Inferred : Confidence.Exact, _game.GetItemName(obj));
}
}
internal void CancelFishingCatch(object rod)
{
_catchTracker.Cancel(_game.GetObjectId(rod));
}
internal void RecordResurrection(object victim, object deadPlayer, string source)
{
if (!GameIntrospection.IsNull(victim) && !GameIntrospection.IsNull(deadPlayer) && _game.IsPlayerDead(victim))
{
object deadPlayerHolder = _game.GetDeadPlayerHolder(deadPlayer);
if (!GameIntrospection.IsNull(deadPlayerHolder) && !_game.IsSameObject(deadPlayerHolder, victim))
{
Record("teammate_revived", MetricKind.TeammatesRevived, deadPlayerHolder, 1L, victim, source, Confidence.Exact, string.Empty);
}
}
}
internal void RecordPlayerKill(object attacker, object victim, string source)
{
if (!GameIntrospection.IsNull(attacker) && !GameIntrospection.IsNull(victim) && !_game.IsSameObject(attacker, victim))
{
Record("player_kill", MetricKind.PlayerKills, attacker, 1L, victim, source, Confidence.Exact, string.Empty);
}
}
internal void RecordDeath(object player, string source)
{
if (!GameIntrospection.IsNull(player))
{
Record("player_death", MetricKind.PlayerDeaths, player, 1L, null, source, Confidence.Exact, string.Empty);
}
}
internal void Tick()
{
float now = Time.realtimeSinceStartup;
for (int num = _moneyDeltas.Count - 1; num >= 0; num--)
{
if (!(now - _moneyDeltas[num].Time <= 1.5f))
{
MoneyDelta moneyDelta = _moneyDeltas[num];
_moneyDeltas.RemoveAt(num);
RecordUnknownMoney(moneyDelta.Amount);
}
}
_moneyCues.RemoveAll((MoneyCue c) => now - c.Time > 1.5f);
}
internal IList<PlayerMetricRow> GetRows(MetricKind metric)
{
return (from p in ((IDictionary<string, PlayerStats>)(UsingServerSnapshot ? _serverPlayers : _players)).Values
select new PlayerMetricRow
{
Key = p.Key,
SteamId = p.SteamId,
Name = p.Name,
IsPresent = p.IsPresent,
Exact = p.Metrics[metric].Exact,
Inferred = p.Metrics[metric].Inferred
} into row
orderby row.Total descending, (row.SteamId != 0L) ? row.SteamId : ulong.MaxValue
select row).ThenBy<PlayerMetricRow, string>((PlayerMetricRow row) => row.Key, StringComparer.Ordinal).ToList();
}
internal IList<PlayerStats> GetPlayers()
{
return ((IDictionary<string, PlayerStats>)(UsingServerSnapshot ? _serverPlayers : _players)).Values.OrderBy((PlayerStats p) => (p.SteamId != 0L) ? p.SteamId : ulong.MaxValue).ThenBy<PlayerStats, string>((PlayerStats p) => p.Key, StringComparer.Ordinal).ToList();
}
internal StatsSyncSnapshot CreateSyncSnapshot()
{
StatsSyncSnapshot statsSyncSnapshot = new StatsSyncSnapshot
{
SessionId = SessionId
};
int length = Enum.GetValues(typeof(MetricKind)).Length;
foreach (PlayerStats item in _players.Values.OrderBy<PlayerStats, string>((PlayerStats p) => p.Key, StringComparer.Ordinal))
{
StatsSyncPlayer statsSyncPlayer = new StatsSyncPlayer
{
Key = item.Key,
SteamId = item.SteamId,
Name = item.Name,
IsPresent = item.IsPresent,
Exact = new long[length],
Inferred = new long[length]
};
foreach (MetricKind value in Enum.GetValues(typeof(MetricKind)))
{
statsSyncPlayer.Exact[(int)value] = item.Metrics[value].Exact;
statsSyncPlayer.Inferred[(int)value] = item.Metrics[value].Inferred;
}
statsSyncSnapshot.Players.Add(statsSyncPlayer);
}
return statsSyncSnapshot;
}
internal void ApplyServerSnapshot(StatsSyncSnapshot snapshot)
{
if (snapshot == null)
{
return;
}
_serverPlayers.Clear();
int length = Enum.GetValues(typeof(MetricKind)).Length;
foreach (StatsSyncPlayer player in snapshot.Players)
{
if (player != null && !string.IsNullOrEmpty(player.Key))
{
PlayerStats playerStats = new PlayerStats
{
Key = player.Key,
SteamId = player.SteamId,
Name = player.Name,
IsPresent = player.IsPresent,
LastSeenUtc = DateTime.UtcNow
};
for (int i = 0; i < length; i++)
{
MetricKind key = (MetricKind)i;
playerStats.Metrics[key].Exact = ((player.Exact != null && i < player.Exact.Length) ? player.Exact[i] : 0);
playerStats.Metrics[key].Inferred = ((player.Inferred != null && i < player.Inferred.Length) ? player.Inferred[i] : 0);
}
_serverPlayers[playerStats.Key] = playerStats;
}
}
UsingServerSnapshot = true;
}
internal void ClearServerSnapshot()
{
if (UsingServerSnapshot || _serverPlayers.Count != 0)
{
UsingServerSnapshot = false;
_serverPlayers.Clear();
}
}
internal void FlushPending()
{
MoneyDelta[] array = _moneyDeltas.ToArray();
foreach (MoneyDelta moneyDelta in array)
{
RecordUnknownMoney(moneyDelta.Amount);
}
_moneyDeltas.Clear();
_moneyCues.Clear();
}
private void RecordAuthoritativeOrInferredMoney(object player, int amount, bool increase, string source)
{
Record(increase ? "money_earned" : "money_spent", increase ? MetricKind.MoneyEarned : MetricKind.MoneySpent, player, amount, null, source, Confidence.Inferred, string.Empty);
}
private void RecordUnknownMoney(int delta)
{
if (delta > 0)
{
UnassignedMoneyEarned += delta;
}
else
{
UnassignedMoneySpent += Math.Abs((long)delta);
}
Record((delta > 0) ? "money_delta_unassigned_earned" : "money_delta_unassigned_spent", null, null, Math.Abs((long)delta), null, "balance_delta", Confidence.Unknown, "Original game traffic did not uniquely identify a player.");
}
private void Record(string eventType, MetricKind? metric, object player, long value, object target, string source, Confidence confidence, string detail)
{
PlayerIdentity playerIdentity = _game.GetPlayerIdentity(player);
string text = ((playerIdentity == null) ? string.Empty : playerIdentity.Key);
string objectId = _game.GetObjectId(target);
float realtimeSinceStartup = Time.realtimeSinceStartup;
string signature = eventType + "|" + text + "|" + objectId + "|" + value.ToString(CultureInfo.InvariantCulture);
PlayerStats playerStats = ((playerIdentity == null) ? null : EnsurePlayer(playerIdentity));
switch (_deduplicator.Observe(signature, source, confidence, realtimeSinceStartup))
{
case DeduplicationResult.PromoteToExact:
if (playerStats != null && metric.HasValue)
{
MetricValue metricValue2 = playerStats.Metrics[metric.Value];
metricValue2.Inferred = Math.Max(0L, metricValue2.Inferred - value);
metricValue2.Exact += value;
Revision++;
}
break;
case DeduplicationResult.NewEvent:
if (metric.HasValue && playerStats != null)
{
MetricValue metricValue = playerStats.Metrics[metric.Value];
switch (confidence)
{
case Confidence.Exact:
case Confidence.Corrected:
metricValue.Exact += value;
break;
case Confidence.Inferred:
metricValue.Inferred += value;
break;
}
}
Revision++;
break;
}
}
private PlayerStats EnsurePlayer(PlayerIdentity identity)
{
if (!_players.TryGetValue(identity.Key, out var value))
{
value = new PlayerStats
{
Key = identity.Key,
SteamId = identity.SteamId,
Name = identity.Name
};
_players.Add(identity.Key, value);
}
else
{
value.SteamId = identity.SteamId;
if (!string.IsNullOrWhiteSpace(identity.Name))
{
value.Name = identity.Name;
}
}
return value;
}
}
internal sealed class StatsSyncSnapshot
{
internal string SessionId;
internal readonly List<StatsSyncPlayer> Players = new List<StatsSyncPlayer>();
}
internal sealed class StatsSyncPlayer
{
internal string Key;
internal ulong SteamId;
internal string Name;
internal bool IsPresent;
internal long[] Exact;
internal long[] Inferred;
}
internal static class StatsSyncPayloadCodec
{
internal const byte ProtocolVersion = 1;
internal static string Serialize(StatsSyncSnapshot snapshot)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(((byte)1).ToString(CultureInfo.InvariantCulture)).Append('\n').Append(Encode((snapshot == null) ? string.Empty : snapshot.SessionId))
.Append('\n');
if (snapshot == null)
{
return stringBuilder.ToString();
}
int length = Enum.GetValues(typeof(MetricKind)).Length;
foreach (StatsSyncPlayer player in snapshot.Players)
{
stringBuilder.Append(Encode(player.Key)).Append('|').Append(player.SteamId.ToString(CultureInfo.InvariantCulture))
.Append('|')
.Append(Encode(player.Name))
.Append('|')
.Append(player.IsPresent ? '1' : '0');
for (int i = 0; i < length; i++)
{
stringBuilder.Append('|').Append(ValueAt(player.Exact, i).ToString(CultureInfo.InvariantCulture)).Append('|')
.Append(ValueAt(player.Inferred, i).ToString(CultureInfo.InvariantCulture));
}
stringBuilder.Append('\n');
}
return stringBuilder.ToString();
}
internal static bool TryDeserialize(string payload, out StatsSyncSnapshot snapshot)
{
snapshot = null;
if (string.IsNullOrEmpty(payload))
{
return false;
}
string[] array = payload.Replace("\r", string.Empty).Split(new char[1] { '\n' });
if (array.Length < 2 || !byte.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result != 1)
{
return false;
}
if (!TryDecode(array[1], out var decoded))
{
return false;
}
StatsSyncSnapshot statsSyncSnapshot = new StatsSyncSnapshot
{
SessionId = decoded
};
int length = Enum.GetValues(typeof(MetricKind)).Length;
int num = 4 + length * 2;
for (int i = 2; i < array.Length; i++)
{
if (string.IsNullOrEmpty(array[i]))
{
continue;
}
string[] array2 = array[i].Split(new char[1] { '|' });
if (array2.Length != num || !TryDecode(array2[0], out var decoded2) || !ulong.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || !TryDecode(array2[2], out var decoded3) || (array2[3] != "0" && array2[3] != "1"))
{
return false;
}
StatsSyncPlayer statsSyncPlayer = new StatsSyncPlayer
{
Key = decoded2,
SteamId = result2,
Name = decoded3,
IsPresent = (array2[3] == "1"),
Exact = new long[length],
Inferred = new long[length]
};
for (int j = 0; j < length; j++)
{
if (!long.TryParse(array2[4 + j * 2], NumberStyles.Integer, CultureInfo.InvariantCulture, out statsSyncPlayer.Exact[j]) || !long.TryParse(array2[5 + j * 2], NumberStyles.Integer, CultureInfo.InvariantCulture, out statsSyncPlayer.Inferred[j]))
{
return false;
}
}
statsSyncSnapshot.Players.Add(statsSyncPlayer);
}
snapshot = statsSyncSnapshot;
return true;
}
private static long ValueAt(long[] values, int index)
{
if (values == null || index < 0 || index >= values.Length)
{
return 0L;
}
return values[index];
}
private static string Encode(string value)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(value ?? string.Empty));
}
private static bool TryDecode(string value, out string decoded)
{
decoded = string.Empty;
try
{
decoded = Encoding.UTF8.GetString(Convert.FromBase64String(value ?? string.Empty));
return true;
}
catch
{
return false;
}
}
}
public struct StatsHandshakeBroadcast : IBroadcast
{
public byte Protocol;
}
public struct StatsSnapshotBroadcast : IBroadcast
{
public string Payload;
}
internal sealed class StatsSyncService
{
private readonly StatsLedger _ledger;
private readonly Dictionary<int, NetworkConnection> _moddedClients = new Dictionary<int, NetworkConnection>();
private ServerManager _server;
private ClientManager _client;
private bool _handshakeSent;
private bool _snapshotReceived;
private float _nextSnapshot;
internal StatsSyncService(StatsLedger ledger)
{
_ledger = ledger;
RegisterSerializers();
}
internal void Tick()
{
EnsureManagers();
if ((Object)(object)_client == (Object)null || !_client.Started)
{
_handshakeSent = false;
_snapshotReceived = false;
_ledger.ClearServerSnapshot();
}
else if (!_handshakeSent && !InstanceFinder.IsServerStarted && _client.Connection != (NetworkConnection)null && _client.Connection.IsAuthenticated)
{
_client.Broadcast<StatsHandshakeBroadcast>(new StatsHandshakeBroadcast
{
Protocol = 1
}, (Channel)0);
_handshakeSent = true;
Plugin.LogDebug("Stats sync handshake sent to host.");
}
if ((Object)(object)_server != (Object)null && _server.Started && Time.unscaledTime >= _nextSnapshot)
{
_nextSnapshot = Time.unscaledTime + 1f;
BroadcastSnapshot();
}
}
internal void Shutdown()
{
if ((Object)(object)_server != (Object)null)
{
_server.UnregisterBroadcast<StatsHandshakeBroadcast>((Action<NetworkConnection, StatsHandshakeBroadcast, Channel>)OnHandshake);
}
if ((Object)(object)_client != (Object)null)
{
_client.UnregisterBroadcast<StatsSnapshotBroadcast>((Action<StatsSnapshotBroadcast, Channel>)OnSnapshot);
_client.OnAuthenticated -= OnAuthenticated;
_client.OnClientConnectionState -= OnClientConnectionState;
}
_moddedClients.Clear();
_ledger.ClearServerSnapshot();
}
private static void RegisterSerializers()
{
GenericWriter<StatsHandshakeBroadcast>.SetWrite((Action<Writer, StatsHandshakeBroadcast>)delegate(Writer writer, StatsHandshakeBroadcast value)
{
writer.WriteUInt8Unpacked(value.Protocol);
});
GenericReader<StatsHandshakeBroadcast>.SetRead((Func<Reader, StatsHandshakeBroadcast>)((Reader reader) => new StatsHandshakeBroadcast
{
Protocol = reader.ReadUInt8Unpacked()
}));
GenericWriter<StatsSnapshotBroadcast>.SetWrite((Action<Writer, StatsSnapshotBroadcast>)delegate(Writer writer, StatsSnapshotBroadcast value)
{
writer.WriteString(value.Payload ?? string.Empty);
});
GenericReader<StatsSnapshotBroadcast>.SetRead((Func<Reader, StatsSnapshotBroadcast>)((Reader reader) => new StatsSnapshotBroadcast
{
Payload = reader.ReadStringAllocated()
}));
}
private void EnsureManagers()
{
ServerManager serverManager = InstanceFinder.ServerManager;
if (serverManager != _server)
{
if ((Object)(object)_server != (Object)null)
{
_server.UnregisterBroadcast<StatsHandshakeBroadcast>((Action<NetworkConnection, StatsHandshakeBroadcast, Channel>)OnHandshake);
}
_server = serverManager;
_moddedClients.Clear();
if ((Object)(object)_server != (Object)null)
{
_server.RegisterBroadcast<StatsHandshakeBroadcast>((Action<NetworkConnection, StatsHandshakeBroadcast, Channel>)OnHandshake, false);
}
}
ClientManager clientManager = InstanceFinder.ClientManager;
if (clientManager != _client)
{
if ((Object)(object)_client != (Object)null)
{
_client.UnregisterBroadcast<StatsSnapshotBroadcast>((Action<StatsSnapshotBroadcast, Channel>)OnSnapshot);
_client.OnAuthenticated -= OnAuthenticated;
_client.OnClientConnectionState -= OnClientConnectionState;
}
_client = clientManager;
_handshakeSent = false;
if ((Object)(object)_client != (Object)null)
{
_client.RegisterBroadcast<StatsSnapshotBroadcast>((Action<StatsSnapshotBroadcast, Channel>)OnSnapshot);
_client.OnAuthenticated += OnAuthenticated;
_client.OnClientConnectionState += OnClientConnectionState;
}
}
}
private void OnAuthenticated()
{
_handshakeSent = false;
}
private void OnClientConnectionState(ClientConnectionStateArgs args)
{
if ((Object)(object)_client == (Object)null || !_client.Started)
{
_handshakeSent = false;
_ledger.ClearServerSnapshot();
}
}
private void OnHandshake(NetworkConnection connection, StatsHandshakeBroadcast message, Channel channel)
{
if (message.Protocol == 1 && !(connection == (NetworkConnection)null) && connection.IsValid)
{
bool num = !_moddedClients.ContainsKey(connection.ClientId);
_moddedClients[connection.ClientId] = connection;
SendSnapshot(connection);
if (num)
{
Plugin.LogInfo("Stats sync client connected: " + connection.ClientId + ".");
}
}
}
private void OnSnapshot(StatsSnapshotBroadcast message, Channel channel)
{
if (InstanceFinder.IsServerStarted)
{
return;
}
if (!StatsSyncPayloadCodec.TryDeserialize(message.Payload, out var snapshot))
{
Plugin.LogWarning("Ignored an invalid multiplayer stats snapshot.");
return;
}
_ledger.ApplyServerSnapshot(snapshot);
if (!_snapshotReceived)
{
_snapshotReceived = true;
Plugin.LogInfo("Authoritative host stats sync active.");
}
}
private void BroadcastSnapshot()
{
foreach (int item in new List<int>(_moddedClients.Keys))
{
NetworkConnection val = _moddedClients[item];
if (val == (NetworkConnection)null || !val.IsValid || !val.IsActive)
{
_moddedClients.Remove(item);
}
else
{
SendSnapshot(val);
}
}
}
private void SendSnapshot(NetworkConnection connection)
{
if (!((Object)(object)_server == (Object)null) && _server.Started && !(connection == (NetworkConnection)null))
{
string payload = StatsSyncPayloadCodec.Serialize(_ledger.CreateSyncSnapshot());
_server.Broadcast<StatsSnapshotBroadcast>(connection, new StatsSnapshotBroadcast
{
Payload = payload
}, true, (Channel)0);
}
}
}