Decompiled source of RealmForgeStats v1.1.1

plugins/RealmForgeStats/RealmForgeStats.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("RealmForgeStats")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+169585884d24fa6066e4c873136bb6cb6dd9421d")]
[assembly: AssemblyProduct("RealmForgeStats")]
[assembly: AssemblyTitle("RealmForgeStats")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RealmForgeStats
{
	internal static class Feats
	{
		internal static readonly KeyValuePair<string, PlayerStatType>[] Counters = new KeyValuePair<string, PlayerStatType>[35]
		{
			P("kills", (PlayerStatType)6),
			P("hits_dealt", (PlayerStatType)5),
			P("hits_taken", (PlayerStatType)10),
			P("arrows", (PlayerStatType)51),
			P("boss_kills", (PlayerStatType)85),
			P("boss_lasthits", (PlayerStatType)86),
			P("boss_solo", (PlayerStatType)147),
			P("boss_group", (PlayerStatType)146),
			P("tree_chops", (PlayerStatType)27),
			P("trees", (PlayerStatType)28),
			P("logs", (PlayerStatType)36),
			P("walked", (PlayerStatType)17),
			P("ran", (PlayerStatType)18),
			P("sailed", (PlayerStatType)19),
			P("airborne", (PlayerStatType)20),
			P("portals", (PlayerStatType)15),
			P("deepest_dungeon", (PlayerStatType)149),
			P("pieces_built", (PlayerStatType)168),
			P("tallest_build", (PlayerStatType)130),
			P("max_comfort", (PlayerStatType)132),
			P("mine_hits", (PlayerStatType)37),
			P("ore_mined", (PlayerStatType)38),
			P("bees", (PlayerStatType)76),
			P("sap", (PlayerStatType)77),
			P("buried_treasure", (PlayerStatType)133),
			P("fish_caught", (PlayerStatType)160),
			P("fish_hooked", (PlayerStatType)158),
			P("fish_lost", (PlayerStatType)159),
			P("days_survived", (PlayerStatType)128),
			P("days_record", (PlayerStatType)129),
			P("sleeps", (PlayerStatType)23),
			P("tamed", (PlayerStatType)48),
			P("pets_given", (PlayerStatType)191),
			P("jumps", (PlayerStatType)3),
			P("raven_talks", (PlayerStatType)46)
		};

		internal static readonly KeyValuePair<string, PlayerStatType>[] TreeDeaths = new KeyValuePair<string, PlayerStatType>[10]
		{
			P("Fir", (PlayerStatType)193),
			P("Oak", (PlayerStatType)194),
			P("Pine", (PlayerStatType)195),
			P("Beech", (PlayerStatType)199),
			P("Birch", (PlayerStatType)200),
			P("Swamp", (PlayerStatType)198),
			P("Snow fir", (PlayerStatType)201),
			P("Snow pine", (PlayerStatType)202),
			P("Ashlands", (PlayerStatType)196),
			P("Yggdrasil", (PlayerStatType)197)
		};

		internal const int TopCreatures = 30;

		private static KeyValuePair<string, PlayerStatType> P(string k, PlayerStatType v)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return new KeyValuePair<string, PlayerStatType>(k, v);
		}
	}
	[BepInPlugin("org.zrikzinc.realmforgestats", "RealmForge Stats", "1.1.1")]
	public class RealmForgeStatsPlugin : BaseUnityPlugin
	{
		public const string GUID = "org.zrikzinc.realmforgestats";

		internal const string RPC = "RealmForgeStats";

		private const float SendIntervalSec = 120f;

		internal static ManualLogSource Log;

		private float _next;

		private string _lastPayload = "";

		private const float RetryWhenNotReadySec = 5f;

		internal static readonly Dictionary<PlayerStatType, string> DeathStats = new Dictionary<PlayerStatType, string>
		{
			{
				(PlayerStatType)0,
				"total"
			},
			{
				(PlayerStatType)56,
				"enemy"
			},
			{
				(PlayerStatType)57,
				"player"
			},
			{
				(PlayerStatType)58,
				"fall"
			},
			{
				(PlayerStatType)59,
				"drowning"
			},
			{
				(PlayerStatType)60,
				"burning"
			},
			{
				(PlayerStatType)61,
				"freezing"
			},
			{
				(PlayerStatType)62,
				"poison"
			},
			{
				(PlayerStatType)63,
				"smoke"
			},
			{
				(PlayerStatType)65,
				"edge_of_world"
			},
			{
				(PlayerStatType)66,
				"impact"
			},
			{
				(PlayerStatType)67,
				"cart"
			},
			{
				(PlayerStatType)68,
				"tree"
			},
			{
				(PlayerStatType)69,
				"self"
			},
			{
				(PlayerStatType)70,
				"structure"
			},
			{
				(PlayerStatType)71,
				"turret"
			},
			{
				(PlayerStatType)72,
				"boat"
			},
			{
				(PlayerStatType)73,
				"stalactite"
			},
			{
				(PlayerStatType)203,
				"drawbridge"
			},
			{
				(PlayerStatType)204,
				"lava"
			}
		};

		private void Awake()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			new Harmony("org.zrikzinc.realmforgestats").PatchAll();
			Log.LogInfo((object)"RealmForgeStats 1.1.1 loaded");
		}

		private void Update()
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer() && !(Time.time < _next))
			{
				if ((Object)(object)Player.m_localPlayer == (Object)null || ZRoutedRpc.instance == null || (Object)(object)Game.instance == (Object)null)
				{
					_next = Time.time + 5f;
					return;
				}
				_next = Time.time + 120f;
				TrySend();
			}
		}

		private void TrySend()
		{
			try
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || ZRoutedRpc.instance == null)
				{
					return;
				}
				string text = Collect(localPlayer);
				if (!string.IsNullOrEmpty(text) && !(text == _lastPayload))
				{
					_lastPayload = text;
					ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
					if (serverPeer != null)
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "RealmForgeStats", new object[1] { text });
					}
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("stats send failed: " + ex.Message));
			}
		}

		private static string Collect(Player player)
		{
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Expected I4, but got Unknown
			PlayerProfile val = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile() : null);
			if (val == null)
			{
				return null;
			}
			StringBuilder stringBuilder = new StringBuilder(512);
			stringBuilder.Append('{');
			Json.Str(stringBuilder, "name", player.GetPlayerName());
			stringBuilder.Append(',');
			Json.Num(stringBuilder, "playerId", val.GetPlayerID());
			stringBuilder.Append(",\"skills\":{");
			Skills skills = ((Character)player).GetSkills();
			bool flag = true;
			if ((Object)(object)skills != (Object)null)
			{
				foreach (Skill skill in skills.GetSkillList())
				{
					if (skill != null && skill.m_info != null && !(skill.m_level <= 0f))
					{
						if (!flag)
						{
							stringBuilder.Append(',');
						}
						flag = false;
						Json.Num(stringBuilder, ((int)skill.m_info.m_skill).ToString(CultureInfo.InvariantCulture), skill.m_level);
					}
				}
			}
			stringBuilder.Append("},\"deaths\":{");
			flag = true;
			foreach (KeyValuePair<PlayerStatType, string> deathStat in DeathStats)
			{
				float stat = val.GetStat(deathStat.Key);
				if (!(stat <= 0f))
				{
					if (!flag)
					{
						stringBuilder.Append(',');
					}
					flag = false;
					Json.Num(stringBuilder, deathStat.Value, stat);
				}
			}
			stringBuilder.Append("},\"feats\":{");
			flag = AppendCounters(stringBuilder, val, Feats.Counters, first: true);
			stringBuilder.Append("},\"tree_deaths\":{");
			AppendCounters(stringBuilder, val, Feats.TreeDeaths, first: true);
			stringBuilder.Append("},\"kills\":{");
			AppendKills(stringBuilder, val);
			stringBuilder.Append("}}");
			return stringBuilder.ToString();
		}

		private static bool AppendCounters(StringBuilder sb, PlayerProfile profile, KeyValuePair<string, PlayerStatType>[] table, bool first)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < table.Length; i++)
			{
				KeyValuePair<string, PlayerStatType> keyValuePair = table[i];
				float stat = profile.GetStat(keyValuePair.Value);
				if (!(stat <= 0f))
				{
					if (!first)
					{
						sb.Append(',');
					}
					first = false;
					Json.Num(sb, keyValuePair.Key, stat);
				}
			}
			return first;
		}

		private static void AppendKills(StringBuilder sb, PlayerProfile profile)
		{
			PlayerStats val = profile.m_playerStats[0];
			Dictionary<string, float> dictionary = ((val != null && val.m_enemyStats != null && val.m_enemyStats.Length != 0) ? val.m_enemyStats[0] : null);
			if (dictionary == null || dictionary.Count == 0)
			{
				return;
			}
			List<KeyValuePair<string, float>> list = new List<KeyValuePair<string, float>>(dictionary);
			list.Sort((KeyValuePair<string, float> a, KeyValuePair<string, float> b) => b.Value.CompareTo(a.Value));
			int num = 0;
			foreach (KeyValuePair<string, float> item in list)
			{
				if (num >= 30)
				{
					break;
				}
				if (!(item.Value <= 0f) && !string.IsNullOrEmpty(item.Key))
				{
					if (num > 0)
					{
						sb.Append(',');
					}
					Json.Num(sb, item.Key, item.Value);
					num++;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Game), "Start")]
	public static class RegisterRpcPatch
	{
		private static void Postfix()
		{
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<string>("RealmForgeStats", (Action<long, string>)StatsStore.OnStats);
				RealmForgeStatsPlugin.Log.LogInfo((object)"RealmForgeStats RPC registered");
			}
		}
	}
	internal static class StatsStore
	{
		private const int MaxPayload = 16384;

		private static readonly object Lock = new object();

		private static readonly Dictionary<long, string> Rows = new Dictionary<long, string>();

		internal static void OnStats(long sender, string payload)
		{
			try
			{
				if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || string.IsNullOrEmpty(payload) || payload.Length > 16384)
				{
					return;
				}
				payload = Stamp(payload);
				if (payload != null)
				{
					bool flag;
					lock (Lock)
					{
						flag = !Rows.ContainsKey(sender);
						Rows[sender] = payload;
						Write();
					}
					if (flag)
					{
						RealmForgeStatsPlugin.Log.LogInfo((object)("stats received from peer " + sender + " (" + payload.Length + " bytes) -> " + Path.Combine(Dir(), "live-stats.json")));
					}
				}
			}
			catch (Exception ex)
			{
				RealmForgeStatsPlugin.Log.LogWarning((object)("stats receive failed: " + ex.Message));
			}
		}

		private static string Stamp(string payload)
		{
			string text = payload.TrimStart();
			if (!text.StartsWith("{") || text.Length < 2)
			{
				return null;
			}
			string text2 = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture);
			return "{\"received\":\"" + text2 + "\"," + text.Substring(1);
		}

		private static string Dir()
		{
			string environmentVariable = Environment.GetEnvironmentVariable("REALMFORGE_STATS_DIR");
			if (!string.IsNullOrEmpty(environmentVariable))
			{
				return environmentVariable;
			}
			return Path.Combine(Directory.GetCurrentDirectory(), "..", "stats");
		}

		private static void Write()
		{
			string text = Dir();
			Directory.CreateDirectory(text);
			StringBuilder stringBuilder = new StringBuilder(1024);
			stringBuilder.Append("{\"source\":\"RealmForgeStats\",\"players\":[");
			bool flag = true;
			foreach (string value in Rows.Values)
			{
				if (!flag)
				{
					stringBuilder.Append(',');
				}
				flag = false;
				stringBuilder.Append(value);
			}
			stringBuilder.Append("]}");
			string text2 = Path.Combine(text, "live-stats.json");
			string text3 = text2 + ".tmp";
			File.WriteAllText(text3, stringBuilder.ToString());
			if (File.Exists(text2))
			{
				File.Delete(text2);
			}
			File.Move(text3, text2);
		}
	}
	internal static class Json
	{
		internal static void Str(StringBuilder sb, string key, string value)
		{
			sb.Append('"').Append(Escape(key)).Append("\":\"")
				.Append(Escape(value ?? ""))
				.Append('"');
		}

		internal static void Num(StringBuilder sb, string key, double value)
		{
			sb.Append('"').Append(Escape(key)).Append("\":")
				.Append(value.ToString("0.###", CultureInfo.InvariantCulture));
		}

		private static string Escape(string s)
		{
			StringBuilder stringBuilder = new StringBuilder(s.Length + 8);
			foreach (char c in s)
			{
				switch (c)
				{
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4"));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}
	}
}