Decompiled source of Spawn Rate Changer v1.1.1

SpawnRateChanger.dll

Decompiled 19 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using WeaponFramework;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("SpawnRateChanger")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.1")]
[assembly: AssemblyProduct("SpawnRateChanger")]
[assembly: AssemblyTitle("SpawnRateChanger")]
[assembly: AssemblyVersion("1.1.1.0")]
namespace SpawnRates;

internal static class FrameworkBridge
{
	private const string Section = "Weights";

	private static readonly Dictionary<string, ConfigEntry<int>> entries = new Dictionary<string, ConfigEntry<int>>(StringComparer.OrdinalIgnoreCase);

	public static void Start()
	{
		SpawnWeights.SetProvider("Spawn Rate Changer", (Func<WeaponInfo, int>)Get, (Action<WeaponInfo, int>)Set);
		WeaponRegistry.Ready += BindAll;
	}

	public static void Refresh()
	{
		SpawnWeights.Refresh();
	}

	private static int Get(WeaponInfo w)
	{
		return Entry(w)?.Value ?? (-1);
	}

	private static void Set(WeaponInfo w, int tickets)
	{
		ConfigEntry<int> val = Entry(w);
		if (val != null)
		{
			val.Value = tickets;
		}
	}

	private static ConfigEntry<int> Entry(WeaponInfo w)
	{
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Expected O, but got Unknown
		if (w == null)
		{
			return null;
		}
		if (entries.TryGetValue(w.Id, out var value))
		{
			return value;
		}
		ConfigFile config = ((BaseUnityPlugin)Plugin.Instance).Config;
		bool saveOnConfigSet = config.SaveOnConfigSet;
		config.SaveOnConfigSet = false;
		try
		{
			value = config.Bind<int>("Weights", Key(w), -1, new ConfigDescription(w.Name + " (" + w.Category + "). Spawn tickets: -1 = its default (" + w.DefaultTickets + "), 0 = never, up to " + 1000 + ".", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 1000), new object[0]));
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Couldn't make a weight entry for " + w.Id + " (it keeps its default): " + ex.Message));
			value = null;
		}
		finally
		{
			config.SaveOnConfigSet = saveOnConfigSet;
		}
		entries[w.Id] = value;
		if (value != null && value.Value < 0)
		{
			int num = OldWeight(config, w);
			if (num >= 0)
			{
				value.Value = num;
				Plugin.Log.LogInfo((object)("Carried over " + w.Name + " = " + num + " from the old per-row settings."));
			}
		}
		return value;
	}

	private static string Key(WeaponInfo w)
	{
		string text = Clean(w.Id);
		if (text.Length <= 0)
		{
			return "weapon " + w.Index;
		}
		return text;
	}

	private static void BindAll()
	{
		ConfigFile config = ((BaseUnityPlugin)Plugin.Instance).Config;
		bool saveOnConfigSet = config.SaveOnConfigSet;
		config.SaveOnConfigSet = false;
		try
		{
			foreach (WeaponInfo item in WeaponRegistry.All)
			{
				Entry(item);
			}
			ForgetOldLines(config);
			config.Save();
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Failed to make the weight entries: " + ex));
		}
		finally
		{
			config.SaveOnConfigSet = saveOnConfigSet;
		}
		Plugin.Log.LogInfo((object)("Spawn weights for " + entries.Count + " weapons in [Weights] of " + config.ConfigFilePath));
	}

	private static Dictionary<ConfigDefinition, string> Orphans(ConfigFile config)
	{
		PropertyInfo propertyInfo = AccessTools.Property(typeof(ConfigFile), "OrphanedEntries");
		if ((object)propertyInfo == null)
		{
			return null;
		}
		return propertyInfo.GetValue(config, null) as Dictionary<ConfigDefinition, string>;
	}

	private static int OldWeight(ConfigFile config, WeaponInfo w)
	{
		try
		{
			Dictionary<ConfigDefinition, string> dictionary = Orphans(config);
			if (dictionary == null)
			{
				return -1;
			}
			string b = Clean(w.MenuLabel ?? w.OriginalName);
			foreach (KeyValuePair<ConfigDefinition, string> item in dictionary)
			{
				if (!(item.Key.Section == "Weights") && string.Equals(Clean(item.Key.Key), b, StringComparison.OrdinalIgnoreCase) && int.TryParse(item.Value.Trim(), out var result) && result >= 0)
				{
					dictionary.Remove(item.Key);
					return Math.Min(result, 1000);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Couldn't read the old settings: " + ex.Message));
		}
		return -1;
	}

	private static void ForgetOldLines(ConfigFile config)
	{
		try
		{
			Dictionary<ConfigDefinition, string> dictionary = Orphans(config);
			if (dictionary == null || dictionary.Count == 0)
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (WeaponInfo item in WeaponRegistry.All)
			{
				if (entries.TryGetValue(item.Id, out var value) && value != null)
				{
					hashSet.Add(Clean(item.MenuLabel ?? item.OriginalName));
				}
			}
			List<ConfigDefinition> list = new List<ConfigDefinition>();
			foreach (KeyValuePair<ConfigDefinition, string> item2 in dictionary)
			{
				if (item2.Key.Section != "Weights" && hashSet.Contains(Clean(item2.Key.Key)))
				{
					list.Add(item2.Key);
				}
			}
			foreach (ConfigDefinition item3 in list)
			{
				dictionary.Remove(item3);
			}
			if (list.Count > 0)
			{
				Plugin.Log.LogInfo((object)("Removed " + list.Count + " old per-row lines from the config (the weights are under [Weights] now)."));
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Couldn't tidy the old settings: " + ex.Message));
		}
	}

	private static string Clean(string s)
	{
		if (s == null)
		{
			return "";
		}
		StringBuilder stringBuilder = new StringBuilder();
		foreach (char value in s)
		{
			if ("=\n\t\r\\\"'[]".IndexOf(value) < 0)
			{
				stringBuilder.Append(value);
			}
		}
		return stringBuilder.ToString().Trim();
	}
}
[BepInPlugin("stickfight.spawnratechanger", "Spawn Rate Changer", "1.1.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "stickfight.spawnratechanger";

	public const string Name = "Spawn Rate Changer";

	public const string Version = "1.1.1";

	private const string FrameworkGuid = "stickfight.weaponframework";

	internal static ManualLogSource Log;

	internal static Plugin Instance;

	internal static bool WithFramework;

	private FileSystemWatcher watcher;

	private volatile bool fileChanged;

	private void Awake()
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Expected O, but got Unknown
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		Instance = this;
		WithFramework = Chainloader.PluginInfos.ContainsKey("stickfight.weaponframework");
		if (WithFramework)
		{
			FrameworkBridge.Start();
		}
		else
		{
			new Harmony("stickfight.spawnratechanger").PatchAll(typeof(Plugin).Assembly);
		}
		GameObject val = new GameObject("SpawnRateChangerManager");
		Object.DontDestroyOnLoad((Object)val);
		((Object)val).hideFlags = (HideFlags)61;
		val.AddComponent<ReloadPoller>();
		try
		{
			watcher = new FileSystemWatcher(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath));
			watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
			watcher.Changed += delegate
			{
				fileChanged = true;
			};
			watcher.Created += delegate
			{
				fileChanged = true;
			};
			watcher.Renamed += delegate
			{
				fileChanged = true;
			};
			watcher.EnableRaisingEvents = true;
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Can't watch the config file; edits take effect after a restart. " + ex.Message));
		}
		Log.LogInfo((object)("Spawn Rate Changer 1.1.1 loaded" + (WithFramework ? " (spawn weights for Weapon Framework)." : ".")));
	}

	internal bool TakeFileChanged()
	{
		if (!fileChanged)
		{
			return false;
		}
		fileChanged = false;
		return true;
	}
}
internal class ReloadPoller : MonoBehaviour
{
	private const float Delay = 0.5f;

	private float reloadAt = -1f;

	private void Update()
	{
		Plugin instance = Plugin.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			return;
		}
		if (instance.TakeFileChanged())
		{
			reloadAt = Time.realtimeSinceStartup + 0.5f;
		}
		if (!(reloadAt < 0f) && !(Time.realtimeSinceStartup < reloadAt))
		{
			reloadAt = -1f;
			try
			{
				((BaseUnityPlugin)instance).Config.Reload();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Couldn't read the config yet, retrying: " + ex.Message));
				reloadAt = Time.realtimeSinceStartup + 0.5f;
				return;
			}
			Plugin.Log.LogInfo((object)"Config reloaded.");
			if (Plugin.WithFramework)
			{
				FrameworkBridge.Refresh();
			}
			else
			{
				Rates.Rebuild();
			}
		}
	}
}
[HarmonyPatch]
internal static class Rates
{
	private const int MaxWeight = 1000;

	private static readonly FieldRef<WeaponSelectionHandler, List<byte>> s_Bag = AccessTools.FieldRefAccess<WeaponSelectionHandler, List<byte>>("m_WeaponRaritiesArray");

	private static readonly FieldRef<WeaponSelectionHandler, List<SingleWeaponUI>> s_Weapons = AccessTools.FieldRefAccess<WeaponSelectionHandler, List<SingleWeaponUI>>("m_Weapons");

	private static readonly MethodInfo s_IsActive = AccessTools.Method(typeof(WeaponSelectionHandler), "GetActiveStateForWeapon", (Type[])null, (Type[])null);

	private static readonly MethodInfo s_Generate = AccessTools.Method(typeof(WeaponSelectionHandler), "GenerateWeaponRarityArray", (Type[])null, (Type[])null);

	private static readonly Dictionary<SingleWeaponUI, ConfigEntry<int>> s_Entries = new Dictionary<SingleWeaponUI, ConfigEntry<int>>();

	private static WeaponSelectionHandler s_Handler;

	private static bool s_LastBuiltOnline;

	private static string s_LastOdds;

	[HarmonyPostfix]
	[HarmonyPriority(0)]
	[HarmonyPatch(typeof(WeaponSelectionHandler), "InitWeaponList")]
	private static void AfterInit(WeaponSelectionHandler __instance)
	{
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0124: Expected O, but got Unknown
		s_Handler = __instance;
		s_Entries.Clear();
		ConfigFile config = ((BaseUnityPlugin)Plugin.Instance).Config;
		bool saveOnConfigSet = config.SaveOnConfigSet;
		config.SaveOnConfigSet = false;
		try
		{
			HashSet<string> hashSet = new HashSet<string>();
			foreach (SingleWeaponUI item in s_Weapons.Invoke(__instance))
			{
				string text = Clean(item.Category);
				string text2 = Clean(item.WeaponName);
				if (text.Length == 0)
				{
					text = "Other";
				}
				if (text2.Length == 0)
				{
					text2 = "Weapon " + item.WeaponIndex;
				}
				if (!hashSet.Add(text + "\n" + text2))
				{
					text2 = text2 + " " + item.WeaponIndex;
					hashSet.Add(text + "\n" + text2);
				}
				s_Entries[item] = config.Bind<int>(text, text2, -1, new ConfigDescription("Spawn weight (tickets in the draw; bigger = more common). -1 = unchanged (game: " + item.Rarity + ", other mods may change it), 0 = never.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 1000), new object[0]));
			}
			config.Save();
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Failed to make config entries: " + ex));
		}
		finally
		{
			config.SaveOnConfigSet = saveOnConfigSet;
		}
		Plugin.Log.LogInfo((object)("Spawn weights for " + s_Entries.Count + " weapons in " + config.ConfigFilePath));
	}

	[HarmonyPostfix]
	[HarmonyPriority(0)]
	[HarmonyPatch(typeof(WeaponSelectionHandler), "GenerateWeaponRarityArray")]
	private static void AfterGenerate(WeaponSelectionHandler __instance)
	{
		s_LastBuiltOnline = MatchmakingHandler.IsNetworkMatch;
		if (s_LastBuiltOnline)
		{
			return;
		}
		try
		{
			List<byte> list = s_Bag.Invoke(__instance);
			if (list == null)
			{
				return;
			}
			foreach (KeyValuePair<SingleWeaponUI, ConfigEntry<int>> s_Entry in s_Entries)
			{
				int value = s_Entry.Value.Value;
				if (value < 0)
				{
					continue;
				}
				byte index = s_Entry.Key.WeaponIndex;
				list.RemoveAll((byte b) => b == index);
				if ((bool)s_IsActive.Invoke(__instance, new object[1] { s_Entry.Key }))
				{
					for (int num = 0; num < value; num++)
					{
						list.Add(index);
					}
				}
			}
			LogOdds(list);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Failed to apply spawn weights: " + ex));
		}
	}

	[HarmonyPrefix]
	[HarmonyPatch(typeof(WeaponSelectionHandler), "GetRandomWeaponIndex")]
	private static void BeforeDraw(WeaponSelectionHandler __instance)
	{
		if (s_LastBuiltOnline != MatchmakingHandler.IsNetworkMatch)
		{
			s_Generate.Invoke(__instance, null);
		}
	}

	internal static void Rebuild()
	{
		if ((Object)(object)s_Handler == (Object)null)
		{
			return;
		}
		try
		{
			s_Generate.Invoke(s_Handler, null);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Failed to rebuild the spawn bag: " + ex));
		}
	}

	private static void LogOdds(List<byte> bag)
	{
		Dictionary<byte, int> dictionary = new Dictionary<byte, int>();
		foreach (byte item in bag)
		{
			dictionary.TryGetValue(item, out var value);
			dictionary[item] = value + 1;
		}
		StringBuilder stringBuilder = new StringBuilder("Spawn odds:");
		if (bag.Count == 0)
		{
			stringBuilder.Append(" nothing can spawn");
		}
		foreach (SingleWeaponUI key in s_Entries.Keys)
		{
			if (dictionary.TryGetValue(key.WeaponIndex, out var value2))
			{
				stringBuilder.AppendFormat(" {0} {1:0.#}%,", key.WeaponName, 100f * (float)value2 / (float)bag.Count);
			}
		}
		string text = stringBuilder.ToString().TrimEnd(new char[1] { ',' });
		if (!(text == s_LastOdds))
		{
			s_LastOdds = text;
			Plugin.Log.LogInfo((object)text);
		}
	}

	private static string Clean(string s)
	{
		if (s == null)
		{
			return "";
		}
		StringBuilder stringBuilder = new StringBuilder();
		foreach (char value in s)
		{
			if ("=\n\t\r\\\"'[]".IndexOf(value) < 0)
			{
				stringBuilder.Append(value);
			}
		}
		return stringBuilder.ToString().Trim();
	}
}