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);
}
}
}