Decompiled source of CivicVolunteer v1.0.1

BepInEx/plugins/OstranautsVolunteer/OstranautsVolunteer.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
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: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 OstranautsVolunteer
{
	internal static class DataPackInstaller
	{
		private const string PackName = "VolunteerShift";

		internal static void Run()
		{
			try
			{
				string text = FindSource();
				if (text == null)
				{
					return;
				}
				string text2 = Path.Combine(Application.dataPath, Path.Combine("Mods", "VolunteerShift"));
				string text3 = ReadVersion(Path.Combine(text, "mod_info.json"));
				string text4 = (Directory.Exists(text2) ? ReadVersion(Path.Combine(text2, "mod_info.json")) : null);
				if (text4 == null || !(text4 == text3))
				{
					bool num = text4 == null && !Directory.Exists(text2);
					if (Directory.Exists(text2))
					{
						Directory.Delete(text2, recursive: true);
					}
					CopyTree(text, text2);
					if (num)
					{
						Plugin.Log.LogWarning((object)("Installed the Civic Volunteer data pack (v" + text3 + ") into Ostranauts_Data/Mods/VolunteerShift. One-time step: enable Civic Volunteer in the in-game MODS menu, then restart the game. The game updates loading_order.json itself when you do."));
						return;
					}
					Plugin.Log.LogInfo((object)("Civic Volunteer data pack refreshed (" + (text4 ?? "unknown") + " -> " + text3 + ")."));
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not self-install the data pack (" + ex.GetType().Name + ": " + ex.Message + "). Manual fix: copy the mod's DataPack/VolunteerShift folder into Ostranauts_Data/Mods/, then enable Civic Volunteer in the in-game MODS menu."));
			}
		}

		private static string? FindSource()
		{
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			for (int i = 0; i < 3; i++)
			{
				if (string.IsNullOrEmpty(directoryName))
				{
					break;
				}
				string text = Path.Combine(Path.Combine(directoryName, "DataPack"), "VolunteerShift");
				if (File.Exists(Path.Combine(text, "mod_info.json")))
				{
					return text;
				}
				directoryName = Path.GetDirectoryName(directoryName);
			}
			return null;
		}

		private static string? ReadVersion(string modInfoPath)
		{
			if (!File.Exists(modInfoPath))
			{
				return null;
			}
			Match match = Regex.Match(File.ReadAllText(modInfoPath), "\"strModVersion\"\\s*:\\s*\"([^\"]+)\"");
			if (!match.Success)
			{
				return null;
			}
			return match.Groups[1].Value;
		}

		private static void CopyTree(string src, string dest)
		{
			Directory.CreateDirectory(dest);
			string[] files = Directory.GetFiles(src);
			foreach (string text in files)
			{
				File.Copy(text, Path.Combine(dest, Path.GetFileName(text)), overwrite: true);
			}
			files = Directory.GetDirectories(src);
			foreach (string text2 in files)
			{
				CopyTree(text2, Path.Combine(dest, Path.GetFileName(text2)));
			}
		}
	}
	[BepInPlugin("com.ostranauts.volunteer", "Civic Volunteer", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "com.ostranauts.volunteer";

		internal static ManualLogSource Log;

		internal static ConfigEntry<float>? TricklePerRepair;

		private Harmony? _harmony;

		private void Awake()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			TricklePerRepair = ((BaseUnityPlugin)this).Config.Bind<float>("Payout", "AltruismPerRepairAction", 0.2f, new ConfigDescription("Altruism worked off per individual repair action on public station infrastructure, before the faction-standing multiplier (0.5-2.0). Deliberately generous relative to the fast-forwarded volunteer hour (-12.5/hr flat) so that playing a shift out beats skipping it: at 0.2 with a neutral station, real-time overtakes fast-forward at roughly 70 repair actions per game-hour. A full repair takes many actions, so the larger completion bonus still lands when an item is actually fixed. Set 0 to pay only completion bonuses.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			DataPackInstaller.Run();
			VolunteerShift.Register();
			_harmony = new Harmony("com.ostranauts.volunteer");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)"Civic Volunteer loaded.");
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class VolunteerScope
	{
		private static readonly Dictionary<Reputation, float> ReputationCoeff = new Dictionary<Reputation, float>
		{
			{
				(Reputation)0,
				2f
			},
			{
				(Reputation)1,
				1.5f
			},
			{
				(Reputation)2,
				1.25f
			},
			{
				(Reputation)4,
				1f
			},
			{
				(Reputation)8,
				0.75f
			},
			{
				(Reputation)16,
				0.5f
			}
		};

		public static bool IsPublicInfrastructure(Ship? ship)
		{
			if (ship == null || ship.bDestroyed)
			{
				return false;
			}
			if (!ship.IsStation(true))
			{
				return false;
			}
			if (ship.IsDerelict())
			{
				return false;
			}
			return true;
		}

		public static bool ShouldVolunteerOn(CondOwner? co, Ship? ship)
		{
			if ((Object)(object)co == (Object)null || ship == null)
			{
				return false;
			}
			if (!VolunteerShift.IsVolunteering(co))
			{
				return false;
			}
			if (co.OwnsShip(ship.strRegID))
			{
				return false;
			}
			return IsPublicInfrastructure(ship);
		}

		public static float AltruismCoeff(CondOwner? co, Ship? ship)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)co == (Object)null || (Object)(object)((ship != null) ? ship.ShipCO : null) == (Object)null)
			{
				return 1f;
			}
			List<string> allFactions = ship.ShipCO.GetAllFactions();
			Reputation reputation = JsonFaction.GetReputation(co.GetFactionScore(allFactions));
			if (!ReputationCoeff.TryGetValue(reputation, out var value))
			{
				return 1f;
			}
			return value;
		}
	}
	internal static class VolunteerShift
	{
		public const int ID = 3;

		public const string Name = "Civic";

		public const string CondLoot = "CONDShiftVolunteer";

		public const string Cond = "IsShiftVolunteer";

		public const string TickInteraction = "Tick1HourShiftVolunteer";

		public const string RepairDoneLoot = "CONDVolunteerRepairDone";

		public const string RepairAltruismLoot = "CONDVolunteerRepairAltruism";

		public const string RepairTickLoot = "CONDVolunteerRepairTick";

		private static readonly Color Colour = new Color(0.24f, 0.72f, 0.42f);

		private static bool _registered;

		private static bool _dataChecked;

		public static bool Registered => _registered;

		public static void Register()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			Dictionary<int, JsonShift> dictionary = JsonCompanyRules.Shifts();
			if (dictionary == null)
			{
				Plugin.Log.LogError((object)"JsonCompanyRules.Shifts() returned null; volunteer shift not registered.");
				return;
			}
			if (!dictionary.ContainsKey(3))
			{
				dictionary[3] = new JsonShift(3, "Civic", "CONDShiftVolunteer", Colour);
			}
			_registered = true;
			Plugin.Log.LogInfo((object)string.Format("Registered shift {0} '{1}' ({2} shifts total).", 3, "Civic", dictionary.Count));
		}

		public static void VerifyDataPack()
		{
			if (_dataChecked)
			{
				return;
			}
			_dataChecked = true;
			List<string> list = new List<string>();
			string[] array = new string[4] { "CONDShiftVolunteer", "CONDVolunteerRepairTick", "CONDVolunteerRepairAltruism", "CONDVolunteerRepairDone" };
			foreach (string text in array)
			{
				if (DataHandler.GetLoot(text) == null)
				{
					list.Add(text);
				}
			}
			if (list.Count == 0)
			{
				Plugin.Log.LogInfo((object)"Data pack found; payouts are active.");
			}
			else
			{
				Plugin.Log.LogError((object)("DATA PACK NOT LOADED: the Civic Volunteer plugin is running, but its data pack isn't registered, so the shift exists and pays nothing. Missing: " + string.Join(", ", list.ToArray()) + ". Fix: copy the mod's VolunteerShift folder into Ostranauts_Data/Mods/, then enable it in the in-game MODS menu (it must also appear in Ostranauts_Data/Mods/loading_order.json -- the game adds it there when you enable it)."));
			}
		}

		public static bool IsVolunteering(CondOwner? co)
		{
			if ((Object)(object)co != (Object)null)
			{
				return co.HasCond("IsShiftVolunteer");
			}
			return false;
		}
	}
}
namespace OstranautsVolunteer.Patches
{
	[HarmonyPatch(typeof(CondOwner), "ProcessAutoTasks")]
	internal static class AutoTaskScopePatch
	{
		private static readonly MethodInfo OwnsShipMethod = AccessTools.Method(typeof(CondOwner), "OwnsShip", new Type[1] { typeof(string) }, (Type[])null);

		private static readonly MethodInfo ReplacementMethod = AccessTools.Method(typeof(AutoTaskScopePatch), "MayGatherFrom", (Type[])null, (Type[])null);

		public static bool MayGatherFrom(CondOwner co, string strRegID)
		{
			if ((Object)(object)co == (Object)null)
			{
				return false;
			}
			if (co.OwnsShip(strRegID))
			{
				return true;
			}
			return VolunteerScope.ShouldVolunteerOn(co, co.ship);
		}

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			if (OwnsShipMethod == null || ReplacementMethod == null)
			{
				Plugin.Log.LogError((object)"Could not resolve OwnsShip; volunteer task scope NOT applied.");
				foreach (CodeInstruction instruction in instructions)
				{
					yield return instruction;
				}
				yield break;
			}
			int swapped = 0;
			foreach (CodeInstruction instruction2 in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction2, OwnsShipMethod))
				{
					swapped++;
					yield return CodeInstructionExtensions.WithBlocks(CodeInstructionExtensions.WithLabels(new CodeInstruction(OpCodes.Call, (object)ReplacementMethod), (IEnumerable<Label>)instruction2.labels), (IEnumerable<ExceptionBlock>)instruction2.blocks);
				}
				else
				{
					yield return instruction2;
				}
			}
			if (swapped == 0)
			{
				Plugin.Log.LogError((object)"ProcessAutoTasks contained no OwnsShip call. Game version likely changed; volunteer task scope NOT applied.");
			}
			else
			{
				Plugin.Log.LogInfo((object)$"Volunteer task scope applied ({swapped} call site(s)).");
			}
		}
	}
	[HarmonyPatch(typeof(GUIFFWDRow), "SetCrew")]
	internal static class FFWDRowPatch
	{
		private const string FreeKey = "Tick1HourShiftFree";

		private static readonly FieldRef<GUIFFWDRow, Dictionary<string, int>?> PayloadsRef = AccessTools.FieldRefAccess<GUIFFWDRow, Dictionary<string, int>>("dictPayloads");

		private static readonly FieldRef<GUIFFWDRow, TMP_Text[]?> HoursRef = AccessTools.FieldRefAccess<GUIFFWDRow, TMP_Text[]>("aHours");

		[HarmonyPostfix]
		private static void Postfix(GUIFFWDRow __instance, CondOwner co, int nHours)
		{
			try
			{
				if (((co != null) ? co.Company : null) == null)
				{
					return;
				}
				Dictionary<string, int> dictionary = PayloadsRef.Invoke(__instance);
				if (dictionary == null)
				{
					return;
				}
				TMP_Text[] array = HoursRef.Invoke(__instance);
				string title = DataHandler.GetInteraction("Tick1HourShiftVolunteer", (JsonInteractionSave)null, false)?.strTitle ?? "Civic";
				int num = 0;
				int num2 = ((array != null) ? Math.Min(nHours, array.Length) : nHours);
				for (int i = 0; i < num2; i++)
				{
					JsonShift shift = co.Company.GetShift(StarSystem.nUTCHour + i, co);
					if (shift != null && shift.nID == 3)
					{
						num++;
						RelabelCell(array, i, title);
					}
				}
				if (num != 0)
				{
					if (dictionary.TryGetValue("Tick1HourShiftFree", out var value))
					{
						dictionary["Tick1HourShiftFree"] = Math.Max(0, value - num);
					}
					dictionary["Tick1HourShiftVolunteer"] = num;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"FFWDRowPatch failed, volunteer hours will pay as free time: {arg}");
			}
		}

		private static void RelabelCell(TMP_Text[]? cells, int i, string title)
		{
			if (cells != null && i < cells.Length && !((Object)(object)cells[i] == (Object)null))
			{
				string text = cells[i].text ?? string.Empty;
				int num = text.IndexOf('\n');
				cells[i].text = ((num < 0) ? title : (title + text.Substring(num)));
			}
		}
	}
	[HarmonyPatch(typeof(Interaction), "ApplyEffects")]
	internal static class RepairPayoutPatch
	{
		private readonly struct Claim
		{
			public readonly CondOwner Actor;

			public readonly CondOwner Item;

			public readonly Ship? Hull;

			public readonly float Coeff;

			public readonly double StartDamage;

			public Claim(CondOwner actor, CondOwner item, Ship? hull, float coeff, double startDamage)
			{
				Actor = actor;
				Item = item;
				Hull = hull;
				Coeff = coeff;
				StartDamage = startDamage;
			}
		}

		private static readonly HashSet<string> PaidDuties = new HashSet<string>(StringComparer.Ordinal) { "Repair", "Patch", "Restore" };

		private static readonly Dictionary<string, Claim> Pending = new Dictionary<string, Claim>(StringComparer.Ordinal);

		private static int _trickleCount;

		private static float _trickleSum;

		private static double _firstTrickleEpoch;

		private static int _shiftActions;

		private static float _shiftAltruism;

		private static int _shiftCompletions;

		private static double _shiftStartEpoch;

		private static double _shiftStartAltruism;

		private static double _shiftAttributed;

		[HarmonyPostfix]
		private static void Postfix(Interaction __instance, bool isCancelIa)
		{
			try
			{
				if (isCancelIa || __instance == null)
				{
					return;
				}
				if (__instance.strName == "Tick1HourShiftVolunteer")
				{
					Plugin.Log.LogInfo((object)"Volunteer FFWD: applied one fast-forwarded volunteer hour (-12.5 altruism from CONDTick1HourVolunteerMoods).");
				}
				else
				{
					if (__instance.strDuty == null || !PaidDuties.Contains(__instance.strDuty))
					{
						return;
					}
					SweepCompleted();
					CondOwner objUs = __instance.objUs;
					if (objUs == null || !VolunteerShift.IsVolunteering(objUs))
					{
						return;
					}
					CondOwner objThem = __instance.objThem;
					if (objThem == null)
					{
						return;
					}
					Ship val = objThem.ship ?? objUs.ship;
					if (!VolunteerScope.ShouldVolunteerOn(objUs, val))
					{
						return;
					}
					float num = VolunteerScope.AltruismCoeff(objUs, val);
					float num2 = Plugin.TricklePerRepair?.Value ?? 0f;
					if (num2 > 0f)
					{
						double num3 = CurrentAltruism();
						Loot loot = DataHandler.GetLoot("CONDVolunteerRepairTick");
						if (loot != null)
						{
							loot.ApplyCondLoot(objUs, num2 * num, (string)null, 0f);
						}
						_shiftAttributed += num3 - CurrentAltruism();
						CountTrickle(num2 * num);
					}
					if (!Pending.ContainsKey(objThem.strID))
					{
						Pending[objThem.strID] = new Claim(objUs, objThem, val, num, objThem.GetCondAmount("StatDamage"));
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"RepairPayoutPatch failed: {arg}");
			}
		}

		internal static void SweepCompleted()
		{
			if (Pending.Count == 0)
			{
				return;
			}
			List<string> list = null;
			foreach (KeyValuePair<string, Claim> item2 in Pending)
			{
				CondOwner item = item2.Value.Item;
				if (item == null || item.bDestroyed)
				{
					(list ?? (list = new List<string>())).Add(item2.Key);
				}
				else if (!(item.GetCondAmount("StatDamage") > 0.0))
				{
					if (item2.Value.StartDamage <= 0.0)
					{
						(list ?? (list = new List<string>())).Add(item2.Key);
						continue;
					}
					(list ?? (list = new List<string>())).Add(item2.Key);
					Pay(item2.Value, item);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (string item3 in list)
			{
				Pending.Remove(item3);
			}
		}

		private static void Pay(Claim claim, CondOwner item)
		{
			if (claim.Actor != null && VolunteerShift.IsVolunteering(claim.Actor))
			{
				Loot loot = DataHandler.GetLoot("CONDVolunteerRepairDone");
				Loot loot2 = DataHandler.GetLoot("CONDVolunteerRepairAltruism");
				if (loot == null || loot2 == null)
				{
					Plugin.Log.LogWarning((object)"Volunteer payout loot missing. Is the VolunteerShift data folder installed and listed in loading_order.json?");
					return;
				}
				double num = CurrentAltruism();
				loot.ApplyCondLoot(claim.Actor, 1f, (string)null, 0f);
				loot2.ApplyCondLoot(claim.Actor, claim.Coeff, (string)null, 0f);
				_shiftAttributed += num - CurrentAltruism();
				_shiftCompletions++;
				Plugin.Log.LogInfo((object)("Volunteer repair: " + claim.Actor.strNameFriendly + " finished " + (item.strNameFriendly ?? "?") + " / " + (claim.Hull?.strRegID ?? "?") + " " + $"(altruism coeff {claim.Coeff:0.##})"));
			}
		}

		internal static void OnVolunteerShiftStart()
		{
			_shiftActions = 0;
			_shiftAltruism = 0f;
			_shiftCompletions = 0;
			_shiftAttributed = 0.0;
			_shiftStartEpoch = StarSystem.fEpoch;
			_shiftStartAltruism = ((CrewSim.coPlayer == null) ? double.NaN : CurrentAltruism());
		}

		private static double CurrentAltruism()
		{
			CondOwner coPlayer = CrewSim.coPlayer;
			if (coPlayer != null)
			{
				return coPlayer.GetCondAmount("StatAltruism");
			}
			return 0.0;
		}

		internal static void OnVolunteerShiftEnd(string who)
		{
			SweepCompleted();
			Pending.Clear();
			if (_shiftActions == 0)
			{
				string text = (double.IsNaN(_shiftStartAltruism) ? "no baseline captured" : ($"StatAltruism {_shiftStartAltruism:0.#} -> {CurrentAltruism():0.#} " + $"(moved {_shiftStartAltruism - CurrentAltruism():0.#})"));
				Plugin.Log.LogInfo((object)("Volunteer shift ended for " + who + ": no repair actions; " + text + ". Fast-forwarded hours and drift only."));
			}
			else
			{
				double num = (StarSystem.fEpoch - _shiftStartEpoch) / 3600.0;
				double num2 = (double.IsNaN(_shiftStartAltruism) ? 0.0 : (_shiftStartAltruism - CurrentAltruism()));
				Plugin.Log.LogInfo((object)($"Volunteer shift ended for {who}: {_shiftActions} actions, " + $"{_shiftCompletions} completed over {num:0.##} game-hr " + $"({(double)_shiftActions / Math.Max(num, 0.01):0.#} actions/hr). " + $"StatAltruism {_shiftStartAltruism:0.#} -> {CurrentAltruism():0.#} " + $"(moved {num2:0.#} total, of which {_shiftAttributed:0.##} from " + "volunteer payouts; rest is drift + vanilla free-will)."));
			}
		}

		private static void CountTrickle(float amount)
		{
			if (_trickleCount == 0)
			{
				_firstTrickleEpoch = StarSystem.fEpoch;
			}
			if (double.IsNaN(_shiftStartAltruism))
			{
				_shiftStartAltruism = CurrentAltruism() + (double)amount;
			}
			_trickleCount++;
			_trickleSum += amount;
			_shiftActions++;
			_shiftAltruism += amount;
			if (_trickleCount % 50 == 0)
			{
				double num = (StarSystem.fEpoch - _firstTrickleEpoch) / 3600.0;
				string arg = ((num > 0.01) ? $"{(double)_trickleSum / num:0.##}/game-hr vs +8.33/hr drift" : "rate pending");
				Plugin.Log.LogInfo((object)($"Volunteer trickle: {_trickleCount} repair actions, " + $"~{_trickleSum:0.#} altruism worked off ({arg})."));
			}
		}
	}
	[HarmonyPatch(typeof(GUIRoster), "SetCrew")]
	internal static class RosterLegendPatch
	{
		private const string LabelMarker = "txtVolunteerLegend";

		private const string SwatchMarker = "bmpVolunteerLegend";

		[HarmonyPostfix]
		private static void Postfix(GUIRoster __instance)
		{
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//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_01e9: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!VolunteerShift.Registered)
				{
					return;
				}
				TMP_Text val = FindLabel(__instance, ShiftName(2));
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)("Roster legend: no Work label found. " + DescribeLabels(__instance)));
					return;
				}
				Transform parent = val.transform.parent;
				if ((Object)(object)parent == (Object)null || (Object)(object)parent.Find("txtVolunteerLegend") != (Object)null)
				{
					return;
				}
				int siblingIndex = val.transform.GetSiblingIndex();
				Transform transform = val.transform;
				RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null);
				RectTransform val3 = SiblingSwatch(parent, siblingIndex);
				if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null)
				{
					Plugin.Log.LogWarning((object)("Roster legend: Work label at index " + siblingIndex + " has no adjacent swatch; skipping."));
					return;
				}
				float num = EntrySpacing(parent, siblingIndex);
				if (num <= 0f)
				{
					Plugin.Log.LogWarning((object)"Roster legend: could not derive entry spacing; skipping.");
					return;
				}
				string text = "Civic" + TrailingPunctuation(val.text);
				float num2 = 0f;
				RectTransform val4 = (RectTransform)(object)((parent is RectTransform) ? parent : null);
				if ((Object)(object)val4 != (Object)null)
				{
					Rect rect = val4.rect;
					if (((Rect)(ref rect)).width > 0f)
					{
						float x = val.GetPreferredValues(val.text).x;
						float x2 = val.GetPreferredValues(text).x;
						float num3 = Mathf.Max(0f, x2 - x);
						rect = val4.rect;
						num2 = num3 / ((Rect)(ref rect)).width;
					}
				}
				RectTransform obj = Clone(val2, parent, "txtVolunteerLegend", num);
				RectTransform val5 = Clone(val3, parent, "bmpVolunteerLegend", num + num2);
				TMP_Text component = ((Component)obj).GetComponent<TMP_Text>();
				if ((Object)(object)component != (Object)null)
				{
					component.text = text;
					component.enableAutoSizing = false;
					component.textWrappingMode = (TextWrappingModes)0;
				}
				Image component2 = ((Component)val5).GetComponent<Image>();
				if ((Object)(object)component2 != (Object)null)
				{
					((Graphic)component2).color = JsonCompanyRules.Shifts()[3].clr;
				}
				Plugin.Log.LogInfo((object)("Roster legend: added Civic entry (spacing " + num.ToString("0.####") + ", swatch nudge " + num2.ToString("0.####") + ")."));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("RosterLegendPatch failed (legend will lack Volunteer): " + ex));
			}
		}

		private static RectTransform Clone(RectTransform src, Transform parent, string name, float step)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = Object.Instantiate<RectTransform>(src, parent);
			((Object)obj).name = name;
			Vector2 anchorMin = src.anchorMin;
			Vector2 anchorMax = src.anchorMax;
			obj.anchorMin = new Vector2(anchorMin.x + step, anchorMin.y);
			obj.anchorMax = new Vector2(anchorMax.x + step, anchorMax.y);
			obj.anchoredPosition = src.anchoredPosition;
			obj.sizeDelta = src.sizeDelta;
			return obj;
		}

		private static RectTransform? SiblingSwatch(Transform parent, int labelIndex)
		{
			if (labelIndex + 1 >= parent.childCount)
			{
				return null;
			}
			Transform child = parent.GetChild(labelIndex + 1);
			if ((Object)(object)((Component)child).GetComponent<Image>() == (Object)null)
			{
				return null;
			}
			if ((Object)(object)((Component)child).GetComponent<TMP_Text>() != (Object)null)
			{
				return null;
			}
			return (RectTransform?)(object)((child is RectTransform) ? child : null);
		}

		private static float EntrySpacing(Transform parent, int labelIndex)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if (labelIndex - 2 < 0)
			{
				return 0f;
			}
			Transform child = parent.GetChild(labelIndex - 2);
			RectTransform val = (RectTransform)(object)((child is RectTransform) ? child : null);
			Transform child2 = parent.GetChild(labelIndex);
			RectTransform val2 = (RectTransform)(object)((child2 is RectTransform) ? child2 : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
			{
				return 0f;
			}
			if ((Object)(object)((Component)val).GetComponent<TMP_Text>() == (Object)null)
			{
				return 0f;
			}
			return val2.anchorMin.x - val.anchorMin.x;
		}

		private static string ShiftName(int id)
		{
			Dictionary<int, JsonShift> dictionary = JsonCompanyRules.Shifts();
			if (dictionary == null || !dictionary.TryGetValue(id, out var value))
			{
				return "Work";
			}
			return value.strName;
		}

		private static TMP_Text? FindLabel(GUIRoster roster, string wanted)
		{
			TMP_Text[] componentsInChildren = ((Component)roster).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && val.text != null && string.Equals(Normalise(val.text), wanted, StringComparison.OrdinalIgnoreCase) && !((Object)(object)((Component)val).GetComponentInParent<GUIRosterHour>() != (Object)null))
				{
					return val;
				}
			}
			return null;
		}

		private static string Normalise(string s)
		{
			return s.Trim().TrimEnd(':', '-', ' ').Trim();
		}

		private static string TrailingPunctuation(string? template)
		{
			string text = ((template == null) ? string.Empty : template.TrimEnd(Array.Empty<char>()));
			if (text.Length == 0)
			{
				return string.Empty;
			}
			char c = text[text.Length - 1];
			if (c != ':' && c != '-')
			{
				return string.Empty;
			}
			return c.ToString();
		}

		private static string DescribeLabels(GUIRoster roster)
		{
			List<string> list = new List<string>();
			TMP_Text[] componentsInChildren = ((Component)roster).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && val.text != null && !((Object)(object)((Component)val).GetComponentInParent<GUIRosterHour>() != (Object)null))
				{
					string text = val.text.Trim();
					if (text.Length > 0 && text.Length < 24 && !list.Contains(text))
					{
						list.Add(text);
					}
				}
			}
			return "Labels present: " + string.Join(" | ", list.ToArray());
		}
	}
	[HarmonyPatch(typeof(CondOwner), "ShiftChange")]
	internal static class ShiftChangeLogPatch
	{
		private static readonly Dictionary<string, int> LastShift = new Dictionary<string, int>(StringComparer.Ordinal);

		[HarmonyPostfix]
		private static void Postfix(CondOwner __instance, JsonShift js)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null || (!__instance.HasCond("IsPlayer") && !__instance.HasCond("IsPlayerCrew")))
				{
					return;
				}
				int num = js?.nID ?? (-1);
				string key = __instance.strID ?? "?";
				if (!LastShift.TryGetValue(key, out var value) || value != num)
				{
					bool flag = value == 3;
					LastShift[key] = num;
					Plugin.Log.LogInfo((object)(string.Format("Shift -> {0} (id {1}) for {2}", js?.strName ?? "None", num, __instance.strNameFriendly) + ((num == 3) ? " [volunteer conditions applied]" : "")));
					if (num == 3)
					{
						VolunteerShift.VerifyDataPack();
						RepairPayoutPatch.OnVolunteerShiftStart();
					}
					else if (flag)
					{
						RepairPayoutPatch.OnVolunteerShiftEnd(__instance.strNameFriendly);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ShiftChangeLogPatch failed: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(CondOwner), "GetMove2")]
	internal static class VolunteerAIPatch
	{
		private static readonly Func<CondOwner, bool>? ProcessAutoTasks = AccessTools.MethodDelegate<Func<CondOwner, bool>>(AccessTools.Method(typeof(CondOwner), "ProcessAutoTasks", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Dictionary<string, string> LastState = new Dictionary<string, string>(StringComparer.Ordinal);

		[HarmonyPrefix]
		private static bool Prefix(CondOwner __instance)
		{
			try
			{
				if (ProcessAutoTasks == null)
				{
					return true;
				}
				if (!VolunteerShift.IsVolunteering(__instance))
				{
					return true;
				}
				bool flag = false;
				string state;
				if (__instance.HasCond("IsAIManual"))
				{
					state = "idle (AI set to manual)";
				}
				else if (!HasRestorePermission(__instance))
				{
					state = "idle (Restore permission off in roster)";
				}
				else
				{
					CrewSim.objInstance.workManager.IdleAdd(__instance);
					flag = ProcessAutoTasks(__instance);
					state = (flag ? "repairing" : "no repairable targets in reach");
				}
				LogTransition(__instance, state);
				return !flag;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"VolunteerAIPatch failed, falling back to free-will: {arg}");
				return true;
			}
		}

		private static bool HasRestorePermission(CondOwner co)
		{
			if (co.Company == null)
			{
				return true;
			}
			if (co.Company.mapRoster.TryGetValue(co.strID, out var value) && value != null)
			{
				return value.bRestorePermission;
			}
			return true;
		}

		private static void LogTransition(CondOwner co, string state)
		{
			string key = co.strID ?? "?";
			if (!LastState.TryGetValue(key, out string value) || !(value == state))
			{
				LastState[key] = state;
				Plugin.Log.LogInfo((object)("Volunteer AI [" + co.strNameFriendly + "] on " + (co.ship?.strRegID ?? "?") + ": " + state));
			}
		}
	}
}