Decompiled source of Repo Contracts v0.5.3

BepInEx\plugins\RepoContracts\RepoContracts.dll

Decompiled 4 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using RepoContracts.Adapters;
using RepoContracts.Contracts;
using RepoContracts.Networking;
using RepoContracts.Patches;
using RepoContracts.Rewards;
using RepoContracts.UI;
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("RepoContracts")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Optional contract objectives and tiered rewards for R.E.P.O.")]
[assembly: AssemblyFileVersion("0.5.3.0")]
[assembly: AssemblyInformationalVersion("0.5.3")]
[assembly: AssemblyProduct("RepoContracts")]
[assembly: AssemblyTitle("RepoContracts")]
[assembly: AssemblyVersion("0.5.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 RepoContracts
{
	[BepInPlugin("com.amgru.repocontracts", "RepoContracts", "0.5.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.amgru.repocontracts";

		public const string PluginName = "RepoContracts";

		public const string PluginVersion = "0.5.3";

		internal static ConfigEntry<bool> EnableRepoContracts;

		internal static ConfigEntry<int> OfferedContractsPerLevel;

		internal static ConfigEntry<int> SelectedContractsPerLevel;

		internal static ConfigEntry<bool> OneActiveContractAtATime;

		internal static ConfigEntry<bool> AdvanceQueueOnFailure;

		internal static ConfigEntry<bool> AdvanceQueueOnCompletion;

		internal static ConfigEntry<bool> EnableMilestoneContracts;

		internal static ConfigEntry<int> MilestoneContractEveryXLevels;

		internal static ConfigEntry<bool> AllowNormalContractsDuringMilestone;

		internal static ConfigEntry<float> RewardMultiplier;

		internal static ConfigEntry<FailureStrictness> FailureStrictness;

		internal static ConfigEntry<DisciplineMode> DisciplineMode;

		internal static ConfigEntry<bool> EnableDisciplineDebt;

		internal static ConfigEntry<bool> EnableDebugLogging;

		internal static ConfigEntry<bool> AutoLockFirstOffersForDebug;

		internal static ConfigEntry<KeyCode> ContractHudKey;

		internal static ConfigEntry<KeyCode> ContractBoardLockKey;

		internal static ConfigEntry<float> BoardSideOffset;

		internal static ConfigEntry<float> BoardForwardOffset;

		internal static ConfigEntry<float> BoardUpOffset;

		internal static ConfigEntry<float> BoardYawOffset;

		internal static ConfigEntry<float> BoardScale;

		internal static ConfigEntry<float> BoardNudgeStep;

		internal static ConfigEntry<float> BoardRotateStep;

		internal static ConfigEntry<bool> EnableExperimentalContracts;

		internal static ConfigEntry<bool> EnableAssetHeavyContracts;

		internal static ConfigEntry<bool> EnableCompatibilityMode;

		private Harmony? harmony;

		internal static Plugin Instance { get; private set; }

		internal static ContractManager Manager { get; private set; }

		internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger;

		private void Awake()
		{
			//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cb: Expected O, but got Unknown
			Instance = this;
			EnableRepoContracts = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableRepoContracts", true, "Enables or disables Repo Contracts.");
			OfferedContractsPerLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Contracts", "OfferedContractsPerLevel", 5, "Contracts shown on the truck board.");
			SelectedContractsPerLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Contracts", "SelectedContractsPerLevel", 3, "Contracts selected into the priority queue.");
			OneActiveContractAtATime = ((BaseUnityPlugin)this).Config.Bind<bool>("Contracts", "OneActiveContractAtATime", true, "Only one selected contract is active at a time.");
			AdvanceQueueOnFailure = ((BaseUnityPlugin)this).Config.Bind<bool>("Contracts", "AdvanceQueueOnFailure", true, "Automatically activate the next contract when the active one fails.");
			AdvanceQueueOnCompletion = ((BaseUnityPlugin)this).Config.Bind<bool>("Contracts", "AdvanceQueueOnCompletion", true, "Automatically activate the next contract when the active one completes.");
			EnableMilestoneContracts = ((BaseUnityPlugin)this).Config.Bind<bool>("Milestones", "EnableMilestoneContracts", true, "Enables special all-encompassing milestone contracts.");
			MilestoneContractEveryXLevels = ((BaseUnityPlugin)this).Config.Bind<int>("Milestones", "MilestoneContractEveryXLevels", 3, "Offer a milestone contract every X completed levels.");
			AllowNormalContractsDuringMilestone = ((BaseUnityPlugin)this).Config.Bind<bool>("Milestones", "AllowNormalContractsDuringMilestone", false, "Allows the normal queue alongside milestone contracts.");
			RewardMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Rewards", "RewardMultiplier", 1f, "Scales additive contract rewards.");
			FailureStrictness = ((BaseUnityPlugin)this).Config.Bind<FailureStrictness>("Contracts", "FailureStrictness", RepoContracts.Contracts.FailureStrictness.Normal, "How punishing failure variants should be.");
			DisciplineMode = ((BaseUnityPlugin)this).Config.Bind<DisciplineMode>("Discipline", "DisciplineMode", RepoContracts.Contracts.DisciplineMode.Standard, "Future penalty severity for failed contracts when discipline debt is enabled.");
			EnableDisciplineDebt = ((BaseUnityPlugin)this).Config.Bind<bool>("Discipline", "EnableDisciplineDebt", false, "Future/off-by-default setting. When enabled, failed contracts add debt that withholds future contract rewards.");
			EnableDebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EnableDebugLogging", true, "Logs adapter activity and TODO hook gaps.");
			AutoLockFirstOffersForDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "AutoLockFirstOffersForDebug", false, "Debug helper that auto-selects and locks the first offered contracts after generation.");
			ContractHudKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Controls", "ContractHudKey", (KeyCode)99, "Keyboard key that toggles the active contract HUD overlay.");
			ContractBoardLockKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Controls", "ContractBoardLockKey", (KeyCode)108, "Host keyboard key that locks/tallies board votes while pointing at the board.");
			BoardSideOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Board", "SideOffset", -6.149996f, "Meters to the truck side from the anchor for the contract TV.");
			BoardForwardOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Board", "ForwardOffset", -0.04999985f, "Meters forward/back from the anchor for the contract TV.");
			BoardUpOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Board", "UpOffset", 0.3499997f, "Meters upward from the anchor for the contract TV.");
			BoardYawOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Board", "YawOffset", 270f, "Yaw offset from the truck anchor for the contract TV.");
			BoardScale = ((BaseUnityPlugin)this).Config.Bind<float>("Board", "Scale", 2.099999f, "Overall contract TV scale.");
			BoardNudgeStep = ((BaseUnityPlugin)this).Config.Bind<float>("Board", "NudgeStep", 0.1f, "Meters moved per calibration key press.");
			BoardRotateStep = ((BaseUnityPlugin)this).Config.Bind<float>("Board", "RotateStep", 5f, "Degrees rotated per calibration key press.");
			EnableExperimentalContracts = ((BaseUnityPlugin)this).Config.Bind<bool>("Contracts", "EnableExperimentalContracts", false, "Allows contracts that rely on unverified or risky hooks.");
			EnableAssetHeavyContracts = ((BaseUnityPlugin)this).Config.Bind<bool>("Contracts", "EnableAssetHeavyContracts", false, "Allows contracts needing new world models, VFX, SFX, or heavier UI assets.");
			EnableCompatibilityMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "EnableCompatibilityMode", true, "Skips contracts needing risky object replacement, spawn replacement, control manipulation, or enemy roster manipulation.");
			ContractBoardUi boardUi = ((Component)this).gameObject.AddComponent<ContractBoardUi>();
			ContractHud hud = ((Component)this).gameObject.AddComponent<ContractHud>();
			Manager = new ContractManager(new ContractCatalog(), new RewardAdapter(), new ContractSyncAdapter(), boardUi, hud);
			harmony = new Harmony("com.amgru.repocontracts");
			harmony.PatchAll();
			OptionalContractPatches.Apply(harmony);
			HookAudit.LogKnownHookStatus();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"RepoContracts v0.5.3 loaded.");
		}

		private void Update()
		{
			Manager?.PollSyncedState();
			Manager?.UpdateActiveWorldContracts();
		}

		private void OnDestroy()
		{
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}

		internal static void SaveSettings()
		{
			((BaseUnityPlugin)Instance).Config.Save();
		}
	}
}
namespace RepoContracts.Patches
{
	[HarmonyPatch(typeof(LevelGenerator), "GenerateDone")]
	internal static class LevelGeneratorGenerateDonePatch
	{
		private static void Postfix()
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			Plugin.Manager.GenerateOffersAtTruck();
			if (Plugin.Manager.Queue.OfferedContracts.Count != 0)
			{
				Plugin.Manager.EventBus.OnLevelStarted();
				if (Plugin.AutoLockFirstOffersForDebug.Value)
				{
					Plugin.Manager.HostLockQueue();
				}
			}
		}
	}
	[HarmonyPatch(typeof(HurtCollider), "EnemyHurt")]
	internal static class ContractEnemyHurtPatch
	{
		private static void Postfix(HurtCollider __instance, Enemy _enemy, bool __result)
		{
			if (__result && SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)_enemy != (Object)null)
			{
				Plugin.Manager.EventBus.OnEnemyDamaged(((Component)_enemy).gameObject, 1, "hurt-collider");
			}
		}
	}
	[HarmonyPatch(typeof(HurtCollider), "PlayerHurt")]
	internal static class ContractPlayerHurtPatch
	{
		private static void Postfix(PlayerAvatar _player)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)_player != (Object)null)
			{
				Plugin.Manager.EventBus.OnPlayerDamaged(((Component)_player).gameObject);
			}
		}
	}
	[HarmonyPatch(typeof(EnemyHealth), "DeathImpulseRPC")]
	internal static class ContractEnemyKilledPatch
	{
		private static void Postfix(EnemyHealth __instance)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)__instance == (Object)null))
			{
				Enemy component = ((Component)__instance).GetComponent<Enemy>();
				GameObject enemy = (((Object)(object)component != (Object)null) ? ((Component)component).gameObject : ((Component)__instance).gameObject);
				Plugin.Manager.HandleEnemyKilled(enemy, "verified-enemy-health");
			}
		}
	}
	[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")]
	internal static class ContractPlayerDeathPatch
	{
		private static void Postfix(PlayerAvatar __instance)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)__instance == (Object)null))
			{
				Plugin.Manager.HandlePlayerDeath(((Component)__instance).gameObject);
			}
		}
	}
	[HarmonyPatch(typeof(ItemGun), "Shoot")]
	internal static class ContractProjectileWeaponUsedPatch
	{
		private static void Postfix(ItemGun __instance, bool __result)
		{
			if (__result && SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)__instance == (Object)null))
			{
				Plugin.Manager.EventBus.OnProjectileWeaponUsed(((Component)__instance).gameObject);
			}
		}
	}
	[HarmonyPatch(typeof(RoundDirector), "HaulCheck")]
	internal static class ContractHaulCheckPatch
	{
		private static void Postfix(RoundDirector __instance)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)__instance == (Object)null))
			{
				List<GameObject> candidates = __instance.dollarHaulList.Where((GameObject item) => (Object)(object)item != (Object)null).ToList();
				Plugin.Manager.CaptureExtractionCandidates(candidates);
			}
		}
	}
	[HarmonyPatch(typeof(RoundDirector), "ExtractionCompleted")]
	internal static class ContractExtractionCompletedPatch
	{
		private static void Postfix(RoundDirector __instance)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				Plugin.Manager.HandleExtractionCompleted(null);
			}
		}
	}
	[HarmonyPatch(typeof(ExtractionPoint), "DestroyTheFirstPhysObjectsInHaulList")]
	internal static class ContractExtractionPointItemExtractPatch
	{
		private static bool Prefix(ExtractionPoint __instance)
		{
			if (!SemiFunc.IsMasterClientOrSingleplayer() || (Object)(object)RoundDirector.instance == (Object)null || RoundDirector.instance.dollarHaulList.Count == 0)
			{
				return true;
			}
			if ((Object)(object)__instance != (Object)null && Plugin.Manager.ShouldBlockPressurePlateExtraction(((Component)__instance).gameObject))
			{
				return false;
			}
			GameObject val = RoundDirector.instance.dollarHaulList[0];
			if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponent<ValuableObject>() == (Object)null)
			{
				return true;
			}
			Plugin.Manager.HandleValuableExtracting(val, ((Object)(object)__instance != (Object)null) ? ((Component)__instance).gameObject : null);
			return true;
		}
	}
	[HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "BreakRPC")]
	internal static class ContractValuableBreakPatch
	{
		private static bool Prefix(PhysGrabObjectImpactDetector __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				return !Plugin.Manager.IsProtectedPressurePlateWeight(((Component)__instance).gameObject);
			}
			return true;
		}

		private static void Postfix(PhysGrabObjectImpactDetector __instance, float valueLost, bool _loseValue)
		{
			if (_loseValue && SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)__instance == (Object)null))
			{
				ValuableObject component = ((Component)__instance).GetComponent<ValuableObject>();
				if (!((Object)(object)component == (Object)null))
				{
					Plugin.Manager.HandleValuableDamaged(((Component)component).gameObject, Mathf.RoundToInt(valueLost));
				}
			}
		}
	}
	[HarmonyPatch(typeof(PhysGrabObjectImpactDetector), "DestroyObjectRPC")]
	internal static class ContractValuableDestroyedPatch
	{
		private static bool Prefix(PhysGrabObjectImpactDetector __instance)
		{
			if (!((Object)(object)__instance == (Object)null))
			{
				return !Plugin.Manager.IsProtectedPressurePlateWeight(((Component)__instance).gameObject);
			}
			return true;
		}

		private static void Postfix(PhysGrabObjectImpactDetector __instance)
		{
			if (SemiFunc.IsMasterClientOrSingleplayer() && !((Object)(object)__instance == (Object)null))
			{
				ValuableObject component = ((Component)__instance).GetComponent<ValuableObject>();
				if (!((Object)(object)component == (Object)null))
				{
					Plugin.Manager.HandleValuableDestroyed(((Component)component).gameObject);
				}
			}
		}
	}
	[HarmonyPatch(typeof(PhysGrabObject), "GrabStarted")]
	internal static class ContractItemGrabStartedPatch
	{
		private static void Postfix(PhysGrabObject __instance, PhysGrabber player)
		{
			if (!((Object)(object)__instance == (Object)null) && SemiFunc.IsMasterClientOrSingleplayer())
			{
				ValuableObject component = ((Component)__instance).GetComponent<ValuableObject>();
				if (!((Object)(object)component == (Object)null))
				{
					Plugin.Manager.HandleItemGrabbed(((Component)component).gameObject, ((Object)(object)player != (Object)null) ? ((Component)player).gameObject : null);
				}
			}
		}
	}
	[HarmonyPatch(typeof(PhysGrabObject), "GrabPlayerAddRPC")]
	internal static class ContractItemGrabPlayerAddPatch
	{
		private static void Postfix(PhysGrabObject __instance, int photonViewID)
		{
			if (!((Object)(object)__instance == (Object)null) && SemiFunc.IsMasterClientOrSingleplayer())
			{
				ValuableObject component = ((Component)__instance).GetComponent<ValuableObject>();
				if (!((Object)(object)component == (Object)null))
				{
					PhotonView obj = PhotonView.Find(photonViewID);
					GameObject player = ((obj != null) ? ((Component)obj).gameObject : null);
					Plugin.Manager.HandleItemGrabbed(((Component)component).gameObject, player);
				}
			}
		}
	}
	internal static class OptionalContractPatches
	{
		internal static void Apply(Harmony harmony)
		{
			PatchIfFound(harmony, typeof(TruckHealer), "Heal", "player healing", null, AccessTools.Method(typeof(OptionalContractPatches), "TruckHealerHealPostfix", (Type[])null, (Type[])null));
			PatchIfFound(harmony, typeof(RunManager), "ChangeLevel", "level transition fallback", AccessTools.Method(typeof(OptionalContractPatches), "RunManagerChangeLevelPrefix", (Type[])null, (Type[])null));
		}

		private static void PatchIfFound(Harmony harmony, Type type, string methodName, string label, MethodInfo? prefix = null, MethodInfo? postfix = null)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo methodInfo = HookAudit.TryMethod(type, methodName, label);
			if (!(methodInfo == null))
			{
				object obj = harmony;
				obj = methodInfo;
				obj = (object)((!(prefix != null)) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix));
				obj = (object)((!(postfix != null)) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix));
				((Harmony)obj).Patch((MethodBase)obj, (HarmonyMethod)obj, (HarmonyMethod)obj, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void TruckHealerHealPostfix()
		{
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				Plugin.Manager.EventBus.OnPlayerHealed(null, 10);
			}
		}

		private static void RunManagerChangeLevelPrefix()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (SemiFunc.IsMasterClientOrSingleplayer())
			{
				Plugin.Manager.CompleteLevel(Vector3.zero);
			}
		}
	}
}
namespace RepoContracts.UI
{
	internal static class ContractAssets
	{
		private static readonly Dictionary<string, Texture2D?> Textures = new Dictionary<string, Texture2D>();

		private static readonly Dictionary<string, Material?> Materials = new Dictionary<string, Material>();

		internal static Texture2D? Texture(string relativePath)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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)
			//IL_0068: Expected O, but got Unknown
			if (Textures.TryGetValue(relativePath, out Texture2D value))
			{
				return value;
			}
			string path = FindAssetPath(relativePath);
			if (!File.Exists(path))
			{
				Plugin.Log.LogWarning((object)("[RepoContracts] Missing packaged asset: " + relativePath));
				Textures[relativePath] = null;
				return null;
			}
			try
			{
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false)
				{
					name = Path.GetFileNameWithoutExtension(relativePath),
					wrapMode = (TextureWrapMode)1,
					filterMode = (FilterMode)1
				};
				if (!LoadImage(val, File.ReadAllBytes(path)))
				{
					Plugin.Log.LogWarning((object)("[RepoContracts] Unity could not decode packaged asset: " + relativePath));
					Textures[relativePath] = null;
					return null;
				}
				Textures[relativePath] = val;
				return val;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[RepoContracts] Failed to load packaged asset " + relativePath + ": " + ex.Message));
				Textures[relativePath] = null;
				return null;
			}
		}

		internal static Material? Material(string relativePath, Color tint, bool transparent = true)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			string key = $"{relativePath}|{tint}|{transparent}";
			if (Materials.TryGetValue(key, out Material value))
			{
				return value;
			}
			Texture2D val = Texture(relativePath);
			if ((Object)(object)val == (Object)null)
			{
				Materials[key] = null;
				return null;
			}
			Material val2 = new Material(Shader.Find("Unlit/Transparent") ?? Shader.Find("GUI/Text Shader") ?? Shader.Find("Standard"))
			{
				name = "RepoContracts " + Path.GetFileNameWithoutExtension(relativePath),
				mainTexture = (Texture)(object)val,
				color = tint
			};
			if (transparent)
			{
				ConfigureTransparentMaterial(val2);
			}
			Materials[key] = val2;
			return val2;
		}

		internal static void ApplyTexture(GameObject target, string relativePath, Color tint, bool transparent = true)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			Renderer component = target.GetComponent<Renderer>();
			Material val = Material(relativePath, tint, transparent);
			if ((Object)(object)component != (Object)null && (Object)(object)val != (Object)null)
			{
				component.material = val;
			}
		}

		private static string FindAssetPath(string relativePath)
		{
			string text = relativePath.Replace('/', Path.DirectorySeparatorChar);
			string text2 = Path.Combine(Paths.PluginPath, "RepoContracts", "assets", text);
			if (File.Exists(text2))
			{
				return text2;
			}
			string text3 = Path.Combine(Path.GetDirectoryName(typeof(Plugin).Assembly.Location) ?? Paths.PluginPath, "assets", text);
			if (File.Exists(text3))
			{
				return text3;
			}
			return Path.Combine(Paths.GameRootPath, "assets", "repo_contracts", text);
		}

		private static bool LoadImage(Texture2D texture, byte[] bytes)
		{
			MethodInfo methodInfo = (Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule") ?? Type.GetType("UnityEngine.ImageConversion, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.ImageConversion, UnityEngine"))?.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3]
			{
				typeof(Texture2D),
				typeof(byte[]),
				typeof(bool)
			}, null);
			if (methodInfo == null)
			{
				Plugin.Log.LogWarning((object)"[RepoContracts] UnityEngine.ImageConversion.LoadImage was unavailable; packaged PNG assets cannot be decoded.");
				return false;
			}
			object obj = methodInfo.Invoke(null, new object[3] { texture, bytes, false });
			bool flag = default(bool);
			int num;
			if (obj is bool)
			{
				flag = (bool)obj;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static void ConfigureTransparentMaterial(Material material)
		{
			material.SetFloat("_Mode", 3f);
			material.SetInt("_SrcBlend", 5);
			material.SetInt("_DstBlend", 10);
			material.SetInt("_ZWrite", 0);
			material.DisableKeyword("_ALPHATEST_ON");
			material.EnableKeyword("_ALPHABLEND_ON");
			material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
			material.renderQueue = 3000;
		}
	}
	internal sealed class ContractBoardUi : MonoBehaviour
	{
		private readonly List<ContractBoardCard> cards = new List<ContractBoardCard>();

		private ContractQueue? queue;

		private GameObject? boardRoot;

		private TextMesh? headerText;

		private TextMesh? selectedText;

		private TextMesh? promptText;

		private int highlightedIndex = -1;

		private bool placementMode;

		private float controllerMoveCooldownUntil;

		internal void Show(ContractQueue contractQueue)
		{
			queue = contractQueue;
			EnsureBoard();
			RebuildBoardText();
			LogBoard("show");
		}

		internal void Refresh(ContractQueue contractQueue)
		{
			queue = contractQueue;
			EnsureBoard();
			RebuildBoardText();
			LogBoard("refresh");
		}

		internal void Hide()
		{
			if ((Object)(object)boardRoot != (Object)null)
			{
				Object.Destroy((Object)(object)boardRoot);
				boardRoot = null;
			}
			cards.Clear();
			headerText = null;
			selectedText = null;
			promptText = null;
			highlightedIndex = -1;
		}

		internal Vector3? GetRewardSpawnPosition()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)boardRoot == (Object)null)
			{
				return null;
			}
			return boardRoot.transform.position - boardRoot.transform.forward * (1.15f * Plugin.BoardScale.Value) + Vector3.up * (0.2f * Plugin.BoardScale.Value);
		}

		private void Update()
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)boardRoot != (Object)null)
			{
				UpdatePlacementMode();
				if (placementMode)
				{
					return;
				}
			}
			if (queue == null || (Object)(object)boardRoot == (Object)null || queue.OfferedContracts.Count == 0 || queue.IsLocked)
			{
				return;
			}
			UpdateHighlight();
			UpdateControllerHighlight();
			Plugin.Manager.TryAutoLockQueueFromVotes();
			if (highlightedIndex >= 0 && highlightedIndex < queue.OfferedContracts.Count)
			{
				if (Input.GetKeyDown((KeyCode)49) || ControllerRankPressed(1))
				{
					Vote(1);
				}
				else if (Input.GetKeyDown((KeyCode)50) || ControllerRankPressed(2))
				{
					Vote(2);
				}
				else if (Input.GetKeyDown((KeyCode)51) || ControllerRankPressed(3))
				{
					Vote(3);
				}
				if (Input.GetKeyDown(Plugin.ContractBoardLockKey.Value))
				{
					Plugin.Manager.HostLockQueueFromVotes();
				}
			}
		}

		private void Vote(int rank)
		{
			if (queue != null)
			{
				string localPlayerId = GetLocalPlayerId();
				string definitionId = queue.OfferedContracts[highlightedIndex].DefinitionId;
				if (Plugin.Manager.CastVote(localPlayerId, rank, definitionId))
				{
					RebuildBoardText();
					Plugin.Manager.TryAutoLockQueueFromVotes();
				}
			}
		}

		private void EnsureBoard()
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_029f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02be: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0306: Unknown result type (might be due to invalid IL or missing references)
			//IL_034e: Unknown result type (might be due to invalid IL or missing references)
			//IL_036d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0391: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_041b: Unknown result type (might be due to invalid IL or missing references)
			//IL_043b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0459: Unknown result type (might be due to invalid IL or missing references)
			//IL_047e: Unknown result type (might be due to invalid IL or missing references)
			//IL_049c: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0548: Unknown result type (might be due to invalid IL or missing references)
			//IL_0567: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cf: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)boardRoot != (Object)null))
			{
				GameObject val = FindTruckAnchor();
				Vector3 position = (((Object)(object)val != (Object)null) ? (val.transform.position + val.transform.right * Plugin.BoardSideOffset.Value + val.transform.forward * Plugin.BoardForwardOffset.Value + Vector3.up * Plugin.BoardUpOffset.Value) : (Vector3.up * Plugin.BoardUpOffset.Value));
				Quaternion rotation = (((Object)(object)val != (Object)null) ? Quaternion.Euler(0f, val.transform.eulerAngles.y + Plugin.BoardYawOffset.Value, 0f) : Quaternion.identity);
				boardRoot = new GameObject("Repo Contracts Board");
				boardRoot.transform.position = position;
				boardRoot.transform.rotation = rotation;
				boardRoot.transform.localScale = Vector3.one * Plugin.BoardScale.Value;
				if ((Object)(object)((val != null) ? val.transform.parent : null) != (Object)null)
				{
					boardRoot.transform.SetParent(val.transform.parent, true);
				}
				GameObject obj = GameObject.CreatePrimitive((PrimitiveType)3);
				((Object)obj).name = "Repo Contracts Board Back";
				obj.transform.SetParent(boardRoot.transform, false);
				obj.transform.localPosition = Vector3.zero;
				obj.transform.localScale = new Vector3(1.54f, 0.88f, 0.045f);
				DisablePhysicalCollider(obj);
				SetColor(obj, new Color(0.012f, 0.018f, 0.02f, 1f));
				GameObject obj2 = GameObject.CreatePrimitive((PrimitiveType)3);
				((Object)obj2).name = "Repo Contracts TV Bezel";
				obj2.transform.SetParent(boardRoot.transform, false);
				obj2.transform.localPosition = new Vector3(0f, 0f, 0.03f);
				obj2.transform.localScale = new Vector3(1.68f, 1.02f, 0.035f);
				DisablePhysicalCollider(obj2);
				SetColor(obj2, new Color(0.02f, 0.022f, 0.024f, 1f));
				GameObject obj3 = GameObject.CreatePrimitive((PrimitiveType)3);
				((Object)obj3).name = "Repo Contracts TV Screen";
				obj3.transform.SetParent(boardRoot.transform, false);
				obj3.transform.localPosition = new Vector3(0f, 0f, -0.035f);
				obj3.transform.localScale = new Vector3(1.48f, 0.8f, 0.018f);
				DisablePhysicalCollider(obj3);
				SetColor(obj3, new Color(0.02f, 0.08f, 0.085f, 1f));
				ContractAssets.ApplyTexture(obj3, "ui/board/ui_contract_board_header.png", new Color(0.65f, 1f, 0.95f, 0.58f));
				GameObject obj4 = GameObject.CreatePrimitive((PrimitiveType)3);
				((Object)obj4).name = "Repo Contracts Card Panel";
				obj4.transform.SetParent(boardRoot.transform, false);
				obj4.transform.localPosition = new Vector3(-0.2f, -0.035f, -0.07f);
				obj4.transform.localScale = new Vector3(1.08f, 0.66f, 0.012f);
				DisablePhysicalCollider(obj4);
				SetColor(obj4, new Color(0.01f, 0.035f, 0.04f, 1f));
				GameObject obj5 = GameObject.CreatePrimitive((PrimitiveType)3);
				((Object)obj5).name = "Repo Contracts TV Glow";
				obj5.transform.SetParent(boardRoot.transform, false);
				obj5.transform.localPosition = new Vector3(0f, 0f, -0.052f);
				obj5.transform.localScale = new Vector3(1.36f, 0.66f, 0.01f);
				DisablePhysicalCollider(obj5);
				SetColor(obj5, new Color(0.03f, 0.16f, 0.16f, 0.9f));
				headerText = CreateText("Header", new Vector3(-0.68f, 0.35f, -0.09f), 0.0185f, new Color(0.72f, 1f, 0.94f, 1f));
				selectedText = CreateText("Selected", new Vector3(0.38f, 0.24f, -0.09f), 0.0105f, new Color(0.88f, 1f, 0.72f, 1f));
				promptText = CreateText("Prompt", new Vector3(-0.68f, -0.37f, -0.09f), 0.0105f, new Color(1f, 0.95f, 0.55f, 1f));
				for (int i = 0; i < 5; i++)
				{
					GameObject obj6 = GameObject.CreatePrimitive((PrimitiveType)3);
					((Object)obj6).name = $"Repo Contracts Card {i + 1}";
					obj6.transform.SetParent(boardRoot.transform, false);
					obj6.transform.localPosition = new Vector3(-0.21f, 0.19f - (float)i * 0.118f, -0.105f);
					obj6.transform.localScale = new Vector3(1.02f, 0.105f, 0.035f);
					MakeTriggerOnly(obj6);
					ContractBoardCard contractBoardCard = obj6.AddComponent<ContractBoardCard>();
					contractBoardCard.Index = i;
					contractBoardCard.Text = CreateText($"CardText{i}", new Vector3(-0.66f, 0.224f - (float)i * 0.118f, -0.13f), 0.0072f, new Color(0.94f, 1f, 0.96f, 1f));
					cards.Add(contractBoardCard);
				}
			}
		}

		private void UpdatePlacementMode()
		{
			if (Input.GetKeyDown((KeyCode)290))
			{
				placementMode = !placementMode;
				Plugin.Log.LogInfo((object)(placementMode ? "[RepoContracts] Placement ON: Numpad 4/6 side, 8/2 forward, 7/9 up, 1/3 yaw, +/- scale, F9 save/exit." : "[RepoContracts] Placement OFF."));
				RebuildBoardText();
				SavePlacement();
			}
			if (placementMode)
			{
				bool flag = false;
				float num = Mathf.Max(0.01f, Plugin.BoardNudgeStep.Value);
				float num2 = Mathf.Max(0.5f, Plugin.BoardRotateStep.Value);
				if (Input.GetKeyDown((KeyCode)260))
				{
					ConfigEntry<float> boardSideOffset = Plugin.BoardSideOffset;
					boardSideOffset.Value -= num;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)262))
				{
					ConfigEntry<float> boardSideOffset2 = Plugin.BoardSideOffset;
					boardSideOffset2.Value += num;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)258))
				{
					ConfigEntry<float> boardForwardOffset = Plugin.BoardForwardOffset;
					boardForwardOffset.Value -= num;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)264))
				{
					ConfigEntry<float> boardForwardOffset2 = Plugin.BoardForwardOffset;
					boardForwardOffset2.Value += num;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)263))
				{
					ConfigEntry<float> boardUpOffset = Plugin.BoardUpOffset;
					boardUpOffset.Value += num;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)265))
				{
					ConfigEntry<float> boardUpOffset2 = Plugin.BoardUpOffset;
					boardUpOffset2.Value -= num;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)257))
				{
					ConfigEntry<float> boardYawOffset = Plugin.BoardYawOffset;
					boardYawOffset.Value -= num2;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)259))
				{
					ConfigEntry<float> boardYawOffset2 = Plugin.BoardYawOffset;
					boardYawOffset2.Value += num2;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)270) || Input.GetKeyDown((KeyCode)61))
				{
					ConfigEntry<float> boardScale = Plugin.BoardScale;
					boardScale.Value += 0.04f;
					flag = true;
				}
				if (Input.GetKeyDown((KeyCode)269) || Input.GetKeyDown((KeyCode)45))
				{
					Plugin.BoardScale.Value = Mathf.Max(0.25f, Plugin.BoardScale.Value - 0.04f);
					flag = true;
				}
				if (flag)
				{
					ApplyPlacement();
					RebuildBoardText();
					SavePlacement();
				}
			}
		}

		private void ApplyPlacement()
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)boardRoot == (Object)null))
			{
				GameObject val = FindTruckAnchor();
				Vector3 position = (((Object)(object)val != (Object)null) ? (val.transform.position + val.transform.right * Plugin.BoardSideOffset.Value + val.transform.forward * Plugin.BoardForwardOffset.Value + Vector3.up * Plugin.BoardUpOffset.Value) : (Vector3.up * Plugin.BoardUpOffset.Value));
				Quaternion rotation = (((Object)(object)val != (Object)null) ? Quaternion.Euler(0f, val.transform.eulerAngles.y + Plugin.BoardYawOffset.Value, 0f) : Quaternion.Euler(0f, Plugin.BoardYawOffset.Value, 0f));
				boardRoot.transform.position = position;
				boardRoot.transform.rotation = rotation;
				boardRoot.transform.localScale = Vector3.one * Plugin.BoardScale.Value;
			}
		}

		private static void SavePlacement()
		{
			Plugin.SaveSettings();
			Plugin.Log.LogInfo((object)("[RepoContracts] Board placement saved: " + $"SideOffset={Plugin.BoardSideOffset.Value:F2}, " + $"ForwardOffset={Plugin.BoardForwardOffset.Value:F2}, " + $"UpOffset={Plugin.BoardUpOffset.Value:F2}, " + $"YawOffset={Plugin.BoardYawOffset.Value:F1}, " + $"Scale={Plugin.BoardScale.Value:F2}"));
		}

		private void UpdateHighlight()
		{
			int num = -1;
			Transform val = (((Object)(object)PlayerController.instance != (Object)null && (Object)(object)PlayerController.instance.cameraGameObject != (Object)null) ? PlayerController.instance.cameraGameObject.transform : (((Object)(object)Camera.main != (Object)null) ? ((Component)Camera.main).transform : null));
			if ((Object)(object)val != (Object)null && TryGetBoardAimIndex(val, out var index))
			{
				num = index;
			}
			if (num != highlightedIndex)
			{
				highlightedIndex = num;
				RebuildBoardText();
			}
		}

		private bool TryGetBoardAimIndex(Transform aim, out int index)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			index = -1;
			if ((Object)(object)boardRoot == (Object)null || queue == null)
			{
				return false;
			}
			Plane val = default(Plane);
			((Plane)(ref val))..ctor(boardRoot.transform.forward, boardRoot.transform.position);
			Ray val2 = default(Ray);
			((Ray)(ref val2))..ctor(aim.position, aim.forward);
			float num = default(float);
			if (!((Plane)(ref val)).Raycast(val2, ref num) || num < 0f || num > 7f)
			{
				return false;
			}
			Vector3 val3 = boardRoot.transform.InverseTransformPoint(((Ray)(ref val2)).GetPoint(num));
			if (val3.x < -0.78f || val3.x > 0.33f)
			{
				return false;
			}
			int num2 = Mathf.Min(queue.OfferedContracts.Count, cards.Count);
			for (int i = 0; i < num2; i++)
			{
				float num3 = 0.19f - (float)i * 0.118f;
				if (Mathf.Abs(val3.y - num3) <= 0.067f)
				{
					index = i;
					return true;
				}
			}
			return false;
		}

		private void UpdateControllerHighlight()
		{
			if (queue != null && queue.OfferedContracts.Count != 0 && !(Time.time < controllerMoveCooldownUntil))
			{
				int num = 0;
				float axisRaw = Input.GetAxisRaw("Vertical");
				if (axisRaw > 0.55f || Input.GetKeyDown((KeyCode)343))
				{
					num = -1;
				}
				else if (axisRaw < -0.55f || Input.GetKeyDown((KeyCode)344))
				{
					num = 1;
				}
				if (num != 0)
				{
					int count = queue.OfferedContracts.Count;
					highlightedIndex = ((highlightedIndex >= 0) ? ((highlightedIndex + num + count) % count) : 0);
					controllerMoveCooldownUntil = Time.time + 0.22f;
					RebuildBoardText();
				}
			}
		}

		private static bool ControllerRankPressed(int rank)
		{
			return rank switch
			{
				1 => Input.GetKeyDown((KeyCode)330) || SafeInputDown((InputKey)26), 
				2 => Input.GetKeyDown((KeyCode)331) || SafeInputDown((InputKey)27), 
				3 => Input.GetKeyDown((KeyCode)332) || SafeInputDown((InputKey)28), 
				_ => false, 
			};
		}

		private static bool SafeInputDown(InputKey key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				return SemiFunc.InputDown(key);
			}
			catch
			{
				return false;
			}
		}

		private void RebuildBoardText()
		{
			if (queue == null)
			{
				return;
			}
			if ((Object)(object)headerText != (Object)null)
			{
				headerText.text = (queue.IsLocked ? "CONTRACT QUEUE LOCKED" : "CONTRACT BOARD");
			}
			for (int i = 0; i < cards.Count; i++)
			{
				ContractBoardCard contractBoardCard = cards[i];
				if (i >= queue.OfferedContracts.Count)
				{
					((Component)contractBoardCard).gameObject.SetActive(false);
					continue;
				}
				((Component)contractBoardCard).gameObject.SetActive(true);
				ContractInstance contractInstance = queue.OfferedContracts[i];
				ApplyCardVisual(((Component)contractBoardCard).gameObject, contractInstance.Definition.Rarity, i == highlightedIndex);
				if ((Object)(object)contractBoardCard.Text != (Object)null)
				{
					contractBoardCard.Text.text = $"{i + 1}. {Shorten(contractInstance.Definition.DisplayName, 18)} [{DifficultyLabel(contractInstance.Definition.Rarity)}]\n" + Shorten(contractInstance.Definition.ObjectiveText, 32);
				}
			}
			if ((Object)(object)selectedText != (Object)null)
			{
				selectedText.text = BuildVoteSummary();
			}
			if ((Object)(object)promptText != (Object)null)
			{
				promptText.text = (placementMode ? "PLACE: 4/6 side 8/2 fwd 7/9 up 1/3 yaw +/- scale F9 save" : (queue.IsLocked ? "QUEUE LOCKED - Press C to view contracts." : "Aim/D-pad select. 1/2/3 or buttons vote. Auto-locks."));
			}
		}

		private string BuildVoteSummary()
		{
			if (queue == null)
			{
				return string.Empty;
			}
			string localPlayerId = GetLocalPlayerId();
			Plugin.Manager.PlayerVotes.TryGetValue(localPlayerId, out string[] value);
			string text = "Votes\n";
			for (int i = 0; i < Plugin.SelectedContractsPerLevel.Value; i++)
			{
				string definitionId = ((value != null && i < value.Length) ? value[i] : string.Empty);
				ContractInstance contractInstance = ((!string.IsNullOrWhiteSpace(definitionId)) ? queue.OfferedContracts.FirstOrDefault((ContractInstance c) => c.DefinitionId == definitionId) : null);
				text += string.Format("{0}. {1}\n", i + 1, (contractInstance != null) ? Shorten(contractInstance.Definition.DisplayName, 11) : "-");
			}
			int num = ((!SemiFunc.IsMultiplayer() || PhotonNetwork.PlayerList == null) ? 1 : PhotonNetwork.PlayerList.Length);
			return text + $"{Plugin.Manager.PlayerVotes.Count}/{num}";
		}

		private TextMesh CreateText(string name, Vector3 localPosition, float size, Color color)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Repo Contracts " + name);
			val.transform.SetParent(boardRoot.transform, false);
			val.transform.localPosition = localPosition;
			val.transform.localRotation = Quaternion.identity;
			TextMesh obj = val.AddComponent<TextMesh>();
			obj.anchor = (TextAnchor)0;
			obj.alignment = (TextAlignment)0;
			obj.characterSize = size;
			obj.fontSize = 46;
			obj.color = color;
			return obj;
		}

		private static string Shorten(string value, int maxLength)
		{
			if (value.Length <= maxLength)
			{
				return value;
			}
			return value.Substring(0, Mathf.Max(1, maxLength - 1)) + ".";
		}

		private static string DifficultyLabel(ContractRarity rarity)
		{
			return rarity switch
			{
				ContractRarity.Common => "EASY", 
				ContractRarity.Uncommon => "MED", 
				ContractRarity.Rare => "HARD", 
				ContractRarity.Epic => "EPIC", 
				ContractRarity.Legendary => "LEG", 
				_ => rarity.ToString(), 
			};
		}

		private static Color GetCardColor(ContractRarity rarity, bool highlighted)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			Color val = (Color)(rarity switch
			{
				ContractRarity.Common => new Color(0.05f, 0.42f, 0.16f, 1f), 
				ContractRarity.Uncommon => new Color(0.55f, 0.46f, 0.08f, 1f), 
				ContractRarity.Rare => new Color(0.58f, 0.11f, 0.08f, 1f), 
				ContractRarity.Epic => new Color(0.33f, 0.13f, 0.48f, 1f), 
				ContractRarity.Legendary => new Color(0.65f, 0.32f, 0.05f, 1f), 
				_ => new Color(0.035f, 0.13f, 0.14f, 1f), 
			});
			if (!highlighted)
			{
				return val;
			}
			return Color.Lerp(val, Color.white, 0.28f);
		}

		private static void ApplyCardVisual(GameObject card, ContractRarity rarity, bool highlighted)
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			string relativePath = rarity switch
			{
				ContractRarity.Common => "ui/cards/ui_contract_card_frame_easy.png", 
				ContractRarity.Uncommon => "ui/cards/ui_contract_card_frame_med.png", 
				ContractRarity.Rare => "ui/cards/ui_contract_card_frame_hard.png", 
				ContractRarity.Epic => "ui/cards/ui_contract_card_frame_hard.png", 
				ContractRarity.Legendary => "ui/cards/ui_contract_card_frame_hard.png", 
				_ => "ui/cards/ui_contract_card_frame_med.png", 
			};
			Color tint = (Color)(highlighted ? Color.white : new Color(0.78f, 0.88f, 0.82f, 0.92f));
			ContractAssets.ApplyTexture(card, relativePath, tint);
			Renderer component = card.GetComponent<Renderer>();
			object obj;
			if (component == null)
			{
				obj = null;
			}
			else
			{
				Material material = component.material;
				obj = ((material != null) ? material.mainTexture : null);
			}
			if ((Object)obj == (Object)null)
			{
				SetColor(card, GetCardColor(rarity, highlighted));
			}
		}

		private static GameObject? FindTruckAnchor()
		{
			if ((Object)(object)TruckSafetySpawnPoint.instance != (Object)null)
			{
				return ((Component)TruckSafetySpawnPoint.instance).gameObject;
			}
			if ((Object)(object)TruckHealer.instance != (Object)null)
			{
				return ((Component)TruckHealer.instance).gameObject;
			}
			if ((Object)(object)TruckScreenText.instance != (Object)null)
			{
				return ((Component)TruckScreenText.instance).gameObject;
			}
			return GameObject.Find("cctv") ?? GameObject.Find("cctv (1)");
		}

		private static string GetLocalPlayerId()
		{
			if (!SemiFunc.IsMultiplayer() || PhotonNetwork.LocalPlayer == null)
			{
				return "singleplayer";
			}
			return $"player-{PhotonNetwork.LocalPlayer.ActorNumber}";
		}

		private static void SetColor(GameObject target, Color color)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Renderer component = target.GetComponent<Renderer>();
			if ((Object)(object)component != (Object)null)
			{
				component.material.color = color;
			}
		}

		private static void DisablePhysicalCollider(GameObject target)
		{
			Collider component = target.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
		}

		private static void MakeTriggerOnly(GameObject target)
		{
			Collider component = target.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				component.isTrigger = true;
			}
		}

		private void LogBoard(string action)
		{
			if (Plugin.EnableDebugLogging.Value && queue != null)
			{
				Plugin.Log.LogInfo((object)$"[RepoContracts] Board UI {action}: {queue.OfferedContracts.Count} offers, {queue.SelectedContracts.Count} selected.");
			}
		}
	}
	internal sealed class ContractBoardCard : MonoBehaviour
	{
		internal int Index { get; set; }

		internal TextMesh? Text { get; set; }
	}
	internal sealed class ContractHud : MonoBehaviour
	{
		private ContractQueue? queue;

		private string notification = string.Empty;

		private float notificationUntil;

		private bool visible;

		private bool overlayOpen;

		private GameObject? trackedTarget;

		private string trackedTargetLabel = string.Empty;

		internal void ShowActive(ContractQueue contractQueue)
		{
			queue = contractQueue;
			visible = true;
			LogHud();
		}

		internal void Hide()
		{
			visible = false;
			overlayOpen = false;
			notification = string.Empty;
			trackedTarget = null;
			trackedTargetLabel = string.Empty;
		}

		internal void SetTrackedTarget(GameObject? target, string label)
		{
			trackedTarget = target;
			trackedTargetLabel = label;
		}

		internal void NotifyComplete(ContractInstance contract)
		{
			ShowNotification("Contract Complete: " + contract.Definition.DisplayName);
		}

		internal void NotifyFailed(ContractInstance contract)
		{
			ShowNotification("Contract Failed: " + contract.Definition.DisplayName);
		}

		internal void NotifyNext(ContractInstance contract)
		{
			ShowNotification("Next Contract Active: " + contract.Definition.DisplayName);
		}

		internal void NotifyDiscipline(string message)
		{
			ShowNotification(message);
		}

		private void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if (Input.GetKeyDown(Plugin.ContractHudKey.Value))
			{
				overlayOpen = !overlayOpen;
			}
		}

		private void OnGUI()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			if (!visible || !overlayOpen || queue == null)
			{
				DrawTrackedTarget();
				DrawNotification();
				return;
			}
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				wordWrap = true
			};
			Rect rect = default(Rect);
			((Rect)(ref rect))..ctor((float)Screen.width - 550f, 36f, 520f, 340f);
			DrawTexturedBox(rect, "ui/hud/ui_active_contract_hud_panel.png", new Color(0.65f, 1f, 0.95f, 0.78f));
			GUILayout.BeginArea(new Rect(((Rect)(ref rect)).x + 18f, ((Rect)(ref rect)).y + 14f, ((Rect)(ref rect)).width - 36f, ((Rect)(ref rect)).height - 28f));
			GUILayout.Label("Repo Contracts", val, Array.Empty<GUILayoutOption>());
			if (Plugin.EnableDisciplineDebt.Value && queue.DisciplineDebt > 0)
			{
				GUILayout.Label(string.Format("Discipline Debt: {0} future reward{1} withheld", queue.DisciplineDebt, (queue.DisciplineDebt == 1) ? string.Empty : "s"), val, Array.Empty<GUILayoutOption>());
			}
			ContractInstance activeContract = queue.ActiveContract;
			if (activeContract == null)
			{
				GUILayout.Label(queue.IsLocked ? "No active contract." : "Vote at the truck contract board.", val, Array.Empty<GUILayoutOption>());
			}
			else
			{
				GUILayout.Label($"Active {queue.ActiveIndex + 1}/{queue.SelectedContracts.Count}: {activeContract.Definition.DisplayName}", val, Array.Empty<GUILayoutOption>());
				GUILayout.Label(activeContract.Definition.ObjectiveText, val, Array.Empty<GUILayoutOption>());
				GUILayout.Label($"Progress: {activeContract.CurrentProgress}/{activeContract.RequiredProgress}", val, Array.Empty<GUILayoutOption>());
				GUILayout.Label("Failure: " + activeContract.Definition.FailureText, val, Array.Empty<GUILayoutOption>());
				GUILayout.Label("Reward: " + activeContract.Definition.RewardPreviewText, val, Array.Empty<GUILayoutOption>());
				if (activeContract.Definition.Id == "glass_cargo")
				{
					string value;
					string text = ((activeContract.SelectedTargets.TryGetValue("targetName", out value) && !string.IsNullOrWhiteSpace(value)) ? value : "Random marked valuable");
					GUILayout.Label("Target: " + text, val, Array.Empty<GUILayoutOption>());
				}
			}
			GUILayout.Space(8f);
			for (int i = 0; i < queue.SelectedContracts.Count; i++)
			{
				ContractInstance contractInstance = queue.SelectedContracts[i];
				GUILayout.Label($"{i + 1}. {contractInstance.Definition.DisplayName} - {contractInstance.Status}", val, Array.Empty<GUILayoutOption>());
			}
			GUILayout.EndArea();
			DrawTrackedTarget();
			DrawNotification();
		}

		private void DrawNotification()
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			if (!string.IsNullOrEmpty(notification) && !(Time.time >= notificationUntil))
			{
				Rect rect = default(Rect);
				((Rect)(ref rect))..ctor(((float)Screen.width - 520f) * 0.5f, 80f, 520f, 70f);
				string asset = (notification.Contains("Failed") ? "ui/banners/ui_banner_contract_failed.png" : (notification.Contains("Complete") ? "ui/banners/ui_banner_contract_complete.png" : (notification.Contains("Reward") ? "ui/banners/ui_banner_reward_earned.png" : "ui/warnings/ui_warning_timer_critical.png")));
				DrawTexturedBox(rect, asset, Color.white);
				GUILayout.BeginArea(new Rect(((Rect)(ref rect)).x + 28f, ((Rect)(ref rect)).y + 18f, ((Rect)(ref rect)).width - 56f, ((Rect)(ref rect)).height - 28f));
				GUILayout.Label(notification, Array.Empty<GUILayoutOption>());
				GUILayout.EndArea();
			}
		}

		private void DrawTrackedTarget()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			if (!visible || (Object)(object)trackedTarget == (Object)null || queue?.ActiveContract?.Definition.Id != "glass_cargo")
			{
				return;
			}
			Camera main = Camera.main;
			if (!((Object)(object)main == (Object)null))
			{
				Vector3 val = trackedTarget.transform.position + Vector3.up * 0.6f;
				Vector3 val2 = main.WorldToScreenPoint(val);
				bool num = val2.z < 0f;
				if (num)
				{
					val2.x = (float)Screen.width - val2.x;
					val2.y = (float)Screen.height - val2.y;
				}
				float num2 = 48f;
				bool flag = num || val2.x < num2 || val2.x > (float)Screen.width - num2 || val2.y < num2 || val2.y > (float)Screen.height - num2;
				float num3 = Mathf.Clamp(val2.x, num2, (float)Screen.width - num2);
				float num4 = Mathf.Clamp((float)Screen.height - val2.y, num2, (float)Screen.height - num2);
				float num5 = Vector3.Distance(((Component)main).transform.position, trackedTarget.transform.position);
				string text = (string.IsNullOrWhiteSpace(trackedTargetLabel) ? "GLASS CARGO" : trackedTargetLabel);
				Color color = GUI.color;
				GUI.color = new Color(0.2f, 1f, 0.9f, 0.95f);
				GUI.Box(new Rect(((float)Screen.width - 320f) * 0.5f, 132f, 320f, 54f), $"GLASS CARGO TARGET\n{text} - {Mathf.RoundToInt(num5)}m");
				GUI.Box(new Rect(num3 - 105f, num4 - 26f, 210f, 52f), string.Format("{0}\n{1} - {2}m", flag ? "TARGET" : "MARKED", text, Mathf.RoundToInt(num5)));
				GUI.color = color;
			}
		}

		private void ShowNotification(string message)
		{
			notification = message;
			notificationUntil = Time.time + 4f;
			if (Plugin.EnableDebugLogging.Value)
			{
				Plugin.Log.LogInfo((object)("[RepoContracts] HUD notification: " + message));
			}
		}

		private static void DrawTexturedBox(Rect rect, string asset, Color tint)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: 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)
			Texture2D val = ContractAssets.Texture(asset);
			Color color = GUI.color;
			if ((Object)(object)val != (Object)null)
			{
				GUI.color = tint;
				GUI.DrawTexture(rect, (Texture)(object)val, (ScaleMode)0, true);
			}
			else
			{
				GUI.Box(rect, GUIContent.none);
			}
			GUI.color = color;
		}

		private void LogHud()
		{
			if (Plugin.EnableDebugLogging.Value && visible && queue != null)
			{
				ContractInstance activeContract = queue.ActiveContract;
				if (activeContract == null)
				{
					Plugin.Log.LogInfo((object)"[RepoContracts] HUD ready: no active contract.");
				}
				else
				{
					Plugin.Log.LogInfo((object)($"[RepoContracts] HUD active {queue.ActiveIndex + 1}/{queue.SelectedContracts.Count}: " + $"{activeContract.Definition.DisplayName} {activeContract.CurrentProgress}/{activeContract.RequiredProgress}. " + "Reward: " + activeContract.Definition.RewardPreviewText));
				}
			}
		}
	}
}
namespace RepoContracts.Rewards
{
	internal sealed class RewardAdapter
	{
		private static readonly FieldInfo? DollarOverrideField = typeof(ValuableObject).GetField("dollarValueOverride", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal void Grant(RewardRoll reward, Vector3 position, RewardDeliveryContext context)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			if (!SemiFunc.IsMasterClientOrSingleplayer())
			{
				return;
			}
			if (!context.CashRewardsUsable && IsCashReward(reward))
			{
				GrantNonCashSubstitute(reward, position, context);
				return;
			}
			switch (reward.Kind)
			{
			case RewardKind.CashBonus:
			case RewardKind.MoneyBag:
				SpawnMoneyBag(position, Mathf.RoundToInt((float)reward.Amount * Plugin.RewardMultiplier.Value));
				break;
			case RewardKind.ShopItem:
				if (!TrySpawnShopItemReward(reward, position, IsGeneralShopReward, "shop item"))
				{
					GrantFallback(reward, position, context);
				}
				break;
			case RewardKind.Weapon:
				if (!TrySpawnShopItemReward(reward, position, IsWeaponReward, "weapon"))
				{
					GrantFallback(reward, position, context);
				}
				break;
			case RewardKind.UpgradeDiscount:
				if (!TrySpawnShopItemReward(reward, position, IsUpgradeReward, "upgrade"))
				{
					GrantFallback(reward, position, context);
				}
				break;
			case RewardKind.CosmeticToken:
				if (!TryGrantCosmeticToken(reward))
				{
					GrantFallback(reward, position, context);
				}
				break;
			case RewardKind.SpawnableValuable:
			case RewardKind.ContractRerollToken:
				Plugin.Log.LogWarning((object)$"[RepoContracts] Reward adapter TODO: {reward.Kind} is modeled but not safely implemented yet.");
				GrantFallback(reward, position, context);
				break;
			default:
				Plugin.Log.LogWarning((object)$"[RepoContracts] Unknown reward kind {reward.Kind}.");
				break;
			}
		}

		private static bool IsCashReward(RewardRoll reward)
		{
			RewardKind kind = reward.Kind;
			if ((uint)kind <= 1u)
			{
				return true;
			}
			return false;
		}

		private static void GrantNonCashSubstitute(RewardRoll reward, Vector3 position, RewardDeliveryContext context)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			Plugin.Log.LogInfo((object)("[RepoContracts] Cash reward converted because cash is unsafe now (" + context.Reason + ")."));
			if (!TrySpawnShopItemReward(reward, position, IsGeneralShopReward, "shop item substitute") && !TryGrantCosmeticToken(reward))
			{
				Plugin.Log.LogWarning((object)"[RepoContracts] Non-cash substitute failed; spawning money bag as last-resort fallback.");
				SpawnFallbackMoneyBag(reward, position);
			}
		}

		private static void GrantFallback(RewardRoll reward, Vector3 position, RewardDeliveryContext context)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if (context.CashRewardsUsable)
			{
				SpawnFallbackMoneyBag(reward, position);
				return;
			}
			if (TryGrantCosmeticToken(reward))
			{
				Plugin.Log.LogInfo((object)("[RepoContracts] Granted cosmetic token fallback because cash is unsafe now (" + context.Reason + ")."));
				return;
			}
			Plugin.Log.LogWarning((object)"[RepoContracts] Cash-unsafe fallback had no cosmetic path; spawning money bag as last resort.");
			SpawnFallbackMoneyBag(reward, position);
		}

		private static void SpawnMoneyBag(Vector3 position, int amount)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)AssetManager.instance == (Object)null || (Object)(object)AssetManager.instance.surplusValuableSmall == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[RepoContracts] Money bag reward unavailable: AssetManager.surplusValuableSmall was not found.");
				return;
			}
			GameObject surplusValuableSmall = AssetManager.instance.surplusValuableSmall;
			int num = Mathf.Max(1000, Mathf.RoundToInt(Mathf.Round((float)amount / 100f) * 100f));
			ValuableObject component = (SemiFunc.IsMultiplayer() ? PhotonNetwork.InstantiateRoomObject("Valuables/" + ((Object)surplusValuableSmall).name, position, Quaternion.identity, (byte)0, (object[])null) : Object.Instantiate<GameObject>(surplusValuableSmall, position, Quaternion.identity)).GetComponent<ValuableObject>();
			if ((Object)(object)component != (Object)null)
			{
				DollarOverrideField?.SetValue(component, num);
			}
			Plugin.Log.LogInfo((object)$"[RepoContracts] Spawned contract money bag reward ${num}.");
		}

		private static void SpawnFallbackMoneyBag(RewardRoll reward, Vector3 position)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			int num = ((reward.Amount > 0) ? reward.Amount : 2500);
			SpawnMoneyBag(position, Mathf.RoundToInt((float)num * Plugin.RewardMultiplier.Value));
		}

		private static bool TrySpawnShopItemReward(RewardRoll reward, Vector3 position, Func<Item, bool> predicate, string label)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			Func<Item, bool> predicate2 = predicate;
			if ((Object)(object)StatsManager.instance == (Object)null || StatsManager.instance.itemDictionary == null)
			{
				Plugin.Log.LogWarning((object)("[RepoContracts] " + label + " reward unavailable: StatsManager item dictionary was not ready."));
				return false;
			}
			List<Item> list = StatsManager.instance.itemDictionary.Values.Where((Item item) => (Object)(object)item != (Object)null && !item.disabled && item.physicalItem && item.prefab != null && item.prefab.IsValid() && predicate2(item)).ToList();
			if (list.Count == 0)
			{
				Plugin.Log.LogWarning((object)("[RepoContracts] " + label + " reward unavailable: no eligible shop items were found."));
				return false;
			}
			Item val = list[Random.Range(0, list.Count)];
			Quaternion val2 = Quaternion.identity * val.spawnRotationOffset;
			try
			{
				if ((Object)(object)(SemiFunc.IsMultiplayer() ? PhotonNetwork.InstantiateRoomObject(val.prefab.ResourcePath, position, val2, (byte)0, (object[])null) : Object.Instantiate<GameObject>(val.prefab.Prefab, position, val2)) == (Object)null)
				{
					Plugin.Log.LogWarning((object)("[RepoContracts] " + label + " reward failed to spawn: " + val.itemName + "."));
					return false;
				}
				Plugin.Log.LogInfo((object)("[RepoContracts] Spawned contract " + label + " reward: " + val.itemName + "."));
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[RepoContracts] " + label + " reward failed to spawn " + val.itemName + ": " + ex.Message));
				return false;
			}
		}

		private static bool TryGrantCosmeticToken(RewardRoll reward)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)MetaManager.instance == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[RepoContracts] Cosmetic token reward unavailable: MetaManager was not ready.");
				return false;
			}
			try
			{
				MetaManager.instance.CosmeticTokenAdd(ToGameRarity(reward.Rarity));
				Plugin.Log.LogInfo((object)$"[RepoContracts] Granted contract cosmetic token reward: {reward.Rarity}.");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[RepoContracts] Cosmetic token reward failed: " + ex.Message));
				return false;
			}
		}

		private static bool IsGeneralShopReward(Item item)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected I4, but got Unknown
			itemType itemType = item.itemType;
			switch ((int)itemType)
			{
			case 0:
			case 1:
			case 2:
			case 5:
			case 6:
			case 7:
			case 9:
			case 10:
			case 11:
			case 12:
			case 13:
			case 14:
			case 15:
				return true;
			default:
				return false;
			}
		}

		private static bool IsWeaponReward(Item item)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected I4, but got Unknown
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Invalid comparison between Unknown and I4
			itemType itemType = item.itemType;
			switch (itemType - 6)
			{
			default:
				if ((int)itemType != 15)
				{
					break;
				}
				goto case 0;
			case 0:
			case 1:
			case 3:
			case 5:
				return true;
			case 2:
			case 4:
				break;
			}
			return false;
		}

		private static bool IsUpgradeReward(Item item)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			itemType itemType = item.itemType;
			if (itemType - 3 <= 1)
			{
				return true;
			}
			return false;
		}

		private static Rarity ToGameRarity(ContractRarity rarity)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			return (Rarity)(rarity switch
			{
				ContractRarity.Common => 0, 
				ContractRarity.Uncommon => 1, 
				ContractRarity.Rare => 2, 
				ContractRarity.Epic => 3, 
				ContractRarity.Legendary => 3, 
				_ => 0, 
			});
		}
	}
	internal readonly struct RewardDeliveryContext
	{
		internal bool CashRewardsUsable { get; }

		internal string Reason { get; }

		private RewardDeliveryContext(bool cashRewardsUsable, string reason)
		{
			CashRewardsUsable = cashRewardsUsable;
			Reason = reason;
		}

		internal static RewardDeliveryContext Normal()
		{
			return new RewardDeliveryContext(cashRewardsUsable: true, "normal");
		}

		internal static RewardDeliveryContext CashUnsafe(string reason)
		{
			return new RewardDeliveryContext(cashRewardsUsable: false, reason);
		}
	}
}
namespace RepoContracts.Networking
{
	internal sealed class ContractSyncAdapter
	{
		internal readonly struct ContractSnapshot
		{
			internal static readonly ContractSnapshot Empty = new ContractSnapshot(Array.Empty<ContractInstance>(), Array.Empty<ContractInstance>(), -1, isLocked: false, 0, string.Empty);

			internal IReadOnlyList<ContractInstance> Offers { get; }

			internal IReadOnlyList<ContractInstance> Selected { get; }

			internal int ActiveIndex { get; }

			internal bool IsLocked { get; }

			internal int DisciplineDebt { get; }

			internal string VoteSessionId { get; }

			internal ContractSnapshot(IReadOnlyList<ContractInstance> offers, IReadOnlyList<ContractInstance> selected, int activeIndex, bool isLocked, int disciplineDebt, string voteSessionId)
			{
				Offers = offers;
				Selected = selected;
				ActiveIndex = activeIndex;
				IsLocked = isLocked;
				DisciplineDebt = disciplineDebt;
				VoteSessionId = voteSessionId;
			}
		}

		private const string StateKey = "RepoContracts.State";

		private const string RevisionKey = "RepoContracts.Revision";

		private int revision;

		internal void SyncOffers(ContractQueue queue)
		{
			Publish("offers", queue);
		}

		internal void SyncSelection(ContractQueue queue)
		{
			Publish("selection", queue);
		}

		internal void SyncLockedQueue(ContractQueue queue)
		{
			Publish("locked queue", queue);
		}

		internal void SyncVotes(ContractQueue queue, IReadOnlyDictionary<string, string[]> votes)
		{
			Publish("votes", queue);
			if (Plugin.EnableDebugLogging.Value)
			{
				Plugin.Log.LogInfo((object)$"[RepoContracts] Synced votes players={votes.Count}, offers={queue.OfferedContracts.Count}.");
			}
		}

		internal void SyncProgress(ContractQueue queue)
		{
			Publish("progress", queue);
		}

		internal void SyncCleared()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			if (CanPublish())
			{
				revision++;
				Hashtable val = new Hashtable
				{
					[(object)"RepoContracts.State"] = string.Empty,
					[(object)"RepoContracts.Revision"] = revision
				};
				PhotonNetwork.CurrentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
				if (Plugin.EnableDebugLogging.Value)
				{
					Plugin.Log.LogInfo((object)"[RepoContracts] Synced cleared contract state.");
				}
			}
		}

		internal bool TryReadSnapshot(ref int lastRevision, out string snapshot)
		{
			snapshot = string.Empty;
			if (SemiFunc.IsMultiplayer())
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (((currentRoom != null) ? ((RoomInfo)currentRoom).CustomProperties : null) != null)
				{
					if (!((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"RepoContracts.Revision", out object value) || !(value is int num))
					{
						return false;
					}
					if (num == lastRevision)
					{
						return false;
					}
					lastRevision = num;
					if (((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"RepoContracts.State", out object value2) && value2 is string text)
					{
						snapshot = text;
					}
					return true;
				}
			}
			return false;
		}

		internal string Serialize(ContractQueue queue)
		{
			string text = string.Join(",", queue.OfferedContracts.Select((ContractInstance c) => Escape(c.DefinitionId)));
			string text2 = string.Join("^", queue.SelectedContracts.Select(SerializeContract));
			return string.Join("|", "v1", queue.IsLocked ? "1" : "0", queue.ActiveIndex.ToString(), text, text2, queue.DisciplineDebt.ToString(), Escape(queue.VoteSessionId));
		}

		internal bool TryDeserialize(string snapshot, IReadOnlyList<ContractDefinition> catalog, out ContractSnapshot result)
		{
			result = ContractSnapshot.Empty;
			if (string.IsNullOrWhiteSpace(snapshot))
			{
				return true;
			}
			string[] array = snapshot.Split('|');
			if (array.Length < 5 || array[0] != "v1")
			{
				return false;
			}
			bool isLocked = array[1] == "1";
			int.TryParse(array[2], out var result2);
			List<ContractInstance> list = new List<ContractInstance>();
			foreach (string id in SplitList(array[3], ','))
			{
				ContractDefinition contractDefinition = catalog.FirstOrDefault((ContractDefinition c) => c.Id == Unescape(id));
				if (contractDefinition != null)
				{
					list.Add(new ContractInstance(contractDefinition, 0));
				}
			}
			List<ContractInstance> list2 = new List<ContractInstance>();
			foreach (string item in SplitList(array[4], '^'))
			{
				ContractInstance contractInstance = DeserializeContract(item, catalog);
				if (contractInstance != null)
				{
					list2.Add(contractInstance);
				}
			}
			int result3 = 0;
			if (array.Length > 5)
			{
				int.TryParse(array[5], out result3);
			}
			string voteSessionId = ((array.Length > 6) ? Unescape(array[6]) : string.Empty);
			result = new ContractSnapshot(list, list2, result2, isLocked, result3, voteSessionId);
			return true;
		}

		private void Publish(string label, ContractQueue queue)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: 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_004f: Expected O, but got Unknown
			if (!CanPublish())
			{
				LogLocal(label, queue);
				return;
			}
			revision++;
			Hashtable val = new Hashtable
			{
				[(object)"RepoContracts.State"] = Serialize(queue),
				[(object)"RepoContracts.Revision"] = revision
			};
			PhotonNetwork.CurrentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
			LogLocal(label, queue);
		}

		private static bool CanPublish()
		{
			if (SemiFunc.IsMultiplayer() && SemiFunc.IsMasterClientOrSingleplayer())
			{
				return PhotonNetwork.CurrentRoom != null;
			}
			return false;
		}

		private static void LogLocal(string stateName, ContractQueue queue)
		{
			if (Plugin.EnableDebugLogging.Value)
			{
				string text = (CanPublish() ? "Synced" : "Local");
				Plugin.Log.LogInfo((object)string.Format("[RepoContracts] {0} {1}: offered={2}, selected={3}, active={4}.", text, stateName, queue.OfferedContracts.Count, queue.SelectedContracts.Count, queue.ActiveContract?.Definition.DisplayName ?? "none"));
			}
		}

		private static string SerializeContract(ContractInstance contract)
		{
			string value = string.Join(";", contract.SelectedTargets.Select<KeyValuePair<string, string>, string>((KeyValuePair<string, string> kv) => Escape(kv.Key) + "=" + Escape(kv.Value)));
			return string.Join("~", Escape(contract.DefinitionId), ((int)contract.Status).ToString(), contract.CurrentProgress.ToString(), contract.RequiredProgress.ToString(), Escape(contract.FailureReason), Escape(value));
		}

		private static ContractInstance? DeserializeContract(string data, IReadOnlyList<ContractDefinition> catalog)
		{
			string[] array = data.Split('~');
			if (array.Length < 4)
			{
				return null;
			}
			string definitionId = Unescape(array[0]);
			ContractDefinition contractDefinition = catalog.FirstOrDefault((ContractDefinition c) => c.Id == definitionId);
			if (contractDefinition == null)
			{
				return null;
			}
			ContractInstance contractInstance = new ContractInstance(contractDefinition, 0);
			if (int.TryParse(array[1], out var result))
			{
				contractInstance.SetStatus((ContractStatus)result);
			}
			int.TryParse(array[2], out var result2);
			int.TryParse(array[3], out var result3);
			contractInstance.SetProgress(result2, result3);
			if (array.Length > 4)
			{
				contractInstance.SetFailureReason(Unescape(array[4]));
			}
			if (array.Length > 5)
			{
				foreach (string item in SplitList(Unescape(array[5]), ';'))
				{
					int num = item.IndexOf('=');
					if (num > 0)
					{
						Dictionary<string, string> selectedTargets = contractInstance.SelectedTargets;
						string key = Unescape(item.Substring(0, num));
						string text = item;
						int num2 = num + 1;
						selectedTargets[key] = Unescape(text.Substring(num2, text.Length - num2));
					}
				}
			}
			return contractInstance;
		}

		private static IEnumerable<string> SplitList(string value, char separator)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return from v in value.Split(separator)
					where !string.IsNullOrWhiteSpace(v)
					select v;
			}
			return Enumerable.Empty<string>();
		}

		private static string Escape(string value)
		{
			return Uri.EscapeDataString(value ?? string.Empty);
		}

		private static string Unescape(string value)
		{
			return Uri.UnescapeDataString(value ?? string.Empty);
		}
	}
}
namespace RepoContracts.Contracts
{
	internal readonly struct AssetRequirements
	{
		internal bool ExistingAssetsOnly { get; }

		internal bool NeedsUiAsset { get; }

		internal bool NeedsWorldObject { get; }

		internal bool NeedsVfx { get; }

		internal bool NeedsSfx { get; }

		internal bool NeedsIcon { get; }

		internal bool NeedsModel { get; }

		internal bool NeedsAnimation { get; }

		internal static AssetRequirements ExistingOnly => default(AssetRequirements);

		internal AssetRequirements(bool existingAssetsOnly = true, bool needsUiAsset = false, bool needsWorldObject = false, bool needsVfx = false, bool needsSfx = false, bool needsIcon = false, bool needsModel = false, bool needsAnimation = false)
		{
			ExistingAssetsOnly = existingAssetsOnly;
			NeedsUiAsset = needsUiAsset;
			NeedsWorldObject = needsWorldObject;
			NeedsVfx = needsVfx;
			NeedsSfx = needsSfx;
			NeedsIcon = needsIcon;
			NeedsModel = needsModel;
			NeedsAnimation = needsAnimation;
		}
	}
	internal sealed class ContractCatalog
	{
		private readonly List<ContractDefinition> definitions;

		internal IReadOnlyList<ContractDefinition> All => definitions;

		internal ContractCatalog()
		{
			definitions = new List<ContractDefinition>
			{
				Prototype("clean_sweep", "Clean Sweep", ContractCategory.Loot, ContractRarity.Common, "Extract a target value while active.", "Extract valuables while this contract is active.", "Extract $1,000 in valuables.", "No failure condition.", "+$2,500 money bag", 1000, ContractHook.ExtractionValueChanged, null, RewardRoll.MoneyBag(ContractRarity.Common, 2500, "+$2,500"), "loot", "value"),
				Prototype("monster_hunter", "Monster Hunter", ContractCategory.Combat, ContractRarity.Uncommon, "Kill enemies while active.", "Hunt enemies during the active contract window.", "Kill 3 enemies.", "No failure condition.", "+$4,000 money bag", 3, ContractHook.EnemyKilled, null, RewardRoll.MoneyBag(ContractRarity.Uncommon, 4000, "+$4,000"), "combat", "enemy-kill"),
				Prototype("melee_clause", "Melee Clause", ContractCategory.Combat, ContractRarity.Rare, "Kill enemies using melee only.", "Projectile weapon kills fail this contract while it is active.", "Kill 2 enemies with melee or close-range physics.", "Projectile kill while active.", "+$6,500 money bag", 2, ContractHook.EnemyKilled, ContractHook.ProjectileWeaponUsed, RewardRoll.MoneyBag(ContractRarity.Rare, 6500, "+$6,500"), "combat", "melee"),
				Prototype("glass_cargo", "Glass Cargo", ContractCategory.Loot, ContractRarity.Rare, "Extract a random protected valuable without breaking it.", "A random breakable valuable is marked and must be extracted before it breaks.", "Find the marked valuable and extract it before it breaks.", "Marked item broke or was destroyed.", "+$7,500 money bag", 1, ContractHook.ItemExtracted, ContractHook.ItemDestroyed, RewardRoll.MoneyBag(ContractRarity.Rare, 7500, "+$7,500"), new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: true, needsSfx: false, needsIcon: true), "loot", "fragile", "marker"),
				Prototype("no_one_left_bent", "No One Left Bent", ContractCategory.Survival, ContractRarity.Uncommon, "Complete the next extraction with no deaths.", "Complete one successful extraction while this contract is active. Any player death fails it, even if they are revived later.", "Complete the next extraction with no player deaths.", "Any player dies before extraction completes.", "+$4,500 money bag", 1, ContractHook.ExtractionPointCompleted, ContractHook.PlayerDeath, RewardRoll.MoneyBag(ContractRarity.Uncommon, 4500, "+$4,500"), "survival", "death"),
				Scaffold("rotation_lock", "Rotation Lock", ContractCategory.Loot, ContractRarity.Rare, "Move a marked item without rotation assistance.", ContractHook.ItemExtracted, ContractHook.ItemGrabbed, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: true, needsSfx: false, needsIcon: true), true, "loot", "experimental", "rotation-control"),
				Scaffold("heavy_haul", "Heavy Haul", ContractCategory.Loot, ContractRarity.Uncommon, "Marked valuables become absurdly heavy.", ContractHook.ItemExtracted, ContractHook.ItemGrabbed, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: false, needsWorldObject: false, needsVfx: true, needsSfx: false, needsIcon: true), true, "loot", "weight-control"),
				Scaffold("cursed_loot", "Cursed Loot", ContractCategory.Loot, ContractRarity.Epic, "A cursed item must reach extraction.", ContractHook.ItemExtracted, ContractHook.ItemReleased, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: false, needsWorldObject: false, needsVfx: true, needsSfx: true, needsIcon: true), true, "loot", "cursed"),
				Scaffold("awkward_monopoly", "Awkward Monopoly", ContractCategory.Loot, ContractRarity.Epic, "Bias the level toward awkward bulky valuables.", ContractHook.ExtractionValueChanged, null, AssetRequirements.ExistingOnly, false, "loot", "spawn-replacement", "experimental"),
				Prototype("debt_collector", "Debt Collector", ContractCategory.Loot, ContractRarity.Common, "Extract a target value while active.", "Extract enough valuables before the contract advances.", "Extract $1,500 in valuables.", "No failure condition.", "+$3,000", 1500, ContractHook.ExtractionValueChanged, null, RewardRoll.MoneyBag(ContractRarity.Common, 3000, "+$3,000"), "loot", "value"),
				Scaffold("crew_limit_clause", "Crew Limit Clause", ContractCategory.Loot, ContractRarity.Rare, "Move a marked item with an exact grabber count.", ContractHook.ItemExtracted, ContractHook.ItemGrabbed, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: true, needsSfx: false, needsIcon: true), true, "loot", "grab-count"),
				Scaffold("bellringer", "Bellringer", ContractCategory.Combat, ContractRarity.Rare, "Alert enemies and survive the response.", ContractHook.EnemyKilled, ContractHook.EnemyAlerted, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: true), true, "combat", "enemy-alert"),
				Scaffold("hit_list", "Hit List", ContractCategory.Combat, ContractRarity.Epic, "Kill selected enemies back-to-back.", ContractHook.EnemyKilled, ContractHook.EnemySpawned, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: true, needsSfx: false, needsIcon: true), true, "combat", "target-marker"),
				Scaffold("chosen_killer", "Chosen Killer", ContractCategory.Combat, ContractRarity.Rare, "Only one selected player may score kills.", ContractHook.EnemyKilled, ContractHook.PlayerDeath, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: false, needsIcon: true), true, "combat", "kill-owner"),
				Scaffold("physics_department", "Physics Department", ContractCategory.Combat, ContractRarity.Epic, "Enemies must die from physics or environmental causes.", ContractHook.EnemyKilled, ContractHook.WeaponUsed, AssetRequirements.ExistingOnly, false, "combat", "damage-source", "experimental"),
				Scaffold("height_advantage", "Height Advantage", ContractCategory.Combat, ContractRarity.Uncommon, "Kill a target from a required vertical difference.", ContractHook.EnemyKilled, null, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true), true, "combat", "position-check"),
				Scaffold("gallery_code", "Gallery Code", ContractCategory.Puzzle, ContractRarity.Epic, "Find clue symbols and open a code safe.", ContractHook.ExtractionPointCompleted, null, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: true, needsVfx: false, needsSfx: false, needsIcon: true, needsModel: true, needsAnimation: true), false, "puzzle", "asset-heavy"),
				Scaffold("simon_extraction", "Simon Extraction", ContractCategory.Puzzle, ContractRarity.Rare, "Repeat the extraction point button sequence.", ContractHook.ExtractionPointCompleted, null, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: true, needsVfx: false, needsSfx: true, needsIcon: false, needsModel: true, needsAnimation: true), false, "puzzle", "asset-heavy"),
				Prototype("pressure_plate_extraction", "Pressure Plate Extraction", ContractCategory.Puzzle, ContractRarity.Rare, "Secure plates controlling one extraction.", "One random extraction point is locked behind pressure plates. Each plate can be held by a player or weighed down with medium-weight loot, so solo players can still complete it.", "Secure all plates, then use the controlled extraction.", "The controlled extraction was used before every plate was secured.", "+$8,000 money bag", 2, ContractHook.ExtractionPointCompleted, null, RewardRoll.MoneyBag(ContractRarity.Rare, 8000, "+$8,000"), new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: true, needsVfx: true, needsSfx: false, needsIcon: false, needsModel: true), "puzzle", "pressure-plate"),
				Scaffold("quiet_quota", "Quiet Quota", ContractCategory.Stealth, ContractRarity.Rare, "Extract value while alerts stay below a cap.", ContractHook.ExtractionValueChanged, ContractHook.EnemyAlerted, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: false, needsIcon: true), true, "stealth", "enemy-alert"),
				Scaffold("silent_courier", "Silent Courier", ContractCategory.Stealth, ContractRarity.Rare, "Extract a marked item without detection.", ContractHook.ItemExtracted, ContractHook.EnemyAlerted, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: true, needsSfx: false, needsIcon: true), true, "stealth", "marker"),
				Prototype("no_loud_tools", "No Loud Tools", ContractCategory.Stealth, ContractRarity.Uncommon, "Complete an extraction without loud weapons.", "Complete one extraction while this contract is active without using projectile or loud weapon tools.", "Complete the next extraction without projectile weapons.", "Projectile or loud weapon used.", "+$4,000 money bag", 1, ContractHook.ExtractionPointCompleted, ContractHook.ProjectileWeaponUsed, RewardRoll.MoneyBag(ContractRarity.Uncommon, 4000, "+$4,000"), "stealth", "weapon-use"),
				Scaffold("avoid_the_hunter", "Avoid the Hunter", ContractCategory.Stealth, ContractRarity.Rare, "Avoid detection from a selected enemy type.", ContractHook.LevelStarted, ContractHook.EnemyAlerted, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: false, needsIcon: true), true, "stealth", "enemy-type"),
				Scaffold("shadow_recovery", "Shadow Recovery", ContractCategory.Stealth, ContractRarity.Rare, "Recover a valuable near patrol without alerting it.", ContractHook.ItemExtracted, ContractHook.EnemyAlerted, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: true, needsSfx: false, needsIcon: true), true, "stealth", "marker"),
				Scaffold("low_profile", "Low Profile", ContractCategory.Stealth, ContractRarity.Uncommon, "Complete without sprinting or making loud noise.", ContractHook.LevelStarted, ContractHook.NoiseMade, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: false, needsIcon: true), true, "stealth", "noise"),
				Scaffold("ghost_clause", "Ghost Clause", ContractCategory.Stealth, ContractRarity.Epic, "Complete without any player being targeted.", ContractHook.LevelStarted, ContractHook.EnemyAlerted, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: false, needsIcon: true), true, "stealth", "targeting"),
				Scaffold("quarter_health", "Quarter Health", ContractCategory.Survival, ContractRarity.Rare, "Cap max health for the active contract.", ContractHook.LevelStarted, ContractHook.PlayerDeath, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: false, needsIcon: true), true, "survival", "health-mutator"),
				Scaffold("protect_the_asset", "Protect the Asset", ContractCategory.Survival, ContractRarity.Rare, "Protect a selected player.", ContractHook.LevelStarted, ContractHook.PlayerDeath, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: false, needsIcon: true), true, "survival", "player-marker"),
				Prototype("no_deaths", "No Deaths", ContractCategory.Survival, ContractRarity.Uncommon, "Complete an extraction with no deaths.", "Complete one successful extraction while this contract is active. Revives do not erase death tracking.", "Complete the next extraction with no deaths.", "Any player dies before extraction completes.", "+$4,500 money bag", 1, ContractHook.ExtractionPointCompleted, ContractHook.PlayerDeath, RewardRoll.MoneyBag(ContractRarity.Uncommon, 4500, "+$4,500"), "survival", "death"),
				Prototype("no_damage", "No Damage", ContractCategory.Survival, ContractRarity.Rare, "Complete an extraction without player damage.", "Complete one successful extraction while this contract is active without any player taking damage.", "Complete the next extraction without player damage.", "Any player takes damage.", "+$7,000 money bag", 1, ContractHook.ExtractionPointCompleted, ContractHook.PlayerDamaged, RewardRoll.MoneyBag(ContractRarity.Rare, 7000, "+$7,000"), "survival", "damage"),
				Prototype("limited_healing", "Limited Healing", ContractCategory.Survival, ContractRarity.Uncommon, "Complete an extraction without truck healing.", "Complete one extraction while this contract is active without using truck healing.", "Complete the next extraction without healing.", "A player heals while active.", "+$4,000 money bag", 1, ContractHook.ExtractionPointCompleted, ContractHook.PlayerHealed, RewardRoll.MoneyBag(ContractRarity.Uncommon, 4000, "+$4,000"), "survival", "healing"),
				Scaffold("last_legs", "Last Legs", ContractCategory.Survival, ContractRarity.Rare, "Start weakened; completion restores a small amount.", ContractHook.LevelStarted, ContractHook.PlayerDeath, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: false, needsVfx: false, needsSfx: false, needsIcon: true), true, "survival", "health-mutator"),
				Scaffold("holdout_clause", "Holdout Clause", ContractCategory.Survival, ContractRarity.Epic, "Survive pressure near a chosen area.", ContractHook.LevelStarted, ContractHook.PlayerDeath, new AssetRequirements(existingAssetsOnly: false, needsUiAsset: true, needsWorldObject: true, needsVfx: true), true, "survival", "area-marker"),
				Scaffold("hunter_lockdown", "Hunter Lockdown", ContractCategory.Milestone, C