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.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("UCHMatchStats")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.2.0")]
[assembly: AssemblyInformationalVersion("0.2.2")]
[assembly: AssemblyProduct("UCHMatchStats")]
[assembly: AssemblyTitle("UCHMatchStats")]
[assembly: AssemblyVersion("0.2.2.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 UCHMatchStats
{
internal static class FontPicker
{
private static bool done;
private static Font font;
internal static Font Get()
{
if (done)
{
return font;
}
done = true;
string wanted = Plugin.FontName.Value.Trim();
if (wanted.ToLowerInvariant() == "default")
{
return null;
}
try
{
Dictionary<Font, int> dictionary = new Dictionary<Font, int>();
Text[] array = Resources.FindObjectsOfTypeAll<Text>();
foreach (Text val in array)
{
if ((Object)(object)val != (Object)null && (Object)(object)val.font != (Object)null && val.font.dynamic)
{
dictionary.TryGetValue(val.font, out var value);
dictionary[val.font] = value + 1;
}
}
if (dictionary.Count == 0)
{
Plugin.LogVerbose("[Stats] No game font found, using the default font");
done = false;
return null;
}
Plugin.LogVerbose("[Stats] Fonts used by the game: " + string.Join(", ", (from u in dictionary
orderby u.Value descending
select $"{((Object)u.Key).name} ({u.Value})").ToArray()));
if (wanted.Length > 0 && wanted.ToLowerInvariant() != "auto")
{
font = ((IEnumerable<Font>)dictionary.Keys).FirstOrDefault((Func<Font, bool>)((Font f) => ((Object)f).name == wanted));
if ((Object)(object)font == (Object)null)
{
Plugin.Log.LogWarning((object)("[Stats] Font '" + wanted + "' not found, using the game's main font instead"));
}
}
if ((Object)(object)font == (Object)null)
{
font = dictionary.OrderByDescending((KeyValuePair<Font, int> u) => u.Value).First().Key;
}
Plugin.Log.LogInfo((object)("[Stats] Panel font: " + ((Object)font).name));
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("[Stats] Could not pick a game font: " + ex.Message));
font = null;
}
return font;
}
}
internal class PlayerStats
{
public int Number;
public string RawName = "";
public Animals Animal;
public int Deaths;
public int Kills;
public int OwnTrapDeaths;
public int FallDeaths;
public int GaveUp;
public int Goals;
public int Coins;
public int Blocks;
public readonly Dictionary<int, int> KilledBy = new Dictionary<int, int>();
public readonly Dictionary<string, int> DeathCauses = new Dictionary<string, int>();
}
internal static class MatchData
{
internal static readonly Dictionary<int, PlayerStats> Players = new Dictionary<int, PlayerStats>();
internal static readonly Dictionary<string, int> TrapKills = new Dictionary<string, int>();
internal static int Rounds;
internal static bool Ended;
internal static int Version;
internal static void Reset()
{
Players.Clear();
TrapKills.Clear();
Rounds = 0;
Ended = false;
Version++;
}
internal static PlayerStats Get(int number)
{
if (!Players.TryGetValue(number, out var value))
{
value = new PlayerStats
{
Number = number
};
Players[number] = value;
Version++;
}
return value;
}
internal static void Bump(Dictionary<string, int> map, string key)
{
map.TryGetValue(key, out var value);
map[key] = value + 1;
}
internal static void Bump(Dictionary<int, int> map, int key)
{
map.TryGetValue(key, out var value);
map[key] = value + 1;
}
internal static string NameOf(int number)
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
if (!Players.TryGetValue(number, out var value))
{
return "Player " + number;
}
string text = AnimalName(value.Animal);
if (string.IsNullOrEmpty(value.RawName))
{
return text ?? ("Player " + number);
}
if (text != null && Plugin.AnimalNamesForLocal.Value)
{
string text2 = BaseName(value.RawName);
foreach (PlayerStats value2 in Players.Values)
{
if (value2 == value || !(BaseName(value2.RawName) == text2))
{
continue;
}
foreach (PlayerStats value3 in Players.Values)
{
if (value3 != value && value3.Animal == value.Animal && BaseName(value3.RawName) == text2)
{
return text + " " + number;
}
}
return text;
}
}
return value.RawName;
}
private static string BaseName(string name)
{
if (string.IsNullOrEmpty(name))
{
return "";
}
int num = name.Length;
while (num > 0 && char.IsDigit(name[num - 1]))
{
num--;
}
if (num < name.Length && num > 0 && name[num - 1] == ' ')
{
return name.Substring(0, num - 1);
}
return name;
}
private unsafe static string AnimalName(Animals animal)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
if ((int)animal == 0)
{
return null;
}
string text = ((object)(*(Animals*)(&animal))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant();
return char.ToUpperInvariant(text[0]) + text.Substring(1);
}
internal static string TrapName(string cause)
{
switch (cause)
{
case "SpikeBall":
return "Spikey Ball";
case "BarbedWire":
return "Barbed Wire";
case "LinearSaw":
return "Saw";
case "TennisBallLauncher":
return "Ball Launcher";
case "PunchingBlock":
return "Boxing Glove";
case "PunchingPlant":
return "Punching Plant";
case "LuckyWheel":
return "Lucky Wheel";
case "Drowning_In_Lava":
return "Lava";
default:
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < cause.Length; i++)
{
char c = cause[i];
if (c == '_')
{
stringBuilder.Append(' ');
continue;
}
if (i > 0 && char.IsUpper(c) && char.IsLower(cause[i - 1]))
{
stringBuilder.Append(' ');
}
stringBuilder.Append(c);
}
return stringBuilder.ToString();
}
}
}
}
internal static class PanelSkin
{
private static bool loaded;
internal static Texture2D Texture { get; private set; }
internal static float Border { get; private set; } = 60f;
internal static float Padding { get; private set; } = 22f;
internal static Color TitleColor { get; private set; } = new Color(1f, 0.82f, 0.25f);
internal static Color HeaderColor { get; private set; } = new Color(0.75f, 0.78f, 0.9f);
internal static Color TextColor { get; private set; } = Color.white;
internal static Color LineColor { get; private set; } = new Color(0.95f, 0.95f, 0.9f);
internal static Color HintColor { get; private set; } = new Color(0.65f, 0.65f, 0.7f);
internal static bool HasTexture
{
get
{
Load();
return (Object)(object)Texture != (Object)null;
}
}
private static string Find(string file)
{
string path = Path.GetDirectoryName(typeof(PanelSkin).Assembly.Location) ?? ".";
string text = Path.Combine(Path.Combine(path, "textures"), file);
if (File.Exists(text))
{
return text;
}
string text2 = Path.Combine(path, file);
if (!File.Exists(text2))
{
return null;
}
return text2;
}
internal static void Load()
{
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Expected O, but got Unknown
if (loaded)
{
return;
}
loaded = true;
try
{
string text = Find("panel.txt");
if (text != null)
{
ReadSettings(text);
}
string text2 = Find("panel.png");
if (text2 == null)
{
Plugin.LogVerbose("[Stats] No panel.png found, using the built-in panel");
return;
}
Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
if (!ImageConversion.LoadImage(val, File.ReadAllBytes(text2)))
{
Plugin.Log.LogWarning((object)"[Stats] panel.png could not be read, using the built-in panel");
return;
}
((Texture)val).filterMode = (FilterMode)1;
((Texture)val).wrapMode = (TextureWrapMode)1;
Texture = val;
Plugin.Log.LogInfo((object)$"[Stats] Custom panel loaded: {((Texture)val).width} x {((Texture)val).height}, border {Border}, padding {Padding}");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("[Stats] Could not load the custom panel: " + ex.Message));
}
}
private static void ReadSettings(string path)
{
//IL_0161: Unknown result type (might be due to invalid IL or missing references)
//IL_0166: Unknown result type (might be due to invalid IL or missing references)
//IL_0174: Unknown result type (might be due to invalid IL or missing references)
//IL_0179: 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)
//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
//IL_019a: 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_0187: Unknown result type (might be due to invalid IL or missing references)
//IL_018c: Unknown result type (might be due to invalid IL or missing references)
string[] array = File.ReadAllLines(path);
for (int i = 0; i < array.Length; i++)
{
string text = array[i].Trim();
int num = text.IndexOf('=');
if (!text.StartsWith("#") && num >= 0)
{
string text2 = text.Substring(0, num).Trim();
string s = text.Substring(num + 1).Trim();
switch (text2)
{
case "border":
Border = Number(s, Border);
break;
case "padding":
Padding = Number(s, Padding);
break;
case "title_color":
TitleColor = Hex(s, TitleColor);
break;
case "header_color":
HeaderColor = Hex(s, HeaderColor);
break;
case "text_color":
TextColor = Hex(s, TextColor);
break;
case "line_color":
LineColor = Hex(s, LineColor);
break;
case "hint_color":
HintColor = Hex(s, HintColor);
break;
}
}
}
}
private static float Number(string s, float fallback)
{
if (!float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
return fallback;
}
return result;
}
private static Color Hex(string s, Color fallback)
{
//IL_0026: 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)
Color result = default(Color);
if (!ColorUtility.TryParseHtmlString(s.StartsWith("#") ? s : ("#" + s), ref result))
{
return fallback;
}
return result;
}
internal static void Draw(Rect box)
{
//IL_017a: Unknown result type (might be due to invalid IL or missing references)
//IL_017f: Unknown result type (might be due to invalid IL or missing references)
//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
Texture2D texture = Texture;
float num = ((Rect)(ref box)).width / (float)((Texture)texture).width;
float num2 = Mathf.Min(new float[3]
{
Border * num,
((Rect)(ref box)).width / 2f,
((Rect)(ref box)).height / 2f
});
float num3 = Mathf.Clamp(Border / (float)((Texture)texture).width, 0f, 0.5f);
float num4 = Mathf.Clamp(Border / (float)((Texture)texture).height, 0f, 0.5f);
float[] array = new float[4]
{
Mathf.Round(((Rect)(ref box)).x),
Mathf.Round(((Rect)(ref box)).x + num2),
Mathf.Round(((Rect)(ref box)).xMax - num2),
Mathf.Round(((Rect)(ref box)).xMax)
};
float[] array2 = new float[4]
{
Mathf.Round(((Rect)(ref box)).y),
Mathf.Round(((Rect)(ref box)).y + num2),
Mathf.Round(((Rect)(ref box)).yMax - num2),
Mathf.Round(((Rect)(ref box)).yMax)
};
float[] array3 = new float[4]
{
0f,
num3,
1f - num3,
1f
};
float[] array4 = new float[4]
{
1f,
1f - num4,
num4,
0f
};
Rect val2 = default(Rect);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
Rect val = Rect.MinMaxRect(array[j], array2[i], array[j + 1], array2[i + 1]);
if (!(((Rect)(ref val)).width <= 0f) && !(((Rect)(ref val)).height <= 0f))
{
((Rect)(ref val2))..ctor(array3[j], array4[i + 1], array3[j + 1] - array3[j], array4[i] - array4[i + 1]);
GUI.DrawTextureWithTexCoords(val, (Texture)(object)texture, val2, true);
}
}
}
}
}
[BepInPlugin("mpb27.uch.matchstats", "UCH Match Stats", "0.2.2")]
public class Plugin : BaseUnityPlugin
{
public const string Guid = "mpb27.uch.matchstats";
public const string Name = "UCH Match Stats";
public const string Version = "0.2.2";
internal static ManualLogSource Log;
internal static ConfigEntry<bool> ModEnabled;
internal static ConfigEntry<bool> ShowAtMatchEnd;
internal static ConfigEntry<KeyboardShortcut> ToggleKey;
internal static ConfigEntry<int> HighlightCount;
internal static ConfigEntry<float> EndDelay;
internal static ConfigEntry<string> FontName;
internal static ConfigEntry<bool> AnimalNamesForLocal;
internal static ConfigEntry<bool> VerboseLog;
private Harmony harmony;
private void Awake()
{
//IL_0065: 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)
//IL_00a6: Expected O, but got Unknown
//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
//IL_00e3: Expected O, but got Unknown
//IL_016e: Unknown result type (might be due to invalid IL or missing references)
//IL_0178: Expected O, but got Unknown
//IL_020e: Unknown result type (might be due to invalid IL or missing references)
Log = ((BaseUnityPlugin)this).Logger;
ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turns the whole mod on or off.");
ShowAtMatchEnd = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowAtMatchEnd", true, "Open the stats panel automatically when a match ends.");
ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Display", "ToggleKey", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Opens and closes the stats panel at any time, also in the middle of a match.");
HighlightCount = ((BaseUnityPlugin)this).Config.Bind<int>("Display", "HighlightCount", 8, new ConfigDescription("How many highlight lines are shown.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()));
EndDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "EndDelay", 2f, new ConfigDescription("Seconds after the match ends before the panel opens.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 15f), Array.Empty<object>()));
AnimalNamesForLocal = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "AnimalNamesForLocalPlayers", true, "Several players on one machine share one account name. true = show them by their animal (Horse, Chicken ...), false = show the name with the number the game appends.");
FontName = ((BaseUnityPlugin)this).Config.Bind<string>("Display", "Font", "auto", "Font of the panel. auto = the font the game uses most in its menus, default = plain system font, or the exact name of a game font (the names are listed in the log).");
VerboseLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLog", true, "Log every recorded death, point and placed block.");
if (!ModEnabled.Value)
{
Log.LogInfo((object)"UCH Match Stats 0.2.2 loaded, but disabled in the config.");
return;
}
harmony = new Harmony("mpb27.uch.matchstats");
harmony.PatchAll(typeof(Plugin).Assembly);
((Component)this).gameObject.AddComponent<Tracker>();
((Component)this).gameObject.AddComponent<StatsWindow>();
int num = 0;
foreach (MethodBase patchedMethod in harmony.GetPatchedMethods())
{
_ = patchedMethod;
num++;
}
Log.LogInfo((object)string.Format("{0} {1} loaded, {2} method(s) patched. Toggle key: {3}", "UCH Match Stats", "0.2.2", num, ToggleKey.Value));
}
private void OnDestroy()
{
Harmony obj = harmony;
if (obj != null)
{
obj.UnpatchSelf();
}
}
internal static void LogVerbose(string msg)
{
if (VerboseLog.Value)
{
Log.LogInfo((object)msg);
}
}
}
internal static class Report
{
private class Line
{
public float Weight;
public string Text;
}
private static int seed = 1;
internal static void NewSeed()
{
seed = Environment.TickCount;
}
private static string Pick(Random rng, params string[] variants)
{
return variants[rng.Next(variants.Length)];
}
private static string Times(int n)
{
return n switch
{
2 => "twice",
1 => "once",
_ => n + " times",
};
}
private static string Cap(string s)
{
if (!string.IsNullOrEmpty(s))
{
return char.ToUpperInvariant(s[0]) + s.Substring(1);
}
return s;
}
internal static List<string> Highlights(int max)
{
List<Line> list = new List<Line>();
Random rng = new Random(seed);
List<PlayerStats> list2 = MatchData.Players.Values.OrderBy((PlayerStats p) => p.Number).ToList();
int rounds = MatchData.Rounds;
foreach (PlayerStats item in list2)
{
string text = MatchData.NameOf(item.Number);
if (item.KilledBy.Count > 0)
{
KeyValuePair<int, int> keyValuePair = item.KilledBy.OrderByDescending((KeyValuePair<int, int> k) => k.Value).First();
if (keyValuePair.Value >= 2)
{
string text2 = MatchData.NameOf(keyValuePair.Key);
list.Add(new Line
{
Weight = 50 + keyValuePair.Value * 10,
Text = Pick(rng, text + " was owned by " + text2 + ". Died " + Times(keyValuePair.Value) + " to their traps.", $"{text2} lives rent-free in {text}'s head: {keyValuePair.Value} kills.", text + " kept walking into " + text2 + "'s traps. " + Cap(Times(keyValuePair.Value)) + ".")
});
}
}
if (item.OwnTrapDeaths >= 1)
{
list.Add(new Line
{
Weight = 45 + item.OwnTrapDeaths * 12,
Text = Pick(rng, text + " played themselves: died " + Times(item.OwnTrapDeaths) + " to their own traps.", text + " built a trap. " + text + " found the trap. " + Cap(Times(item.OwnTrapDeaths)) + ".", $"{text}'s worst enemy was {text}: {item.OwnTrapDeaths} self-inflicted deaths.")
});
}
if (item.FallDeaths >= 2)
{
list.Add(new Line
{
Weight = 30 + item.FallDeaths * 6,
Text = Pick(rng, $"{text} lost {item.FallDeaths} fights against gravity.", text + " forgot how jumping works " + Times(item.FallDeaths) + ".", "The floor was optional for " + text + ": fell " + Times(item.FallDeaths) + ".")
});
}
if (item.GaveUp >= 2)
{
list.Add(new Line
{
Weight = 26 + item.GaveUp * 5,
Text = Pick(rng, text + " gave up on the round " + Times(item.GaveUp) + ".", text + " hit the panic button " + Times(item.GaveUp) + ".")
});
}
if (item.DeathCauses.TryGetValue("LuckyWheel", out var value) && value >= 1)
{
list.Add(new Line
{
Weight = 42 + value * 8,
Text = Pick(rng, text + " gambled at the Lucky Wheel and lost " + Times(value) + ".", $"The house always wins. Ask {text}: {value} lost spins.")
});
}
if (rounds >= 3 && item.Goals == 0)
{
list.Add(new Line
{
Weight = 38f,
Text = Pick(rng, text + " has not seen the goal up close. Not once.", text + " heard rumours of a goal. Never confirmed.")
});
}
if (item.Blocks >= 3 && item.Kills == 0)
{
list.Add(new Line
{
Weight = 28 + item.Blocks,
Text = Pick(rng, $"{text} placed {item.Blocks} blocks and hurt nobody. Interior designer.", $"{text} placed {item.Blocks} blocks. All of them harmless.")
});
}
}
for (int num = 0; num < list2.Count; num++)
{
for (int num2 = num + 1; num2 < list2.Count; num2++)
{
list2[num].KilledBy.TryGetValue(list2[num2].Number, out var value2);
list2[num2].KilledBy.TryGetValue(list2[num].Number, out var value3);
if (value2 >= 2 && value3 >= 2)
{
string text3 = MatchData.NameOf(list2[num].Number);
string text4 = MatchData.NameOf(list2[num2].Number);
list.Add(new Line
{
Weight = 58 + (value2 + value3) * 4,
Text = Pick(rng, $"{text3} and {text4} have beef: {value3} to {value2} in kills.", $"{text3} vs {text4} got personal: {value3} to {value2}.")
});
}
}
}
if (list2.Count >= 2)
{
PlayerStats killer = list2.OrderByDescending((PlayerStats p) => p.Kills).First();
int value4;
bool flag = list2.Any((PlayerStats p) => p != killer && p.KilledBy.TryGetValue(killer.Number, out value4) && value4 == killer.Kills);
if (killer.Kills >= 2 && !flag)
{
string arg = MatchData.NameOf(killer.Number);
list.Add(new Line
{
Weight = 55 + killer.Kills * 5,
Text = Pick(rng, $"{arg} is the trap master with {killer.Kills} kills.", $"{arg} chose violence: {killer.Kills} kills.", $"Nobody was safe from {arg}: {killer.Kills} kills.")
});
}
PlayerStats playerStats = list2.OrderByDescending((PlayerStats p) => p.Deaths).First();
if (playerStats.Deaths >= 3)
{
string text5 = MatchData.NameOf(playerStats.Number);
list.Add(new Line
{
Weight = 35 + playerStats.Deaths * 4,
Text = Pick(rng, text5 + " died " + Times(playerStats.Deaths) + ". Someone check on them.", $"{text5} found {playerStats.Deaths} different ways to not finish.")
});
}
if (rounds >= 3)
{
PlayerStats playerStats2 = list2.OrderBy((PlayerStats p) => p.Deaths).First();
if (playerStats2 != playerStats)
{
string text6 = MatchData.NameOf(playerStats2.Number);
list.Add(new Line
{
Weight = ((playerStats2.Deaths == 0) ? 44 : 30),
Text = ((playerStats2.Deaths == 0) ? Pick(rng, text6 + " never died. Suspicious.", text6 + " went the whole match without dying. Show-off.") : Pick(rng, $"{text6} barely got a scratch: only {playerStats2.Deaths} deaths.", $"{text6} was hard to kill: just {playerStats2.Deaths} deaths."))
});
}
}
PlayerStats playerStats3 = list2.OrderByDescending((PlayerStats p) => p.Goals).First();
if (playerStats3.Goals >= 2)
{
string text7 = MatchData.NameOf(playerStats3.Number);
list.Add(new Line
{
Weight = 32 + playerStats3.Goals * 3,
Text = Pick(rng, text7 + " reached the goal " + Times(playerStats3.Goals) + ". Show-off.", $"{text7} made it look easy: {playerStats3.Goals} finishes.")
});
}
PlayerStats playerStats4 = list2.OrderByDescending((PlayerStats p) => p.Coins).First();
if (playerStats4.Coins >= 2)
{
string arg2 = MatchData.NameOf(playerStats4.Number);
list.Add(new Line
{
Weight = 30 + playerStats4.Coins * 4,
Text = Pick(rng, $"{arg2} is the coin goblin: {playerStats4.Coins} coins.", $"{arg2} only came for the money: {playerStats4.Coins} coins.")
});
}
}
if (MatchData.TrapKills.Count > 0)
{
KeyValuePair<string, int> keyValuePair2 = MatchData.TrapKills.OrderByDescending((KeyValuePair<string, int> t) => t.Value).First();
if (keyValuePair2.Value >= 2)
{
string arg3 = MatchData.TrapName(keyValuePair2.Key);
list.Add(new Line
{
Weight = 40 + keyValuePair2.Value * 5,
Text = Pick(rng, $"Deadliest trap: {arg3} with {keyValuePair2.Value} victims.", $"The {arg3} did most of the work: {keyValuePair2.Value} victims.")
});
}
}
return (from l in list.OrderByDescending((Line l) => l.Weight).Take(max)
select l.Text).ToList();
}
internal static string BuildText(int maxHighlights)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine(string.Format("=== MATCH STATS ({0} round{1}) ===", MatchData.Rounds, (MatchData.Rounds == 1) ? "" : "s"));
stringBuilder.AppendLine(string.Format("{0,-24}{1,7}{2,7}{3,6}{4,7}{5,7}{6,7}{7,8}", "Player", "Deaths", "Kills", "Own", "Falls", "Goals", "Coins", "Blocks"));
foreach (PlayerStats item in MatchData.Players.Values.OrderBy((PlayerStats x) => x.Number))
{
stringBuilder.AppendLine($"{MatchData.NameOf(item.Number),-24}{item.Deaths,7}{item.Kills,7}{item.OwnTrapDeaths,6}{item.FallDeaths,7}{item.Goals,7}{item.Coins,7}{item.Blocks,8}");
}
List<string> list = Highlights(maxHighlights);
if (list.Count > 0)
{
stringBuilder.AppendLine("--- Highlights ---");
foreach (string item2 in list)
{
stringBuilder.AppendLine(item2);
}
}
return stringBuilder.ToString();
}
}
internal class StatsWindow : MonoBehaviour
{
private static bool visible;
private static bool autoShown;
private GUIStyle title;
private GUIStyle header;
private GUIStyle cell;
private GUIStyle nameCell;
private GUIStyle line;
private GUIStyle hint;
private Texture2D background;
private float builtForHeight;
private int cachedVersion = -1;
private List<string> highlights = new List<string>();
private readonly List<float> lineHeights = new List<float>();
private static readonly string[] Columns = new string[7] { "Deaths", "Kills", "Own trap", "Falls", "Goals", "Coins", "Blocks" };
private const float MinNameScale = 0.6f;
internal static void Hide()
{
visible = false;
autoShown = false;
Report.NewSeed();
}
private void Update()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
KeyboardShortcut value = Plugin.ToggleKey.Value;
if (((KeyboardShortcut)(ref value)).IsDown())
{
visible = !visible;
}
if (Plugin.ShowAtMatchEnd.Value && MatchData.Ended && !autoShown && Tracker.EndedAt > 0f && Time.unscaledTime - Tracker.EndedAt >= Plugin.EndDelay.Value)
{
autoShown = true;
visible = true;
}
}
private void BuildStyles(float scale)
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Expected O, but got Unknown
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: 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_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_0078: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Expected O, but got Unknown
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: 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_00c2: 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_00d0: Unknown result type (might be due to invalid IL or missing references)
//IL_00dc: Expected O, but got Unknown
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
//IL_0101: Unknown result type (might be due to invalid IL or missing references)
//IL_0113: 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)
//IL_0126: Expected O, but got Unknown
//IL_0131: Unknown result type (might be due to invalid IL or missing references)
//IL_0142: Unknown result type (might be due to invalid IL or missing references)
//IL_0147: Unknown result type (might be due to invalid IL or missing references)
//IL_014e: Unknown result type (might be due to invalid IL or missing references)
//IL_0155: Unknown result type (might be due to invalid IL or missing references)
//IL_0161: Expected O, but got Unknown
//IL_016c: Unknown result type (might be due to invalid IL or missing references)
//IL_0171: 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_018a: Unknown result type (might be due to invalid IL or missing references)
//IL_0196: Expected O, but got Unknown
//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
//IL_01e0: Expected O, but got Unknown
//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
builtForHeight = Screen.height;
PanelSkin.Load();
background = new Texture2D(1, 1);
background.SetPixel(0, 0, new Color(0.08f, 0.07f, 0.11f, 0.92f));
background.Apply();
title = new GUIStyle(GUI.skin.label)
{
fontSize = Mathf.RoundToInt(30f * scale),
fontStyle = (FontStyle)1,
alignment = (TextAnchor)4,
wordWrap = false
};
title.normal.textColor = PanelSkin.TitleColor;
header = new GUIStyle(GUI.skin.label)
{
fontSize = Mathf.RoundToInt(16f * scale),
fontStyle = (FontStyle)1,
alignment = (TextAnchor)4,
wordWrap = false
};
header.normal.textColor = PanelSkin.HeaderColor;
cell = new GUIStyle(GUI.skin.label)
{
fontSize = Mathf.RoundToInt(19f * scale),
alignment = (TextAnchor)4,
wordWrap = false
};
cell.normal.textColor = PanelSkin.TextColor;
nameCell = new GUIStyle(cell)
{
alignment = (TextAnchor)3,
fontStyle = (FontStyle)1,
clipping = (TextClipping)1
};
line = new GUIStyle(GUI.skin.label)
{
fontSize = Mathf.RoundToInt(19f * scale),
alignment = (TextAnchor)0,
wordWrap = true
};
line.normal.textColor = PanelSkin.LineColor;
hint = new GUIStyle(GUI.skin.label)
{
fontSize = Mathf.RoundToInt(13f * scale),
alignment = (TextAnchor)4,
wordWrap = false
};
hint.normal.textColor = PanelSkin.HintColor;
Font val = FontPicker.Get();
if ((Object)(object)val != (Object)null)
{
GUIStyle[] array = (GUIStyle[])(object)new GUIStyle[6] { title, header, cell, nameCell, line, hint };
for (int i = 0; i < array.Length; i++)
{
array[i].font = val;
}
}
}
private static void FitLabel(Rect rect, string text, GUIStyle style)
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Expected O, but got Unknown
//IL_0010: 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_0087: Unknown result type (might be due to invalid IL or missing references)
int fontSize = style.fontSize;
GUIContent val = new GUIContent(text);
float x = style.CalcSize(val).x;
if (x > ((Rect)(ref rect)).width)
{
style.fontSize = Mathf.Max(Mathf.RoundToInt((float)fontSize * 0.6f), Mathf.FloorToInt((float)fontSize * ((Rect)(ref rect)).width / x));
while (text.Length > 4 && style.CalcSize(val).x > ((Rect)(ref rect)).width)
{
text = text.Substring(0, text.Length - 1);
val.text = text.TrimEnd(Array.Empty<char>()) + "...";
}
}
GUI.Label(rect, val, style);
style.fontSize = fontSize;
}
private float MeasureLines(float textWidth, float minHeight, float gap)
{
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Expected O, but got Unknown
lineHeights.Clear();
float num = 0f;
foreach (string highlight in highlights)
{
float num2 = Mathf.Max(minHeight, line.CalcHeight(new GUIContent(highlight), textWidth) + gap);
lineHeights.Add(num2);
num += num2;
}
return num;
}
private void OnGUI()
{
//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
//IL_0216: Unknown result type (might be due to invalid IL or missing references)
//IL_0257: Unknown result type (might be due to invalid IL or missing references)
//IL_028d: Unknown result type (might be due to invalid IL or missing references)
//IL_04ad: Unknown result type (might be due to invalid IL or missing references)
//IL_04bc: Unknown result type (might be due to invalid IL or missing references)
//IL_04c1: Unknown result type (might be due to invalid IL or missing references)
//IL_03e1: Unknown result type (might be due to invalid IL or missing references)
//IL_041a: Unknown result type (might be due to invalid IL or missing references)
//IL_0445: Unknown result type (might be due to invalid IL or missing references)
//IL_02e1: Unknown result type (might be due to invalid IL or missing references)
//IL_0361: Unknown result type (might be due to invalid IL or missing references)
if (!visible)
{
return;
}
float num = Mathf.Clamp((float)Screen.height / 1080f, 0.6f, 2.5f);
if (title == null || !Mathf.Approximately(builtForHeight, (float)Screen.height))
{
BuildStyles(num);
}
if (cachedVersion != MatchData.Version)
{
cachedVersion = MatchData.Version;
highlights = Report.Highlights(Plugin.HighlightCount.Value);
}
List<PlayerStats> list = MatchData.Players.Values.OrderBy((PlayerStats p) => p.Number).ToList();
float num2 = PanelSkin.Padding * num;
float num3 = 30f * num;
float num4 = 250f * num;
float num5 = 96f * num;
float num6 = num2 * 2f + num4 + num5 * (float)Columns.Length;
float num7 = 26f * num;
float num8 = num6 - num2 * 2f - num7;
float num9 = MeasureLines(num8, 30f * num, 6f * num);
int num10 = ((list.Count <= 0) ? 1 : (list.Count + 1));
float num11 = num2 * 2f + 46f * num + num3 * (float)num10 + 14f * num + ((highlights.Count > 0) ? (34f * num + num9) : 0f) + 26f * num;
num11 = Mathf.Min(num11, (float)Screen.height - 20f);
Rect val = default(Rect);
((Rect)(ref val))..ctor(((float)Screen.width - num6) / 2f, ((float)Screen.height - num11) / 2f, num6, num11);
if (PanelSkin.HasTexture)
{
PanelSkin.Draw(val);
}
else
{
GUI.DrawTexture(val, (Texture)(object)background);
}
float num12 = ((Rect)(ref val)).x + num2;
float num13 = ((Rect)(ref val)).y + num2;
string text = MatchData.Rounds + ((MatchData.Rounds == 1) ? " round" : " rounds");
GUI.Label(new Rect(((Rect)(ref val)).x, num13, num6, 40f * num), "MATCH STATS - " + text.ToUpperInvariant(), title);
num13 += 46f * num;
if (list.Count == 0)
{
GUI.Label(new Rect(((Rect)(ref val)).x, num13, num6, num3), "Nothing recorded yet.", cell);
num13 += num3;
}
else
{
for (int num14 = 0; num14 < Columns.Length; num14++)
{
GUI.Label(new Rect(num12 + num4 + (float)num14 * num5, num13, num5, num3), Columns[num14], header);
}
num13 += num3;
foreach (PlayerStats item in list)
{
FitLabel(new Rect(num12, num13, num4 - 8f * num, num3), MatchData.NameOf(item.Number), nameCell);
int[] array = new int[7] { item.Deaths, item.Kills, item.OwnTrapDeaths, item.FallDeaths, item.Goals, item.Coins, item.Blocks };
for (int num15 = 0; num15 < array.Length; num15++)
{
GUI.Label(new Rect(num12 + num4 + (float)num15 * num5, num13, num5, num3), array[num15].ToString(), cell);
}
num13 += num3;
}
}
num13 += 14f * num;
if (highlights.Count > 0)
{
GUI.Label(new Rect(num12, num13, num6 - num2 * 2f, 30f * num), "HIGHLIGHTS", header);
num13 += 34f * num;
for (int num16 = 0; num16 < highlights.Count; num16++)
{
GUI.Label(new Rect(num12, num13, num7, lineHeights[num16]), "•", line);
GUI.Label(new Rect(num12 + num7, num13, num8, lineHeights[num16]), highlights[num16], line);
num13 += lineHeights[num16];
}
}
GUI.Label(new Rect(((Rect)(ref val)).x, ((Rect)(ref val)).yMax - num2 - 22f * num, num6, 22f * num), "Press " + ((object)Plugin.ToggleKey.Value/*cast due to .constrained prefix*/).ToString() + " to close", hint);
}
}
internal class Tracker : MonoBehaviour
{
private const float ScanInterval = 0.25f;
private const float SameDeathWindow = 0.5f;
private static readonly Dictionary<int, float> lastDeath = new Dictionary<int, float>();
private static readonly HashSet<int> reachedGoal = new HashSet<int>();
private GameControl controller;
private GamePhase phase;
private float nextScan;
internal static float EndedAt { get; private set; } = -1f;
private static bool InRun(GamePhase p)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Invalid comparison between Unknown and I4
//IL_0004: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Invalid comparison between Unknown and I4
if ((int)p != 2)
{
return (int)p == 4;
}
return true;
}
private void Update()
{
//IL_002b: 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_0085: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_0088: 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_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
//IL_0096: 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_009e: Unknown result type (might be due to invalid IL or missing references)
LobbyManager instance = LobbyManager.instance;
GameControl val = (((Object)(object)instance != (Object)null) ? instance.CurrentGameController : null);
if ((Object)(object)val == (Object)null)
{
controller = null;
phase = (GamePhase)0;
return;
}
if ((Object)(object)val != (Object)(object)controller)
{
controller = val;
phase = (GamePhase)0;
MatchData.Reset();
lastDeath.Clear();
reachedGoal.Clear();
EndedAt = -1f;
StatsWindow.Hide();
Plugin.LogVerbose("[Stats] New match, statistics reset");
}
GamePhase val2 = val.Phase;
if (val2 != phase)
{
OnPhaseChanged(phase, val2);
phase = val2;
}
if (InRun(phase) && Time.unscaledTime >= nextScan)
{
nextScan = Time.unscaledTime + 0.25f;
ScanCharacters();
}
}
private void OnPhaseChanged(GamePhase from, GamePhase to)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Invalid comparison between Unknown and I4
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Invalid comparison between Unknown and I4
if ((int)to == 2)
{
MatchData.Rounds++;
MatchData.Version++;
reachedGoal.Clear();
ScanCharacters();
Plugin.LogVerbose($"[Stats] Round {MatchData.Rounds} started");
}
else if ((int)to == 5 && !MatchData.Ended)
{
MatchData.Ended = true;
MatchData.Version++;
EndedAt = Time.unscaledTime;
Plugin.Log.LogInfo((object)("[Stats] Match ended\n" + Report.BuildText(Plugin.HighlightCount.Value)));
}
}
private static void ScanCharacters()
{
Character[] array = Object.FindObjectsOfType<Character>();
foreach (Character val in array)
{
if (!((Object)(object)val == (Object)null) && val.networkNumber > 0)
{
PlayerStats playerStats = Register(val);
if (val.Success && reachedGoal.Add(val.networkNumber))
{
playerStats.Goals++;
MatchData.Version++;
Plugin.LogVerbose($"[Stats] {MatchData.NameOf(playerStats.Number)} reached the goal ({playerStats.Goals})");
}
}
}
}
private static PlayerStats Register(Character c)
{
//IL_0074: Unknown result type (might be due to invalid IL or missing references)
//IL_007c: 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_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
PlayerStats playerStats = MatchData.Get(c.networkNumber);
string text = null;
if ((Object)(object)c.AssociatedGamePlayer != (Object)null)
{
text = c.AssociatedGamePlayer.playerName;
}
if (string.IsNullOrEmpty(text) && (Object)(object)c.AssociatedLobbyPlayer != (Object)null)
{
text = c.AssociatedLobbyPlayer.playerName;
}
if (!string.IsNullOrEmpty(text) && text != playerStats.RawName)
{
playerStats.RawName = text;
MatchData.Version++;
}
if ((int)c.CharacterSprite != 0 && c.CharacterSprite != playerStats.Animal)
{
playerStats.Animal = c.CharacterSprite;
MatchData.Version++;
}
return playerStats;
}
internal static void OnDeath(Character victim, string cause, int killerNumber)
{
if (MatchData.Ended || (Object)(object)victim == (Object)null || victim.networkNumber <= 0 || string.IsNullOrEmpty(cause) || cause == "Retry")
{
return;
}
int networkNumber = victim.networkNumber;
if (lastDeath.TryGetValue(networkNumber, out var value) && Time.unscaledTime - value < 0.5f)
{
return;
}
lastDeath[networkNumber] = Time.unscaledTime;
PlayerStats playerStats = Register(victim);
playerStats.Deaths++;
MatchData.Bump(playerStats.DeathCauses, cause);
string text;
switch (cause)
{
case "Falling":
case "Drowning":
case "Drowning_In_Lava":
playerStats.FallDeaths++;
text = "fell";
break;
case "Suicide":
case "Run Timer":
case "AFK Auto-Kill":
playerStats.GaveUp++;
text = "gave up / timed out";
break;
default:
if (killerNumber == networkNumber)
{
playerStats.OwnTrapDeaths++;
MatchData.Bump(MatchData.TrapKills, cause);
text = "own trap";
}
else if (killerNumber > 0)
{
MatchData.Bump(playerStats.KilledBy, killerNumber);
MatchData.Get(killerNumber).Kills++;
MatchData.Bump(MatchData.TrapKills, cause);
text = "trap of " + MatchData.NameOf(killerNumber);
}
else
{
text = "level hazard";
}
break;
}
MatchData.Version++;
Plugin.LogVerbose("[Stats] Death: " + MatchData.NameOf(networkNumber) + " by " + cause + " (" + text + ")");
}
internal static void OnPoint(PointBlock point)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Invalid comparison between Unknown and I4
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
if (!MatchData.Ended && point != null && point.playerNumber > 0)
{
if ((int)point.type == 7)
{
MatchData.Get(point.playerNumber).Coins++;
MatchData.Version++;
}
Plugin.LogVerbose($"[Stats] Point: {point.type} for {MatchData.NameOf(point.playerNumber)}");
}
}
internal static void OnBlockPlaced(Placeable block, int playerNumber)
{
if (!MatchData.Ended && !((Object)(object)block == (Object)null) && playerNumber > 0 && (!((Object)(object)((Component)block).transform.parent != (Object)null) || !((Object)(object)((Component)((Component)block).transform.parent).GetComponentInParent<Placeable>() != (Object)null)))
{
MatchData.Get(playerNumber).Blocks++;
Plugin.LogVerbose("[Stats] Block: " + MatchData.NameOf(playerNumber) + " placed '" + block.Name + "'");
MatchData.Version++;
}
}
}
}
namespace UCHMatchStats.Patches
{
[HarmonyPatch(typeof(Character), "setupDeath")]
internal static class Character_setupDeath_Patch
{
private static void Postfix(Character __instance, string cause, int causedByPlayerNumber)
{
Tracker.OnDeath(__instance, cause, causedByPlayerNumber);
}
}
[HarmonyPatch(typeof(ScoreKeeper), "addPointBlock")]
internal static class ScoreKeeper_addPointBlock_Patch
{
private static void Postfix(PointBlock pointBlock)
{
Tracker.OnPoint(pointBlock);
}
}
[HarmonyPatch(typeof(Placeable), "Place", new Type[]
{
typeof(int),
typeof(bool),
typeof(bool)
})]
internal static class Placeable_Place_Patch
{
private static void Postfix(Placeable __instance, int playerNumber)
{
Tracker.OnBlockPlaced(__instance, playerNumber);
}
}
}