Decompiled source of BetterRepair v0.1.0

plugins/BetterRepair.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using BetterRepair.Data;
using BetterRepair.Logic;
using BetterRepair.Scene;
using BetterRepair.Settings;
using BetterRepair.Sync;
using CatLib.Config;
using CatLib.Core;
using CatLib.DevTools;
using CatLib.Game.Events;
using CatLib.Localization;
using CatLib.Logging;
using CatLib.Net;
using CatLib.Saves;
using CatLib.UI;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CatLib contributors")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+4162013e7c932bfa1cf0340558d83186f1fd5817")]
[assembly: AssemblyProduct("CatLib")]
[assembly: AssemblyTitle("BetterRepair")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
[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 BetterRepair
{
	public sealed class BetterRepairController
	{
		public const double ScanIntervalSeconds = 2.0;

		private readonly CatLogger _log;

		private readonly TextCatalog _texts;

		private readonly List<WorkstationView> _tables = new List<WorkstationView>();

		private readonly DayClock _clock = new DayClock();

		private double _nextScan;

		private bool _hostStockKnown;

		private bool _reported;

		private bool _paused;

		public RepairSettings Settings { get; set; }

		public StockSync Sync { get; set; }

		public CardboardStock Stock { get; }

		public int CurrentStock => Stock.Current;

		public BetterRepairController(CatLogger log, TextCatalog texts)
		{
			_log = log;
			_texts = texts;
			Stock = new CardboardStock(3);
		}

		public void Initialize()
		{
			ReadSettings();
			Stock.SetCurrent(Stock.Maximum);
			Sync.StockReceived += OnStockReceived;
		}

		public void OnSettingsChanged()
		{
			int current = Stock.Current;
			ReadSettings();
			_log.Info($"Cardboard settings: {(Stock.Unlimited ? "unlimited" : $"stock {Stock.Maximum}")}, refill {Describe(Settings.Refill.Value, Settings.RefillAmount.Value)}, now {Stock.Describe()}");
			if (CatNetwork.IsAuthority && current != Stock.Current)
			{
				Sync.Broadcast(Stock.Current);
			}
			ApplyAll();
		}

		public void OnStoreLoaded(int? stored)
		{
			ReadSettings();
			Stock.SetCurrent(stored ?? Stock.Maximum);
			ApplyAll();
		}

		public void OnMainMenuLoaded()
		{
			Forget();
			ReadSettings();
			Stock.SetCurrent(Stock.Maximum);
			_hostStockKnown = false;
		}

		public void Forget()
		{
			_tables.Clear();
			_clock.Reset();
			_reported = false;
			_nextScan = 0.0;
		}

		public void Update()
		{
			if (!CatNetwork.IsActive("catlib.betterrepair"))
			{
				if (!_paused)
				{
					_paused = true;
					Forget();
					_log.Info("Better Repair is paused in this session because not every player has it, the repair table works like in the game");
				}
				return;
			}
			if (_paused)
			{
				_paused = false;
				_log.Info("Better Repair is active again in this session");
			}
			try
			{
				Track();
				if (_tables.Count != 0)
				{
					WatchDay();
					WatchTables();
				}
			}
			catch (Exception ex)
			{
				if (!_reported)
				{
					_reported = true;
					_log.Error("Updating the repair table failed, the mod stops touching it until the next level", ex);
				}
				_tables.Clear();
			}
		}

		public string Describe()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>
			{
				"cardboard " + Stock.Describe(),
				$"role {CatNetwork.Role}",
				_paused ? "paused in this session" : "active"
			};
			if (!CatNetwork.IsAuthority)
			{
				list.Add(_hostStockKnown ? "stock from the host" : "waiting for the host");
			}
			list.Add("period " + (_clock.Last.HasValue ? Period(_clock.Last.Value) : "unknown"));
			foreach (WorkstationView item in _tables.Where((WorkstationView table) => table.IsAlive))
			{
				list.Add($"{item.Name}: game index {item.GameIndex}, {item.DescribeButtons()}");
			}
			if (_tables.Count == 0)
			{
				list.Add("no repair table found");
			}
			string text = string.Join("; ", list);
			_log.Info("Repair tables: " + text);
			return $"{Stock.Describe()}, {_tables.Count} table(s), see the log";
		}

		public string RefillNow()
		{
			if (!CatNetwork.IsAuthority)
			{
				return "only the host refills";
			}
			Refill("the developer menu");
			return Stock.Describe();
		}

		private void ReadSettings()
		{
			if (Settings != null)
			{
				Stock.Unlimited = Settings.Unlimited.Value;
				Stock.SetMaximum(Settings.Stock.Value);
			}
		}

		private void Track()
		{
			_tables.RemoveAll((WorkstationView table) => !table.IsAlive);
			if (_tables.Count > 0 || FrameLoop.Realtime < _nextScan)
			{
				return;
			}
			_nextScan = FrameLoop.Realtime + 2.0;
			foreach (EntityRepairWorkstation item in WorkstationView.FindAll())
			{
				WorkstationView view = new WorkstationView(item);
				if (_tables.All((WorkstationView table) => table.Pointer != view.Pointer))
				{
					_tables.Add(view);
					_log.Info($"Found the repair table {view.Name}: game index {view.GameIndex}, {view.Capacity} sheet(s) on the table, cardboard {Stock.Describe()}");
					Apply(view);
				}
			}
		}

		private void WatchDay()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected I4, but got Unknown
			if (!Singleton<GameTimeManager>.HasInstance())
			{
				return;
			}
			int period = (int)Singleton<GameTimeManager>.Instance.CurrentTimePeriod;
			bool changed;
			bool flag = _clock.Observe(period, out changed);
			if (changed)
			{
				_log.Info($"Time period is now {Period(period)}, game index {string.Join(", ", _tables.Select((WorkstationView table) => table.GameIndex))}, cardboard {Stock.Describe()}");
				if (flag && CatNetwork.IsAuthority)
				{
					Refill("a new day");
				}
				else
				{
					ApplyAll();
				}
			}
		}

		private void WatchTables()
		{
			foreach (WorkstationView table in _tables)
			{
				int gameIndex = table.GameIndex;
				if (table.AppliedIndex >= 0 && gameIndex != table.AppliedIndex)
				{
					if (gameIndex > table.AppliedIndex)
					{
						OnSheetsUsed(table, gameIndex - table.AppliedIndex);
					}
					else
					{
						_log.Info($"The game set the repair table {table.Name} to index {gameIndex} (the mod had {table.AppliedIndex}), cardboard {Stock.Describe()}");
					}
					ApplyAll();
				}
			}
		}

		private void OnSheetsUsed(WorkstationView table, int sheets)
		{
			if (!CatNetwork.IsAuthority)
			{
				_log.Info($"A repair on {table.Name} used {sheets} sheet(s), waiting for the host's stock");
				return;
			}
			int num = Stock.Consume(sheets);
			_log.Info($"A repair on {table.Name} used {sheets} sheet(s), cardboard {Stock.Describe()}");
			if (num > 0)
			{
				Sync.Broadcast(Stock.Current);
			}
			ShowLeft();
		}

		private void OnStockReceived(int current)
		{
			if (CatNetwork.IsAuthority)
			{
				return;
			}
			int current2 = Stock.Current;
			bool hostStockKnown = _hostStockKnown;
			_hostStockKnown = true;
			Stock.SetCurrent(current);
			_log.Info("Cardboard from the host: " + Stock.Describe());
			ApplyAll();
			if (hostStockKnown)
			{
				if (Stock.Current < current2)
				{
					ShowLeft();
				}
				else if (Stock.Current > current2)
				{
					ShowRefilled();
				}
			}
		}

		private void Refill(string reason)
		{
			ReadSettings();
			int num = ((Settings == null) ? Stock.Refill(RefillMode.Full, 0) : Stock.Refill(Settings.Refill.Value, Settings.RefillAmount.Value));
			_log.Info($"Cardboard refilled by {reason}: +{num}, now {Stock.Describe()}");
			Sync.Broadcast(Stock.Current);
			ApplyAll();
			if (num > 0)
			{
				ShowRefilled();
			}
		}

		private void ApplyAll()
		{
			foreach (WorkstationView table in _tables)
			{
				if (table.IsAlive)
				{
					Apply(table);
				}
			}
		}

		private void Apply(WorkstationView table)
		{
			if (CatNetwork.IsAuthority || _hostStockKnown)
			{
				table.Apply(Stock.GameIndex(table.Capacity));
			}
		}

		private void ShowLeft()
		{
			if (!Stock.Unlimited && Settings != null && Settings.Messages.Value)
			{
				Notifications.Show(Stock.IsEmpty ? _texts.Get("message.empty") : _texts.Format("message.left", new object[2] { Stock.Current, Stock.Maximum }), (string)null);
			}
		}

		private void ShowRefilled()
		{
			if (!Stock.Unlimited && Settings != null && Settings.Messages.Value)
			{
				Notifications.Show(_texts.Format("message.refilled", new object[2] { Stock.Current, Stock.Maximum }), (string)null);
			}
		}

		private static string Describe(RefillMode mode, int amount)
		{
			if (mode != RefillMode.Full)
			{
				return $"by {amount}";
			}
			return "to full";
		}

		private static string Period(int period)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (!Enum.IsDefined(typeof(GameTimePeriod), period))
			{
				return period.ToString();
			}
			return ((object)(GameTimePeriod)period/*cast due to .constrained prefix*/).ToString();
		}
	}
	[BepInPlugin("catlib.betterrepair", "Better Repair", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class BetterRepairPlugin : BasePlugin
	{
		public const string LanguageResourcePrefix = "BetterRepair.Lang.";

		public const int DataVersion = 1;

		private BetterRepairController _controller;

		private StockStore _store;

		public override void Load()
		{
			CatLogger val = CatLogger.From(((BasePlugin)this).Log);
			CatSettings val2 = CatSettings.For((BasePlugin)(object)this);
			int count = val2.Texts.Count;
			CatNetwork.Declare((BasePlugin)(object)this, (SessionPolicy)1, (VersionRule)2);
			_controller = new BetterRepairController(val, val2.Texts);
			_controller.Settings = new RepairSettings(val2, _controller.OnSettingsChanged);
			_controller.Sync = new StockSync(CatNetwork.Channel((BasePlugin)(object)this), () => _controller.CurrentStock, val);
			_controller.Initialize();
			_store = new StockStore(CatSaves.For((BasePlugin)(object)this, 1), () => _controller.CurrentStock, val);
			_store.Loaded += _controller.OnStoreLoaded;
			FrameLoop.Update += _controller.Update;
			BootstrapEvents.MainMenuLoaded += _controller.OnMainMenuLoaded;
			BootstrapEvents.LevelLoadStarted += _controller.Forget;
			BootstrapEvents.GameRestartStarted += _controller.Forget;
			DevMenu.Command("Better Repair", "Cardboard state", (Func<string>)_controller.Describe, "Writes the cardboard stock, the time period and the state of every repair table to the log.");
			DevMenu.Command("Better Repair", "Refill now", (Func<string>)_controller.RefillNow, "Refills the cardboard as a new day would. Host only.");
			val.Info($"Better Repair {"0.1.0"} loaded with {count} translated text(s) in {string.Join(", ", val2.Texts.Languages)}");
		}
	}
	public static class PluginMeta
	{
		public const string Guid = "catlib.betterrepair";

		public const string Name = "Better Repair";

		public const string Version = "0.1.0";
	}
}
namespace BetterRepair.Sync
{
	public sealed class StockSync
	{
		public const string StockMessage = "stock";

		private readonly ModChannel _channel;

		private readonly Func<int> _current;

		private readonly CatLogger _log;

		public int Rejected { get; private set; }

		public event Action<int> StockReceived;

		public StockSync(ModChannel channel, Func<int> current, CatLogger log)
		{
			_channel = channel;
			_current = current;
			_log = log;
			_channel.Received += OnReceived;
			_channel.PeerJoined += delegate(ulong peer)
			{
				_channel.SendTo(peer, "stock", StockText.Format(_current()));
			};
		}

		public int Broadcast(int current)
		{
			return _channel.Broadcast("stock", StockText.Format(current));
		}

		private void OnReceived(ModMessage message)
		{
			if (!(message.Name != "stock") && message.FromHost && !message.IsLocal)
			{
				if (!StockText.TryParse(message.Text, out var current))
				{
					Rejected++;
					_log.Warning("Ignored a cardboard stock message with the value \"" + message.Text + "\"");
				}
				else
				{
					this.StockReceived?.Invoke(current);
				}
			}
		}
	}
}
namespace BetterRepair.Settings
{
	public sealed class RepairSettings
	{
		public Setting<bool> Unlimited { get; }

		public Setting<int> Stock { get; }

		public Setting<RefillMode> Refill { get; }

		public Setting<int> RefillAmount { get; }

		public Setting<bool> Messages { get; }

		public RepairSettings(CatSettings settings, Action changed)
		{
			AcceptableValueRange<int> val = new AcceptableValueRange<int>(1, 99);
			Unlimited = settings.Session<bool>("Cardboard", "Unlimited", false, "The repair table never runs out of cardboard. The host decides for everyone.", (AcceptableValueBase)null);
			Stock = settings.Session<int>("Cardboard", "Stock", 3, "How many sheets of cardboard the repair table holds. The table shows up to three, the rest waits in stock. 3 is the game's amount. The host decides for everyone.", (AcceptableValueBase)(object)val);
			Refill = settings.Session<RefillMode>("Cardboard", "Refill", RefillMode.Full, "What a new day brings: the full stock, like in the game, or a set number of sheets. The host decides for everyone.", (AcceptableValueBase)null);
			RefillAmount = settings.Session<int>("Cardboard", "RefillAmount", 3, "Sheets added every new day when a set number is chosen above. The stock never grows above its size. The host decides for everyone.", (AcceptableValueBase)(object)val);
			Messages = settings.Local<bool>("Messages", "Enabled", true, "Shows how much cardboard is left after a repair and how much a new day brought. Only for you.", (AcceptableValueBase)null);
			Unlimited.Changed += delegate
			{
				changed();
			};
			Stock.Changed += delegate
			{
				changed();
			};
			Refill.Changed += delegate
			{
				changed();
			};
			RefillAmount.Changed += delegate
			{
				changed();
			};
		}
	}
}
namespace BetterRepair.Scene
{
	public sealed class WorkstationView
	{
		private readonly EntityRepairWorkstation _workstation;

		public IntPtr Pointer { get; }

		public string Name { get; }

		public int AppliedIndex { get; private set; } = -1;

		public bool IsAlive => (Object)(object)_workstation != (Object)null;

		public int Capacity => ((Il2CppArrayBase<EntityInteractableAction>)(object)_workstation.ActivationButtons)?.Length ?? 3;

		public int GameIndex => _workstation._currentRepairIndex;

		public WorkstationView(EntityRepairWorkstation workstation)
		{
			_workstation = workstation;
			Pointer = ((Il2CppObjectBase)workstation).Pointer;
			Name = ((Object)((Component)workstation).gameObject).name;
		}

		public void Apply(int index)
		{
			int capacity = Capacity;
			int num = Math.Max(0, Math.Min(capacity, index));
			_workstation._currentRepairIndex = num;
			Il2CppReferenceArray<EntityInteractableAction> activationButtons = _workstation.ActivationButtons;
			if (activationButtons != null)
			{
				for (int i = 0; i < ((Il2CppArrayBase<EntityInteractableAction>)(object)activationButtons).Length; i++)
				{
					EntityInteractableAction val = ((Il2CppArrayBase<EntityInteractableAction>)(object)activationButtons)[i];
					if ((Object)(object)val != (Object)null)
					{
						bool flag = i >= num;
						if (((Component)val).gameObject.activeSelf != flag)
						{
							((Component)val).gameObject.SetActive(flag);
						}
					}
				}
			}
			AppliedIndex = num;
		}

		public string DescribeButtons()
		{
			Il2CppReferenceArray<EntityInteractableAction> activationButtons = _workstation.ActivationButtons;
			if (activationButtons == null)
			{
				return "no buttons";
			}
			List<string> list = new List<string>();
			for (int i = 0; i < ((Il2CppArrayBase<EntityInteractableAction>)(object)activationButtons).Length; i++)
			{
				EntityInteractableAction val = ((Il2CppArrayBase<EntityInteractableAction>)(object)activationButtons)[i];
				list.Add(((Object)(object)val == (Object)null) ? "missing" : (((Object)((Component)val).gameObject).name + " " + (((Component)val).gameObject.activeSelf ? "shown" : "hidden")));
			}
			return string.Join(", ", list);
		}

		public static List<EntityRepairWorkstation> FindAll()
		{
			List<EntityRepairWorkstation> list = new List<EntityRepairWorkstation>();
			foreach (EntityRepairWorkstation item in Object.FindObjectsOfType<EntityRepairWorkstation>(true))
			{
				if ((Object)(object)item != (Object)null)
				{
					list.Add(item);
				}
			}
			return list;
		}
	}
}
namespace BetterRepair.Logic
{
	public sealed class CardboardStock
	{
		public const int SheetsOnTable = 3;

		public const int MinimumStock = 1;

		public const int MaximumStock = 99;

		public int Maximum { get; private set; }

		public int Current { get; private set; }

		public bool Unlimited { get; set; }

		public bool IsEmpty
		{
			get
			{
				if (!Unlimited)
				{
					return Current == 0;
				}
				return false;
			}
		}

		public CardboardStock(int maximum)
		{
			Maximum = Clamp(maximum);
			Current = Maximum;
		}

		public static int Clamp(int maximum)
		{
			return Math.Max(1, Math.Min(99, maximum));
		}

		public bool SetMaximum(int maximum)
		{
			int maximum2 = Clamp(maximum);
			int current = Current;
			Maximum = maximum2;
			Current = Math.Min(Current, Maximum);
			return Current != current;
		}

		public bool SetCurrent(int current)
		{
			int num = Math.Max(0, Math.Min(Maximum, current));
			if (num == Current)
			{
				return false;
			}
			Current = num;
			return true;
		}

		public int Consume(int sheets)
		{
			if (Unlimited || sheets <= 0)
			{
				return 0;
			}
			int num = Math.Min(sheets, Current);
			Current -= num;
			return num;
		}

		public int Refill(RefillMode mode, int amount)
		{
			int current = Current;
			Current = ((mode == RefillMode.Full) ? Maximum : Math.Min(Maximum, Current + Math.Max(0, amount)));
			return Current - current;
		}

		public int VisibleSheets(int capacity = 3)
		{
			int num = Math.Max(0, capacity);
			if (!Unlimited)
			{
				return Math.Min(Current, num);
			}
			return num;
		}

		public int GameIndex(int capacity = 3)
		{
			return Math.Max(0, capacity) - VisibleSheets(capacity);
		}

		public string Describe()
		{
			if (!Unlimited)
			{
				return $"{Current} of {Maximum}";
			}
			return "unlimited";
		}
	}
	public sealed class DayClock
	{
		public const int DayPeriod = 0;

		public const int DawnRecapPeriod = 5;

		private int? _last;

		public int? Last => _last;

		public bool Observe(int period, out bool changed)
		{
			changed = _last.HasValue && _last.Value != period;
			int result = (changed ? (period switch
			{
				0 => (_last.Value != 5) ? 1 : 0, 
				5 => 1, 
				_ => 0, 
			}) : 0);
			_last = period;
			return (byte)result != 0;
		}

		public void Reset()
		{
			_last = null;
		}
	}
	public enum RefillMode
	{
		Full,
		Amount
	}
	public static class StockText
	{
		public static string Format(int current)
		{
			return current.ToString(CultureInfo.InvariantCulture);
		}

		public static bool TryParse(string text, out int current)
		{
			if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out current) && current >= 0)
			{
				return current <= 99;
			}
			return false;
		}
	}
}
namespace BetterRepair.Data
{
	public sealed class StockStore
	{
		public const string StockKey = "cardboard";

		private readonly ModSave _save;

		private readonly Func<int> _current;

		private readonly CatLogger _log;

		public event Action<int?> Loaded;

		public StockStore(ModSave save, Func<int> current, CatLogger log)
		{
			_save = save;
			_current = current;
			_log = log;
			_save.Loaded += OnLoaded;
			_save.Saving += Store;
		}

		public void Store()
		{
			if (_save.IsReady)
			{
				int num = _current();
				if (!_save.Has("cardboard") || _save.Get<int>("cardboard", -1) != num)
				{
					_save.Set<int>("cardboard", num);
				}
			}
		}

		private void OnLoaded()
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			int? obj = null;
			if (_save.Has("cardboard"))
			{
				int num = _save.Get<int>("cardboard", -1);
				if (num >= 0 && num <= 99)
				{
					obj = num;
				}
				else
				{
					_log.Warning($"The stored cardboard stock {num} is out of range and is ignored");
				}
			}
			_log.Info($"Cardboard stock of {_save.SaveName}: {(obj.HasValue ? StockText.Format(obj.Value) : "not stored, the table starts full")} ({_save.State}, {_save.LastRead})");
			this.Loaded?.Invoke(obj);
		}
	}
}