Decompiled source of Straftat CMR v3.0.0

StraftatCMR.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CMRLib.Core;
using ComputerysModdingUtilities;
using FishNet;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Managing.Object;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using StraftatCMR.Features;
using StraftatCMR.Tweaks;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: StraftatMod(false)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("StraftatCMR")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("3.0.0.0")]
[assembly: AssemblyInformationalVersion("3.0.0+035ee42632884b38ea937935eb423d7c7e886381")]
[assembly: AssemblyProduct("StraftatCMR")]
[assembly: AssemblyTitle("StraftatCMR")]
[assembly: AssemblyVersion("3.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace StraftatCMR
{
	internal static class CmrRebalanceSettings
	{
		public static ConfigEntry<bool> HkVerbose { get; private set; }

		public static ConfigEntry<bool> WardenVerbose { get; private set; }

		public static void Bind(ConfigFile cfg)
		{
			HkVerbose = cfg.Bind<bool>("HK_G11", "VerboseLog", false, "Debug logging.");
			WardenVerbose = cfg.Bind<bool>("Warden", "VerboseLog", false, "Debug logging.");
		}
	}
	[BepInPlugin("cmr.rebalance", "StraftatCMR", "3.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class StraftatCMRPlugin : BaseUnityPlugin
	{
		public const string ModGuid = "cmr.rebalance";

		public const string ModName = "StraftatCMR";

		public const string ModVersion = "3.0.0";

		private readonly Harmony _harmony = new Harmony("cmr.rebalance");

		public static StraftatCMRPlugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"StraftatCMR 3.0.0 - starting");
			CmrRebalanceSettings.Bind(((BaseUnityPlugin)this).Config);
			ApplyTweaks();
			InstallFeatures();
			Install("Menu text", delegate
			{
				MenuText.Apply(Log, "3.0.0");
			});
			Install("Mod menu (optional)", delegate
			{
				ModMenuDecoration.Apply(typeof(StraftatCMRPlugin), "StraftatCMR.Resources.icon.png", "Weapon rebalance.", Log, (ConfigEntryBase[])(object)new ConfigEntryBase[2]
				{
					(ConfigEntryBase)CmrRebalanceSettings.HkVerbose,
					(ConfigEntryBase)CmrRebalanceSettings.WardenVerbose
				});
			});
			Log.LogInfo((object)"StraftatCMR 3.0.0 - ready");
		}

		private void InstallFeatures()
		{
			Install("HK_G11", delegate
			{
				HKG11FireMode.Init(Log, CmrRebalanceSettings.HkVerbose.Value);
				_harmony.PatchAll(typeof(HKG11FireMode));
			});
			Install("Gamma: projectile split", delegate
			{
				GammaProjectileSplit.Apply(Log);
				_harmony.PatchAll(typeof(GammaSpawnPatch));
			});
			Install("Mines: sounds", delegate
			{
				MineSounds.Apply(Log);
				_harmony.PatchAll(typeof(MineSounds));
			});
			Install("Claymore: arming sound", delegate
			{
				ClaymoreSounds.Apply(Log);
				_harmony.PatchAll(typeof(ClaymoreSounds));
			});
			Install("Weapon sounds (Bat / BlankState / Phoenix)", delegate
			{
				WeaponSounds.Apply(Log);
				_harmony.PatchAll(typeof(WeaponSoundsShootPatch));
				_harmony.PatchAll(typeof(WeaponSoundsMeleePatch));
				_harmony.PatchAll(typeof(WeaponSoundsAwakePatch));
				_harmony.PatchAll(typeof(MeleeSoundsAwakePatch));
			});
			Install("Warden flashlight", delegate
			{
				WardenFlashlight.Init(Log, CmrRebalanceSettings.WardenVerbose.Value);
				_harmony.PatchAll(typeof(WardenFlashlight));
			});
		}

		private void Install(string name, Action install)
		{
			try
			{
				install();
				Log.LogInfo((object)("[Features] " + name + ": installed"));
			}
			catch (Exception ex)
			{
				Log.LogError((object)("[Features] " + name + ": NOT installed - " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private void ApplyTweaks()
		{
			(string, Func<IEnumerable<Tweak>>)[] obj = new(string, Func<IEnumerable<Tweak>>)[2]
			{
				("Weapons", WeaponTweaks.All),
				("Projectiles and explosives", ProjectileTweaks.All)
			};
			List<string> list = new List<string>();
			int num = 0;
			(string, Func<IEnumerable<Tweak>>)[] array = obj;
			for (int i = 0; i < array.Length; i++)
			{
				(string, Func<IEnumerable<Tweak>>) tuple = array[i];
				IEnumerable<Tweak> enumerable;
				try
				{
					enumerable = tuple.Item2();
				}
				catch (Exception ex)
				{
					Log.LogError((object)("[Tweaks] group '" + tuple.Item1 + "' failed to build: " + ex.Message));
					list.Add(tuple.Item1 + ": failed to build");
					continue;
				}
				Report val = TweakRunner.Run(tuple.Item1, enumerable, Log);
				num += val.Applied;
				list.AddRange(val.Problems);
			}
			if (list.Count == 0)
			{
				Log.LogInfo((object)$"[Tweaks] TOTAL: {num} applied, no problems");
				return;
			}
			Log.LogWarning((object)$"[Tweaks] TOTAL: {num} applied, {list.Count} problems:");
			foreach (string item in list)
			{
				Log.LogWarning((object)("[Tweaks]   " + item));
			}
		}
	}
}
namespace StraftatCMR.Tweaks
{
	internal static class ProjectileTweaks
	{
		public static IEnumerable<Tweak> All()
		{
			yield return Tweak.Spawned("Shrapnel").Private<ShrapnelBallistic>("damage", (object)1.5f);
			yield return Tweak.Spawned("GlandGrenade").Private<PhysicsGrenade>("explosionRadius", (object)16f).Private<PhysicsGrenade>("numberOfRays", (object)400f);
			yield return Tweak.Spawned("OBUSGROS").OnLayer("Grenade").Private<Obus>("explosionRadius", (object)4f)
				.Private<Obus>("explosionTimer", (object)0.9f)
				.Private<Obus>("timeBeforeExplosion", (object)0.7f);
			yield return Tweak.Spawned("Kanye").OnLayer("Default").Private<PredictedProjectile>("damage", (object)1.4f)
				.Private<PredictedProjectile>("MOVE_RATE", (object)100f);
			yield return Tweak.Spawned("BeamLoadBullet").OnLayer("Default").Private<PredictedProjectile>("damage", (object)999f)
				.Private<PredictedProjectile>("MOVE_RATE", (object)0.5f)
				.Private<PredictedProjectile>("radius", (object)0.5f);
			yield return Tweak.Spawned("ProphetProj").OnLayer("Default").Private<PredictedProjectile>("damage", (object)0.22f)
				.Private<PredictedProjectile>("MOVE_RATE", (object)31f)
				.Private<PredictedProjectile>("maxDistance", (object)67f);
		}
	}
	internal static class WeaponTweaks
	{
		public static IEnumerable<Tweak> All()
		{
			yield return Tweak.Pickup("AAA12").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.currentAmmo = 90;
			}).Rename("AA12");
			yield return Tweak.Pickup("AK-K").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.headMultiplier = 3f;
			}).Rename("Kaka-K3");
			yield return Tweak.Pickup("APMine").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.currentAmmo = 5;
			}).Rename("ghep mine");
			yield return Tweak.Pickup("AR15").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.needsAmmo = false;
				w.headMultiplier = 2f;
				w.damage = 0.5f;
			}).Rename("BLEP GEN 1");
			yield return Tweak.Pickup("BaseballBat").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.movementFactor = 1.2f;
				w.damage = 2f;
			}).Private<MeleeWeapon>("baseAttackDamage", (object)1f)
				.Private<MeleeWeapon>("secondAttackDamage", (object)1f)
				.Rename("tung tung sahur");
			yield return Tweak.Pickup("BeamLoad").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.timeBetweenFire = 1.5f;
			}).Rename("dreamloader");
			yield return Tweak.Pickup("Bender").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 2f;
			}).Pin("our Bender damage is 2, regardless of the developers' balancing");
			yield return Tweak.Pickup("BigFattyBro").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 2.8f;
				w.headMultiplier = 1.57f;
				w.movementFactor = 1.5f;
			}).Private<MeleeWeapon>("baseAttackDamage", (object)2.8f)
				.Private<MeleeWeapon>("secondAttackDamage", (object)2.8f)
				.Rename("buvala");
			yield return Tweak.Pickup("BlankState").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 1.99f;
				w.currentAmmo = 3;
				w.headMultiplier = 1.1f;
				w.timeBetweenFire = 0.7f;
				w.movementFactor = 1.4f;
				w.jumpFactor = 1.2f;
			}).Rename("Blep gen 2 (State)");
			yield return Tweak.Pickup("Claymore").Rename("S.L.A.M.");
			yield return Tweak.Pickup("Couperet").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.timeBetweenFire = 0.125f;
				w.damage = 0.3125f;
				w.onePressShoot = false;
				w.movementFactor = 0.4f;
			}).Private<MeleeWeapon>("baseAttackDamage", (object)0.375f)
				.Private<MeleeWeapon>("secondAttackDamage", (object)0.625f)
				.Private<MeleeWeapon>("timeBetweenBaseAttack", (object)0.125f)
				.Private<MeleeWeapon>("timeBetweenSecondAttack", (object)0.15f)
				.Rename("COQUETA");
			yield return Tweak.Pickup("Crisis").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.currentAmmo = 60;
				w.damage = 0.3f;
				w.timeBetweenFire = 0.05f;
				w.timeBetweenBullets = 0.025f;
			}).Rename("kulana gen 1");
			yield return Tweak.Pickup("DF_Torrent").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 0.8f;
				w.bulletsAmount = 5;
				w.currentAmmo = 30;
				w.timeBetweenBullets = 0.13f;
			}).Rename("qBitTorrent");
			yield return Tweak.Pickup("Dispenser").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 0.9f;
				w.currentAmmo = 32;
			}).Rename("Decimort");
			yield return Tweak.Pickup("DualLauncher").Rename("duck launcher");
			yield return Tweak.Pickup("Elephant").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.onePressShoot = false;
				w.timeBetweenFire = 0.55f;
				w.ammoCharge = 0;
				w.currentAmmo = 10;
				w.damage = 2f;
				w.reloadWeapon = false;
			}).Rename("KSS-ZGETCH");
			yield return Tweak.Pickup("FG42").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 2f;
			}).Rename("kazn gen 2");
			yield return Tweak.Pickup("Gamma").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.movementFactor = 0.7f;
				w.timeBetweenFire = 0.135f;
			}).Rename("Ghep gen 1");
			yield return Tweak.Pickup("GammaGen2").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.timeBetweenFire = 0.07f;
				w.movementFactor = 1.1f;
			}).Rename("Ghep gen 2");
			yield return Tweak.Pickup("GlandGrenade").Rename("Blep Grenade");
			yield return Tweak.Pickup("Glock").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 0.8f;
				w.currentAmmo = 27;
				w.timeBetweenFire = 0.099f;
			}).Rename("de_glock");
			yield return Tweak.Pickup("Gun").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 1.2f;
				w.timeBetweenFire = 0.1595f;
			}).Rename("pistolerus");
			yield return Tweak.Pickup("Gust").Rename("kazn gen 3");
			yield return Tweak.Pickup("HandCanon").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.movementFactor = 0.1f;
			}).Rename("BFG9000");
			yield return Tweak.Pickup("Hill_H15").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 0.4f;
			}).Rename("Hill Coleman");
			yield return Tweak.Pickup("HK_G11").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.onePressShoot = false;
				w.aimBurstGun = false;
				w.timeBetweenBullets = 0.135f;
				w.timeBetweenFire = 0.135f;
				w.damage = 1f;
			}).Rename("ghepk_g11");
			yield return Tweak.Pickup("Kanye").Rename("M.O.M.");
			yield return Tweak.Pickup("Katana").Rename("kuz-yakich");
			yield return Tweak.Pickup("Keso").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.timeBetweenFire = 0.12f;
			}).Rename("machine gun Keso");
			yield return Tweak.Pickup("M2000").Rename("WLEL2000");
			yield return Tweak.Pickup("Mac10").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.currentAmmo = 112;
				w.damage = 0.2f;
				w.timeBetweenFire = 0.01656f;
			}).Rename("kulana gen 2 (firework)");
			yield return Tweak.Pickup("Minigun").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.maxSpread = 0.1f;
				w.minSpread = 0.025f;
				w.damage = 0.6f;
				w.headMultiplier = 1.5f;
			}).PrivateOn("Minigun", "timeBeforeShooting", (object)0.5f)
				.Rename("Bear eating shift");
			yield return Tweak.Pickup("Nizeh").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 2f;
			}).Private<MeleeWeapon>("baseAttackDamage", (object)2f)
				.Private<MeleeWeapon>("secondAttackDamage", (object)4f)
				.Rename("nizehzao");
			yield return Tweak.Pickup("Nugget").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.currentAmmo = 12;
				w.jumpFactor = 3f;
			}).Rename("SERUM");
			yield return Tweak.Pickup("Phoenix").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 0.067f;
				w.currentAmmo = 500;
				w.ammoCharge = 100;
				w.maxWallJumps = 2;
				w.jumpFactor = 1.5f;
				w.movementFactor = 1.05f;
				w.wallJumpFactor = 1.7f;
				w.timeBetweenFire = 0.05f;
			}).Rename("Flamethrower-PHNX223");
			yield return Tweak.Pickup("Prophet").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.timeBetweenFire = 0.48f;
				w.currentAmmo = 20;
				w.onePressShoot = false;
			}).Rename("Natures Prophet");
			yield return Tweak.Pickup("ProximityMine").Rename("set and plant!");
			yield return Tweak.Pickup("QCW05").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 1f;
			}).Pin("our QCW05 damage is 1, regardless of the developers' balancing");
			yield return Tweak.Pickup("Repulsar").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.movementFactor = 1.2f;
				w.jumpFactor = 3f;
			}).Rename("RAILPULSAR");
			yield return Tweak.Pickup("Revolver").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.damage = 2.4f;
			}).Rename("revolvstrel");
			yield return Tweak.Pickup("RocketLauncher").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.movementFactor = 0.8f;
			}).Rename("Quadicabus Launcher");
			yield return Tweak.Pickup("SMG").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.currentAmmo = 29;
				w.damage = 0.6f;
			}).Rename("NG-SMG");
			yield return Tweak.Pickup("SawedOff").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.currentAmmo = 48;
			});
			yield return Tweak.Pickup("Shotgun").PrivateOn("Shotgun", "spread", (object)0.1325f).Rename("gunshot");
			yield return Tweak.Pickup("Silenzzio").Rename("salozao");
			yield return Tweak.Pickup("SmithCarbine").Rename("kazn gen 1");
			yield return Tweak.Pickup("Stylus").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.movementFactor = 1.35f;
				w.jumpFactor = 1.35f;
			}).Rename("Shtylus");
			yield return Tweak.Pickup("Taser").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.jumpFactor = 1.5f;
				w.maxWallJumps = 20;
				w.wallJumpFactor = 3f;
			}).PrivateOn("Taser", "stunTime", (object)1.25f)
				.Rename("RETARZER");
			yield return Tweak.Pickup("Yangtse").Weapon((Action<Weapon>)delegate(Weapon w)
			{
				w.timeBetweenFire = 0.035f;
			}).Rename("Yousuke Yukimatsu");
		}
	}
}
namespace StraftatCMR.Features
{
	[HarmonyPatch(typeof(Claymore))]
	internal static class ClaymoreSounds
	{
		private static readonly string[] BipResources = new string[3] { "StraftatCMR.Audio.ClaymoreBip1.wav", "StraftatCMR.Audio.ClaymoreBip2.wav", "StraftatCMR.Audio.ClaymoreBip3.wav" };

		private static ManualLogSource _log;

		private static readonly List<AudioClip> _bips = new List<AudioClip>();

		private static readonly Random _rng = new Random();

		private static bool _ready;

		private static bool _firstTrapLogged;

		public static void Apply(ManualLogSource log)
		{
			_log = log;
			string[] bipResources = BipResources;
			foreach (string text in bipResources)
			{
				AudioClip val = EmbeddedAudio.Load(Assembly.GetExecutingAssembly(), text, log);
				if ((Object)(object)val != (Object)null)
				{
					_bips.Add(val);
				}
			}
			_ready = _bips.Count > 0;
			_log.LogInfo((object)($"[Claymore] beeps loaded {_bips.Count}/{BipResources.Length}" + (_ready ? "" : " - random sound disabled")));
		}

		[HarmonyPatch("ActivateTrap")]
		[HarmonyPrefix]
		private static void ActivateTrapPrefix(Claymore __instance)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return;
				}
				if (!_firstTrapLogged)
				{
					_firstTrapLogged = true;
					_log.LogInfo((object)("[Claymore] ActivateTrap fired for the first time on " + ((Object)((Component)__instance).gameObject).name + " - the hook is live"));
				}
				if (_ready)
				{
					AudioClip val = _bips[_rng.Next(_bips.Count)];
					AudioClip val2 = Read(__instance);
					if (val2 != val && Write(__instance, val))
					{
						_log.LogInfo((object)("[Claymore] " + ((Object)((Component)__instance).gameObject).name + ": sound " + Name(val2) + " -> " + Name(val)));
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Claymore] ActivateTrap failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static AudioClip Read(Claymore c)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(Claymore), "activationSound");
			if (fieldInfo == null)
			{
				return null;
			}
			try
			{
				object? value = fieldInfo.GetValue(c);
				return (AudioClip)((value is AudioClip) ? value : null);
			}
			catch
			{
				return null;
			}
		}

		private static bool Write(Claymore c, AudioClip clip)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(Claymore), "activationSound");
			if (fieldInfo == null)
			{
				_log.LogError((object)"[Claymore] Claymore.activationSound field is missing - did the game update?");
				return false;
			}
			try
			{
				fieldInfo.SetValue(c, clip);
				return true;
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Claymore] could not write activationSound: " + ex.Message));
				return false;
			}
		}

		private static string Name(AudioClip c)
		{
			if (!((Object)(object)c == (Object)null))
			{
				return ((Object)c).name;
			}
			return "<none>";
		}
	}
	internal static class GammaProjectileSplit
	{
		private const string Gen1Weapon = "Gamma";

		private const string Gen2Weapon = "GammaGen2";

		private const float Gen1Damage = 1.5f;

		private const float Gen2Damage = 0.5f;

		private static readonly Dictionary<string, float> _damageByWeapon = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase)
		{
			{ "Gamma", 1.5f },
			{ "GammaGen2", 0.5f }
		};

		private static ManualLogSource _log;

		private static FieldInfo _projectileField;

		private static FieldInfo _damageField;

		private static bool _ready;

		private static readonly HashSet<string> _seen = new HashSet<string>();

		public static void Apply(ManualLogSource log)
		{
			_log = log;
			_projectileField = AccessTools.Field(AccessTools.TypeByName("DualLauncher"), "_projectile");
			_damageField = AccessTools.Field(typeof(PredictedProjectile), "damage");
			if (_projectileField == null || _damageField == null)
			{
				_log.LogError((object)"[Gamma] DualLauncher._projectile or PredictedProjectile.damage is missing - both gammas will keep sharing one damage value");
				return;
			}
			GameObject val = Single("Gamma");
			GameObject val2 = Single("GammaGen2");
			if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null)
			{
				PrefabRebinder.ReportSharing((IEnumerable<GameObject>)(object)new GameObject[2] { val, val2 }, log);
			}
			ApplyToPrefab(val, 1.5f, "Gamma");
			_ready = true;
			_log.LogInfo((object)string.Format("[Gamma] per-shot damage armed: {0}={1}, {2}={3}", "Gamma", 1.5f, "GammaGen2", 0.5f));
		}

		internal static void BeforeSpawn(object launcher)
		{
			if (!_ready || launcher == null)
			{
				return;
			}
			Component val = (Component)((launcher is Component) ? launcher : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			string text = StripClone(((Object)val.gameObject).name);
			if (!_damageByWeapon.TryGetValue(text, out var value))
			{
				return;
			}
			object value2;
			try
			{
				value2 = _projectileField.GetValue(launcher);
			}
			catch
			{
				return;
			}
			if (value2 != null)
			{
				try
				{
					_damageField.SetValue(value2, value);
				}
				catch (Exception ex)
				{
					_log.LogError((object)("[Gamma] could not set damage: " + ex.Message));
					return;
				}
				if (_seen.Add(text))
				{
					_log.LogInfo((object)("[Gamma] " + text + " fired: projectile damage set to " + FieldSnapshot.Fmt((object)value)));
				}
			}
		}

		private static void ApplyToPrefab(GameObject weapon, float damage, string label)
		{
			if ((Object)(object)weapon == (Object)null)
			{
				return;
			}
			foreach (PrefabRef item in PrefabRebinder.FindExternalRefs(weapon))
			{
				PredictedProjectile componentInChildren = item.TargetRoot.GetComponentInChildren<PredictedProjectile>(true);
				if (!((Object)(object)componentInChildren == (Object)null))
				{
					Dictionary<string, object> dictionary = FieldSnapshot.Take((Component)(object)componentInChildren);
					_damageField.SetValue(componentInChildren, damage);
					var (flag, text) = FieldSnapshot.Diff(dictionary, (Component)(object)componentInChildren);
					_log.LogInfo((object)("[Gamma] " + label + " baseline: " + ((Object)item.TargetRoot).name + " damage -> " + FieldSnapshot.Fmt((object)damage) + " [" + (flag ? text : "pin, value already ours") + "]"));
					break;
				}
			}
		}

		private static GameObject Single(string name)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_001f: Expected O, but got Unknown
			GameObject[] array = PrefabRegistry.Resolve(new PrefabQuery
			{
				Name = name,
				Exact = true,
				Scope = (PrefabScope)1
			});
			if (array.Length == 1)
			{
				return array[0];
			}
			_log.LogWarning((object)$"[Gamma] {name}: {array.Length} matches, expected 1");
			return null;
		}

		private static string StripClone(string name)
		{
			if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, name.Length - "(Clone)".Length);
		}
	}
	[HarmonyPatch]
	internal static class GammaSpawnPatch
	{
		[HarmonyPatch(typeof(DualLauncher), "SpawnProjectile")]
		[HarmonyPrefix]
		private static void Prefix(DualLauncher __instance)
		{
			try
			{
				GammaProjectileSplit.BeforeSpawn(__instance);
			}
			catch (Exception ex)
			{
				StraftatCMRPlugin.Log.LogError((object)("[Gamma] SpawnProjectile prefix failed: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(FirstPersonController))]
	internal static class HKG11FireMode
	{
		private sealed class State
		{
			public NetworkBehaviour Net;

			public Transform PlayerRoot;

			public Weapon Weapon;

			public int LastLookupFrame = -999;

			public bool HasHk;

			public bool IsAiming;

			public bool FastMode;

			public bool Initialised;

			public string LastLoggedSituation;
		}

		public const string WeaponNameFragment = "HK_G11";

		private const string SwitchSoundResource = "StraftatCMR.Audio.SwitchMode.wav";

		private const string PlayerRootName = "PlayerIK(Clone)";

		private const int CacheRefreshFrames = 15;

		private static ManualLogSource _log;

		private static AudioClip _switchSound;

		private static bool _verbose;

		private static readonly Dictionary<int, State> _states = new Dictionary<int, State>();

		public static void Init(ManualLogSource log, bool verbose)
		{
			_log = log;
			_verbose = verbose;
			ModeTextHud.Init(log);
			_switchSound = EmbeddedAudio.Load(Assembly.GetExecutingAssembly(), "StraftatCMR.Audio.SwitchMode.wav", log);
			if ((Object)(object)_switchSound == (Object)null)
			{
				_log.LogWarning((object)"[HK] switch sound failed to load - mode changes will be silent");
			}
			_log.LogInfo((object)("[HK] initialised, verbose log: " + (verbose ? "on" : "off")));
		}

		[HarmonyPatch("Update")]
		[HarmonyPostfix]
		private static void UpdatePostfix(FirstPersonController __instance)
		{
			try
			{
				Tick(__instance);
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[HK] Update failed: " + ex.GetType().Name + ": " + ex.Message + "\n" + ex.StackTrace));
			}
		}

		private static void Tick(FirstPersonController fpc)
		{
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)fpc == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)fpc).GetInstanceID();
			if (!_states.TryGetValue(instanceID, out var value))
			{
				value = new State();
				_states[instanceID] = value;
				if (_verbose)
				{
					_log.LogInfo((object)$"[HK] new FirstPersonController #{instanceID}");
				}
			}
			if ((Object)(object)value.Net == (Object)null)
			{
				value.Net = ((Component)fpc).GetComponent<NetworkBehaviour>();
			}
			if (!((Object)(object)value.Net != (Object)null) || !value.Net.IsClient || !value.Net.IsOwner)
			{
				LogSituationOnce(value, "not our player");
				return;
			}
			RefreshWeaponIfNeeded(fpc, value);
			bool flag = (Object)(object)value.Weapon != (Object)null;
			if (flag != value.HasHk)
			{
				value.HasHk = flag;
				_log.LogInfo((object)(flag ? ("[HK] picked up HK_G11 (" + ((Object)value.Weapon).name + "), mode: " + ModeName(value.FastMode)) : "[HK] HK_G11 put away"));
			}
			if (!flag)
			{
				ModeTextHud.Hide();
				LogSituationOnce(value, "holding something other than the HK");
				return;
			}
			bool isScopeAiming = fpc.isScopeAiming;
			if (!value.Initialised)
			{
				value.Initialised = true;
				value.IsAiming = isScopeAiming;
				ApplyStats(value, isScopeAiming, value.FastMode, "initial application");
			}
			if (isScopeAiming != value.IsAiming)
			{
				value.IsAiming = isScopeAiming;
				ApplyStats(value, isScopeAiming, value.FastMode, isScopeAiming ? "entered aim" : "left aim");
			}
			if (!isScopeAiming && Keyboard.current != null && ((ButtonControl)Keyboard.current.xKey).wasPressedThisFrame)
			{
				value.FastMode = !value.FastMode;
				_log.LogInfo((object)("[HK] === fire mode switched -> " + ModeName(value.FastMode) + " ==="));
				if ((Object)(object)_switchSound != (Object)null)
				{
					AudioSource.PlayClipAtPoint(_switchSound, ((Component)fpc).transform.position, 1f);
				}
				ApplyStats(value, isScopeAiming, value.FastMode, "mode switch");
			}
			ModeTextHud.Set(visible: true, value.FastMode ? "Mode: Single" : "Mode: Auto");
			LogSituationOnce(value, "HK in hand, " + (isScopeAiming ? "aiming" : "hip") + ", " + ModeName(value.FastMode));
		}

		private static void RefreshWeaponIfNeeded(FirstPersonController fpc, State st)
		{
			bool num = (Object)(object)st.Weapon != (Object)null && (Object)(object)((Component)st.Weapon).gameObject != (Object)null && ((Component)st.Weapon).gameObject.activeInHierarchy;
			bool flag = Time.frameCount - st.LastLookupFrame >= 15;
			if (num && !flag)
			{
				return;
			}
			st.LastLookupFrame = Time.frameCount;
			if ((Object)(object)st.PlayerRoot == (Object)null)
			{
				Transform val = ((Component)fpc).transform;
				while ((Object)(object)val != (Object)null && ((Object)val).name != "PlayerIK(Clone)")
				{
					val = val.parent;
				}
				st.PlayerRoot = val;
				if ((Object)(object)val == (Object)null)
				{
					if (_verbose)
					{
						LogSituationOnce(st, "PlayerIK(Clone) not found");
					}
					st.Weapon = null;
					return;
				}
			}
			Weapon componentInChildren = ((Component)st.PlayerRoot).GetComponentInChildren<Weapon>(true);
			if ((Object)(object)componentInChildren == (Object)null || ((Object)componentInChildren).name.IndexOf("HK_G11", StringComparison.OrdinalIgnoreCase) < 0)
			{
				st.Weapon = null;
				return;
			}
			NetworkBehaviour component = ((Component)componentInChildren).GetComponent<NetworkBehaviour>();
			if ((Object)(object)component == (Object)null || !component.IsClient || !component.IsOwner)
			{
				if (_verbose)
				{
					LogSituationOnce(st, "HK found but it is not our instance");
				}
				st.Weapon = null;
			}
			else if (st.Weapon != componentInChildren)
			{
				st.Weapon = componentInChildren;
				st.Initialised = false;
				if (_verbose)
				{
					_log.LogInfo((object)$"[HK] cached instance {((Object)componentInChildren).name} #{((Object)componentInChildren).GetInstanceID()}");
				}
			}
		}

		private static void ApplyStats(State st, bool aiming, bool fastMode, string reason)
		{
			Weapon weapon = st.Weapon;
			if ((Object)(object)weapon == (Object)null)
			{
				_log.LogWarning((object)("[HK] " + reason + ": no weapon, stats not applied"));
				return;
			}
			Dictionary<string, object> dictionary = FieldSnapshot.Take((Component)(object)weapon);
			string text;
			if (aiming)
			{
				text = "AIMING";
				weapon.onePressShoot = false;
				weapon.aimBurstGun = false;
				weapon.timeBetweenBullets = 2f;
				weapon.timeBetweenFire = 2f;
				weapon.damage = 100f;
			}
			else if (fastMode)
			{
				text = "HIP / Single";
				weapon.onePressShoot = true;
				weapon.aimBurstGun = false;
				weapon.timeBetweenBullets = 0.07f;
				weapon.timeBetweenFire = 0.07f;
				weapon.damage = 1.4f;
			}
			else
			{
				text = "HIP / Auto";
				weapon.onePressShoot = false;
				weapon.aimBurstGun = false;
				weapon.timeBetweenBullets = 0.135f;
				weapon.timeBetweenFire = 0.135f;
				weapon.damage = 1f;
			}
			var (flag, text2) = FieldSnapshot.Diff(dictionary, (Component)(object)weapon);
			_log.LogInfo((object)("[HK] " + reason + " -> set " + text + ": " + text2));
			if (!flag)
			{
				_log.LogInfo((object)("[HK]   (values already matched set " + text + ")"));
			}
			_log.LogInfo((object)("[HK]   result: damage=" + FieldSnapshot.Fmt((object)weapon.damage) + ", tbf=" + FieldSnapshot.Fmt((object)weapon.timeBetweenFire) + ", tbb=" + FieldSnapshot.Fmt((object)weapon.timeBetweenBullets) + ", onePressShoot=" + FieldSnapshot.Fmt((object)weapon.onePressShoot) + ", aimBurstGun=" + FieldSnapshot.Fmt((object)weapon.aimBurstGun)));
		}

		private static string ModeName(bool fast)
		{
			if (!fast)
			{
				return "Auto (default)";
			}
			return "Single (fast)";
		}

		private static void LogSituationOnce(State st, string situation)
		{
			if (_verbose && !(st.LastLoggedSituation == situation))
			{
				st.LastLoggedSituation = situation;
				_log.LogInfo((object)("[HK] state: " + situation));
			}
		}
	}
	internal static class MenuText
	{
		private const string StartupMenu = "---USER INTERFACE---/--MAIN MENU--/--STARTUP MENU--";

		public static void Apply(ManualLogSource log, string modVersion)
		{
			TextPatch.At("---USER INTERFACE---/--MAIN MENU--/--STARTUP MENU--/StraftatText").Set("STRAFTAT: CMR");
			TextPatch.At("---USER INTERFACE---/--MAIN MENU--/--STARTUP MENU--/StraftatText (2)").Set("--- press any key to blep ---");
			TextPatch.At("---USER INTERFACE---/--MAIN MENU--/--STARTUP MENU--/version").Transform((Func<string, string>)((string orig) => orig.TrimEnd(Array.Empty<char>()) + "\r\nCMR " + modVersion));
			log.LogInfo((object)("[Menu] text patches queued, mod version " + modVersion));
			log.LogInfo((object)("[Menu] game version per SteamLobby: " + (ReadGameVersion(log) ?? "<unreadable>")));
		}

		public static string ReadGameVersion(ManualLogSource log)
		{
			try
			{
				Type type = AccessTools.TypeByName("SteamLobby");
				if (type == null)
				{
					return null;
				}
				FieldInfo fieldInfo = AccessTools.Field(type, "GameVersion");
				if (fieldInfo == null)
				{
					return null;
				}
				return fieldInfo.GetValue(null) as string;
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("[Menu] could not read the game version: " + ex.Message));
				return null;
			}
		}
	}
	[HarmonyPatch(typeof(ProximityMine))]
	internal static class MineSounds
	{
		private const string GhepResource = "StraftatCMR.Audio.Ghep.wav";

		private static readonly string[] BipResources = new string[3] { "StraftatCMR.Audio.ClaymoreBip1.wav", "StraftatCMR.Audio.ClaymoreBip2.wav", "StraftatCMR.Audio.ClaymoreBip3.wav" };

		private const string ApMinePrefabName = "APMineObject";

		private static ManualLogSource _log;

		private static AudioClip _ghep;

		private static readonly List<AudioClip> _bips = new List<AudioClip>();

		private static readonly Random _rng = new Random();

		private static readonly HashSet<string> _randomBipPrefabs = new HashSet<string>();

		private static readonly HashSet<string> _seenAtStart = new HashSet<string>();

		private static bool _ready;

		public static void Apply(ManualLogSource log)
		{
			_log = log;
			LoadClips();
			ClassifyPrefabs();
			ApplyGhepToApMinePrefab();
			_ready = _bips.Count > 0;
			if (!_ready)
			{
				_log.LogWarning((object)"[Mines] no beeps loaded - random sounds disabled");
			}
		}

		private static void LoadClips()
		{
			_ghep = EmbeddedAudio.Load(Assembly.GetExecutingAssembly(), "StraftatCMR.Audio.Ghep.wav", _log);
			string[] bipResources = BipResources;
			foreach (string text in bipResources)
			{
				AudioClip val = EmbeddedAudio.Load(Assembly.GetExecutingAssembly(), text, _log);
				if ((Object)(object)val != (Object)null)
				{
					_bips.Add(val);
				}
			}
			_log.LogInfo((object)("[Mines] sounds: Ghep=" + (((Object)(object)_ghep != (Object)null) ? "ok" : "MISSING") + ", " + $"beeps {_bips.Count}/{BipResources.Length}"));
		}

		private static void ClassifyPrefabs()
		{
			GameObject[] array = PrefabRegistry.Spawned.Where((GameObject go) => (Object)(object)go != (Object)null && (Object)(object)go.GetComponentInChildren<ProximityMine>(true) != (Object)null).ToArray();
			GameObject[] array2 = array;
			foreach (GameObject val in array2)
			{
				bool flag = string.Equals(((Object)val).name, "APMineObject", StringComparison.OrdinalIgnoreCase);
				if (!flag)
				{
					_randomBipPrefabs.Add(((Object)val).name);
				}
				_log.LogInfo((object)($"[Mines] {((Object)val).name}#{((Object)val).GetInstanceID()} tag='{val.tag}' -> " + (flag ? "own Ghep sound" : "random beep")));
			}
			if (array.Length == 0)
			{
				_log.LogWarning((object)"[Mines] no prefabs with ProximityMine found");
			}
		}

		private static void ApplyGhepToApMinePrefab()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			if ((Object)(object)_ghep == (Object)null)
			{
				_log.LogWarning((object)"[Mines] Ghep.wav not loaded - the AP mine keeps the game's sound");
				return;
			}
			GameObject[] array = PrefabRegistry.Resolve(new PrefabQuery
			{
				Name = "APMineObject",
				Exact = true,
				Scope = (PrefabScope)2
			});
			if (array.Length != 1)
			{
				_log.LogWarning((object)string.Format("[Mines] {0}: {1} matches, expected 1 - sound not applied", "APMineObject", array.Length));
				return;
			}
			ProximityMine componentInChildren = array[0].GetComponentInChildren<ProximityMine>(true);
			if ((Object)(object)componentInChildren == (Object)null)
			{
				_log.LogWarning((object)"[Mines] APMineObject has no ProximityMine");
				return;
			}
			AudioClip c = ReadSound(componentInChildren);
			if (WriteSound(componentInChildren, _ghep))
			{
				_log.LogInfo((object)("[Mines] APMineObject: activationSound " + Name(c) + " -> " + Name(_ghep) + " (into the prefab, once)"));
			}
		}

		[HarmonyPatch("Start")]
		[HarmonyPrefix]
		private static void StartPrefix(ProximityMine __instance)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null)
				{
					return;
				}
				string text = StripClone(((Object)((Component)__instance).gameObject).name);
				if (_seenAtStart.Add(text))
				{
					_log.LogInfo((object)("[Mines] Start fired on " + ((Object)((Component)__instance).gameObject).name + " " + $"(prefab {text}), in the random list: {_randomBipPrefabs.Contains(text)}"));
				}
				if (_ready && _randomBipPrefabs.Contains(text))
				{
					AudioClip val = _bips[_rng.Next(_bips.Count)];
					AudioClip val2 = ReadSound(__instance);
					if (val2 != val && WriteSound(__instance, val))
					{
						_log.LogInfo((object)("[Mines] " + ((Object)((Component)__instance).gameObject).name + ": beep " + Name(val2) + " -> " + Name(val)));
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Mines] Start failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static AudioClip ReadSound(ProximityMine mine)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(ProximityMine), "activationSound");
			if (fieldInfo == null)
			{
				return null;
			}
			try
			{
				object? value = fieldInfo.GetValue(mine);
				return (AudioClip)((value is AudioClip) ? value : null);
			}
			catch
			{
				return null;
			}
		}

		private static bool WriteSound(ProximityMine mine, AudioClip clip)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(ProximityMine), "activationSound");
			if (fieldInfo == null)
			{
				_log.LogError((object)"[Mines] ProximityMine.activationSound field is missing - did the game update?");
				return false;
			}
			try
			{
				fieldInfo.SetValue(mine, clip);
				return true;
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Mines] could not write activationSound: " + ex.Message));
				return false;
			}
		}

		private static string StripClone(string name)
		{
			if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, name.Length - "(Clone)".Length);
		}

		private static string Name(AudioClip c)
		{
			if (!((Object)(object)c == (Object)null))
			{
				return ((Object)c).name;
			}
			return "<none>";
		}
	}
	internal static class ModeTextHud
	{
		private const string UiRootName = "---USER INTERFACE---";

		private const string MinimalUiName = "---MINIMAL UI---";

		private const string SourceName = "HealthDisplay";

		private const string HudName = "CMR_ModeText";

		private static ManualLogSource _log;

		private static GameObject _root;

		private static TMP_Text _text;

		private static string _lastText;

		private static bool _lastActive;

		private static int _lastAttemptFrame = -1;

		public static void Init(ManualLogSource log)
		{
			_log = log;
		}

		public static void Set(bool visible, string text)
		{
			if (!EnsureCreated())
			{
				return;
			}
			if (_root.activeSelf != visible)
			{
				_root.SetActive(visible);
				if (_lastActive != visible)
				{
					_lastActive = visible;
					_log.LogInfo((object)("[HUD] indicator " + (visible ? "shown" : "hidden")));
				}
			}
			if (visible && !((Object)(object)_text == (Object)null) && _text.text != text)
			{
				_text.text = text;
				if (_lastText != text)
				{
					_lastText = text;
					_log.LogInfo((object)("[HUD] text: \"" + text + "\""));
				}
			}
		}

		public static void Hide()
		{
			if (!((Object)(object)_root == (Object)null) && _root.activeSelf)
			{
				_root.SetActive(false);
				_lastActive = false;
				_log.LogInfo((object)"[HUD] indicator hidden");
			}
		}

		private static bool EnsureCreated()
		{
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: 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_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_root != (Object)null)
			{
				return true;
			}
			if (_lastAttemptFrame >= 0 && Time.frameCount - _lastAttemptFrame < 30)
			{
				return false;
			}
			_lastAttemptFrame = Time.frameCount;
			Transform val = FindRoot("---USER INTERFACE---");
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Transform val2 = FindDescendant(val, "---MINIMAL UI---");
			if ((Object)(object)val2 == (Object)null)
			{
				_log.LogWarning((object)"[HUD] ---MINIMAL UI--- not found - there will be no indicator");
				return false;
			}
			Transform val3 = FindDescendant(val2, "HealthDisplay");
			if ((Object)(object)val3 == (Object)null)
			{
				_log.LogWarning((object)"[HUD] HealthDisplay not found, nothing to clone");
				return false;
			}
			GameObject val4 = Object.Instantiate<GameObject>(((Component)val3).gameObject);
			((Object)val4).name = "CMR_ModeText";
			val4.transform.SetParent(val, false);
			RectTransform component = val4.GetComponent<RectTransform>();
			if ((Object)(object)component != (Object)null)
			{
				component.anchorMin = new Vector2(1f, 1f);
				component.anchorMax = new Vector2(1f, 1f);
				component.pivot = new Vector2(1f, 1f);
				component.anchoredPosition = new Vector2(-1613.105f, -442.4104f);
				component.sizeDelta = new Vector2(200f, 30f);
			}
			else
			{
				_log.LogWarning((object)"[HUD] clone has no RectTransform - position stays as in the original");
			}
			Transform val5 = FindDescendant(val4.transform, "text");
			_text = (((Object)(object)val5 != (Object)null) ? ((Component)val5).GetComponent<TMP_Text>() : null);
			if ((Object)(object)_text == (Object)null)
			{
				_text = val4.GetComponentInChildren<TMP_Text>(true);
				if ((Object)(object)_text != (Object)null)
				{
					_log.LogInfo((object)"[HUD] child 'text' not found, using the first TMP_Text in the clone");
				}
			}
			if ((Object)(object)_text == (Object)null)
			{
				_log.LogWarning((object)"[HUD] no TMP_Text found - the indicator will be blank");
			}
			val4.SetActive(false);
			_root = val4;
			_lastActive = false;
			_log.LogInfo((object)("[HUD] CMR_ModeText created (clone of HealthDisplay), position " + (((Object)(object)component != (Object)null) ? ((object)component.anchoredPosition/*cast due to .constrained prefix*/).ToString() : "as in the original")));
			return true;
		}

		private static Transform FindRoot(string name)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < SceneManager.sceneCount; i++)
			{
				Scene sceneAt = SceneManager.GetSceneAt(i);
				if (!((Scene)(ref sceneAt)).isLoaded)
				{
					continue;
				}
				GameObject[] rootGameObjects = ((Scene)(ref sceneAt)).GetRootGameObjects();
				foreach (GameObject val in rootGameObjects)
				{
					if (((Object)val).name == name)
					{
						return val.transform;
					}
				}
			}
			GameObject val2 = GameObject.Find(name);
			if (!((Object)(object)val2 != (Object)null))
			{
				return null;
			}
			return val2.transform;
		}

		private static Transform FindDescendant(Transform parent, string name)
		{
			if ((Object)(object)parent == (Object)null)
			{
				return null;
			}
			Queue<Transform> queue = new Queue<Transform>();
			queue.Enqueue(parent);
			while (queue.Count > 0)
			{
				Transform val = queue.Dequeue();
				if (((Object)val).name == name)
				{
					return val;
				}
				for (int i = 0; i < val.childCount; i++)
				{
					queue.Enqueue(val.GetChild(i));
				}
			}
			return null;
		}
	}
	public struct FlashlightToggleBroadcast : IBroadcast
	{
		public int ObjectId;

		public bool IsOn;
	}
	[HarmonyPatch(typeof(FirstPersonController))]
	internal static class WardenFlashlight
	{
		private sealed class State
		{
			public NetworkBehaviour Net;

			public Weapon Warden;

			public Transform Light;

			public int LastLookupFrame = -999;

			public bool HadWarden;
		}

		private const string WeaponNameFragment = "warden";

		private const string LightObjectName = "Spot Light";

		private const int CacheRefreshFrames = 15;

		private static ManualLogSource _log;

		private static bool _verbose;

		private static bool _registered;

		private static bool _serializersSet;

		private static int _lastRegisterAttempt = -999;

		private static readonly Dictionary<int, State> _states = new Dictionary<int, State>();

		public static void Init(ManualLogSource log, bool verbose)
		{
			_log = log;
			_verbose = verbose;
			GenericWriter<FlashlightToggleBroadcast>.Write = Write;
			GenericReader<FlashlightToggleBroadcast>.Read = Read;
			_serializersSet = true;
			_log.LogInfo((object)"[Flashlight] serializers assigned, waiting for the network managers");
		}

		private static void TryRegister()
		{
			if (!_registered && _serializersSet && Time.frameCount - _lastRegisterAttempt >= 60)
			{
				_lastRegisterAttempt = Time.frameCount;
				bool flag = false;
				if ((Object)(object)InstanceFinder.ClientManager != (Object)null)
				{
					InstanceFinder.ClientManager.RegisterBroadcast<FlashlightToggleBroadcast>((Action<FlashlightToggleBroadcast>)OnClientToggle);
					_log.LogInfo((object)"[Flashlight] client broadcast registered");
					flag = true;
				}
				if ((Object)(object)InstanceFinder.ServerManager != (Object)null)
				{
					InstanceFinder.ServerManager.RegisterBroadcast<FlashlightToggleBroadcast>((Action<NetworkConnection, FlashlightToggleBroadcast>)OnServerToggle, true);
					_log.LogInfo((object)"[Flashlight] server broadcast registered");
					flag = true;
				}
				if (flag)
				{
					_registered = true;
				}
			}
		}

		private static void OnServerToggle(NetworkConnection conn, FlashlightToggleBroadcast msg)
		{
			if (!((Object)(object)InstanceFinder.ServerManager == (Object)null))
			{
				if (conn != (NetworkConnection)null && !OwnsObject(conn, msg.ObjectId))
				{
					_log.LogWarning((object)$"[Flashlight] client {conn.ClientId} is toggling a foreign object {msg.ObjectId}");
				}
				InstanceFinder.ServerManager.Broadcast<FlashlightToggleBroadcast>(msg, true, (Channel)0);
			}
		}

		private static bool OwnsObject(NetworkConnection conn, int objectId)
		{
			try
			{
				if (((ManagedObjects)InstanceFinder.ServerManager.Objects).Spawned.TryGetValue(objectId, out var value))
				{
					return (Object)(object)value != (Object)null && value.Owner == conn;
				}
			}
			catch
			{
			}
			return true;
		}

		private static void OnClientToggle(FlashlightToggleBroadcast msg)
		{
			try
			{
				ApplyRemote(msg.ObjectId, msg.IsOn);
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Flashlight] receive failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void ApplyRemote(int objectId, bool isOn)
		{
			if ((Object)(object)InstanceFinder.ClientManager == (Object)null)
			{
				return;
			}
			if (!((ManagedObjects)InstanceFinder.ClientManager.Objects).Spawned.TryGetValue(objectId, out var value) || (Object)(object)value == (Object)null)
			{
				if (_verbose)
				{
					_log.LogInfo((object)$"[Flashlight] object {objectId} not found on this client");
				}
				return;
			}
			Transform val = FindLight(((Component)value).transform);
			if ((Object)(object)val == (Object)null)
			{
				if (_verbose)
				{
					_log.LogInfo((object)string.Format("[Flashlight] object {0} has no '{1}'", objectId, "Spot Light"));
				}
				return;
			}
			((Component)val).gameObject.SetActive(isOn);
			if (_verbose)
			{
				_log.LogInfo((object)string.Format("[Flashlight] remote {0} -> {1}", objectId, isOn ? "on" : "off"));
			}
		}

		[HarmonyPatch("Update")]
		[HarmonyPostfix]
		private static void UpdatePostfix(FirstPersonController __instance)
		{
			try
			{
				Tick(__instance);
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Flashlight] Update failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void Tick(FirstPersonController fpc)
		{
			if ((Object)(object)fpc == (Object)null)
			{
				return;
			}
			TryRegister();
			int instanceID = ((Object)fpc).GetInstanceID();
			if (!_states.TryGetValue(instanceID, out var value))
			{
				value = (_states[instanceID] = new State());
			}
			if ((Object)(object)value.Net == (Object)null)
			{
				value.Net = ((Component)fpc).GetComponent<NetworkBehaviour>();
			}
			if (!((Object)(object)value.Net == (Object)null) && value.Net.IsClient && value.Net.IsOwner)
			{
				RefreshCache(fpc, value);
				bool flag = (Object)(object)value.Warden != (Object)null;
				if (flag != value.HadWarden)
				{
					value.HadWarden = flag;
					_log.LogInfo((object)(flag ? ("[Flashlight] picked up Warden (" + ((Object)value.Warden).name + "), light: " + (((Object)(object)value.Light != (Object)null) ? "found" : "NOT found")) : "[Flashlight] Warden put away"));
				}
				if (flag && !((Object)(object)value.Light == (Object)null) && Keyboard.current != null && ((ButtonControl)Keyboard.current.xKey).wasPressedThisFrame)
				{
					bool flag2 = !((Component)value.Light).gameObject.activeSelf;
					((Component)value.Light).gameObject.SetActive(flag2);
					_log.LogInfo((object)("[Flashlight] === toggled -> " + (flag2 ? "ON" : "OFF") + " ==="));
					Send(value.Net.ObjectId, flag2);
				}
			}
		}

		private static void Send(int objectId, bool isOn)
		{
			if ((Object)(object)InstanceFinder.ClientManager == (Object)null || !InstanceFinder.ClientManager.Connection.IsActive)
			{
				_log.LogWarning((object)"[Flashlight] no client connection - the state stayed local");
				return;
			}
			InstanceFinder.ClientManager.Broadcast<FlashlightToggleBroadcast>(new FlashlightToggleBroadcast
			{
				ObjectId = objectId,
				IsOn = isOn
			}, (Channel)0);
			if (_verbose)
			{
				_log.LogInfo((object)$"[Flashlight] sent: object {objectId} -> {isOn}");
			}
		}

		private static void RefreshCache(FirstPersonController fpc, State st)
		{
			if ((Object)(object)st.Warden != (Object)null && (Object)(object)((Component)st.Warden).gameObject != (Object)null && ((Component)st.Warden).gameObject.activeInHierarchy && (Object)(object)st.Light != (Object)null && Time.frameCount - st.LastLookupFrame < 15)
			{
				return;
			}
			st.LastLookupFrame = Time.frameCount;
			st.Warden = null;
			st.Light = null;
			Weapon[] componentsInChildren = ((Component)fpc).GetComponentsInChildren<Weapon>(true);
			foreach (Weapon val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy && ((Object)val).name.IndexOf("warden", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					st.Warden = val;
					st.Light = FindLight(((Component)val).transform);
					break;
				}
			}
		}

		private static Transform FindLight(Transform root)
		{
			Transform[] componentsInChildren = ((Component)root).GetComponentsInChildren<Transform>(true);
			foreach (Transform val in componentsInChildren)
			{
				if (((Object)val).name == "Spot Light")
				{
					return val;
				}
			}
			return null;
		}

		public static void Write(Writer writer, FlashlightToggleBroadcast value)
		{
			writer.WriteInt32(value.ObjectId, (AutoPackType)1);
			writer.WriteBoolean(value.IsOn);
		}

		public static FlashlightToggleBroadcast Read(Reader reader)
		{
			return new FlashlightToggleBroadcast
			{
				ObjectId = reader.ReadInt32((AutoPackType)1),
				IsOn = reader.ReadBoolean()
			};
		}
	}
	internal static class WeaponSounds
	{
		private sealed class SoundSet
		{
			public string Prefab;

			public string[] FireClips;

			public string[] MeleeSecondClips;

			public bool IsFixed
			{
				get
				{
					if (FireClips != null)
					{
						return FireClips.Length == 1;
					}
					return false;
				}
			}
		}

		private const string Res = "StraftatCMR.Audio.";

		private static readonly SoundSet[] Sets = new SoundSet[3]
		{
			new SoundSet
			{
				Prefab = "Phoenix",
				FireClips = new string[1] { "StraftatCMR.Audio.PhoenixNew.wav" }
			},
			new SoundSet
			{
				Prefab = "BlankState",
				FireClips = (from i in Enumerable.Range(1, 4)
					select string.Format("{0}BlepState{1}.wav", "StraftatCMR.Audio.", i)).ToArray()
			},
			new SoundSet
			{
				Prefab = "BaseballBat",
				FireClips = (from i in Enumerable.Range(1, 8)
					select string.Format("{0}Tung{1}.wav", "StraftatCMR.Audio.", i)).ToArray(),
				MeleeSecondClips = (from i in Enumerable.Range(1, 8)
					select string.Format("{0}Tung{1}.wav", "StraftatCMR.Audio.", i)).ToArray()
			}
		};

		private static ManualLogSource _log;

		private static readonly Random _rng = new Random();

		private static readonly Dictionary<string, List<AudioClip>> _fire = new Dictionary<string, List<AudioClip>>();

		private static readonly Dictionary<string, List<AudioClip>> _melee = new Dictionary<string, List<AudioClip>>();

		private static readonly HashSet<string> _seenShoot = new HashSet<string>();

		private static readonly HashSet<string> _seenMelee = new HashSet<string>();

		private static readonly HashSet<string> _seenAwake = new HashSet<string>();

		public static void Apply(ManualLogSource log)
		{
			_log = log;
			SoundSet[] sets = Sets;
			foreach (SoundSet soundSet in sets)
			{
				List<AudioClip> list = LoadAll(soundSet.FireClips);
				if (list.Count == 0)
				{
					_log.LogWarning((object)("[Sounds] " + soundSet.Prefab + ": no clips loaded - skipped"));
					continue;
				}
				if (soundSet.IsFixed)
				{
					AssignToPrefab(soundSet.Prefab, list[0]);
				}
				else
				{
					_fire[soundSet.Prefab] = list;
					_log.LogInfo((object)$"[Sounds] {soundSet.Prefab}: fireClip rotation, {list.Count} clips");
					AssignToPrefab(soundSet.Prefab, list[0]);
				}
				if (soundSet.MeleeSecondClips != null)
				{
					List<AudioClip> list2 = LoadAll(soundSet.MeleeSecondClips);
					if (list2.Count > 0)
					{
						_melee[soundSet.Prefab] = list2;
						_log.LogInfo((object)$"[Sounds] {soundSet.Prefab}: secondAttackClip rotation, {list2.Count} clips");
					}
				}
			}
		}

		private static List<AudioClip> LoadAll(string[] resources)
		{
			List<AudioClip> list = new List<AudioClip>();
			if (resources == null)
			{
				return list;
			}
			foreach (string text in resources)
			{
				AudioClip val = EmbeddedAudio.Load(Assembly.GetExecutingAssembly(), text, _log);
				if ((Object)(object)val != (Object)null)
				{
					list.Add(val);
				}
			}
			return list;
		}

		private static void AssignToPrefab(string prefabName, AudioClip clip)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_001f: Expected O, but got Unknown
			GameObject[] array = PrefabRegistry.Resolve(new PrefabQuery
			{
				Name = prefabName,
				Exact = true,
				Scope = (PrefabScope)1
			});
			if (array.Length != 1)
			{
				_log.LogWarning((object)$"[Sounds] {prefabName}: {array.Length} matches, expected 1 - sound not applied");
				return;
			}
			Weapon componentInChildren = array[0].GetComponentInChildren<Weapon>(true);
			if ((Object)(object)componentInChildren == (Object)null)
			{
				_log.LogWarning((object)("[Sounds] " + prefabName + ": no Weapon component"));
				return;
			}
			AudioClip fireClip = componentInChildren.fireClip;
			componentInChildren.fireClip = clip;
			_log.LogInfo((object)("[Sounds] " + prefabName + ": fireClip " + Name(fireClip) + " -> " + Name(clip) + " (into the prefab, once)"));
		}

		internal static void OnWeaponAwake(Weapon weapon)
		{
			if ((Object)(object)weapon == (Object)null)
			{
				return;
			}
			string text = StripClone(((Object)((Component)weapon).gameObject).name);
			if (_fire.TryGetValue(text, out var value))
			{
				weapon.fireClip = value[_rng.Next(value.Count)];
			}
			if (_melee.TryGetValue(text, out var value2))
			{
				MeleeWeapon val = (MeleeWeapon)(object)((weapon is MeleeWeapon) ? weapon : null);
				if (val != null)
				{
					SetPrivateClip(val, "secondAttackClip", value2[_rng.Next(value2.Count)]);
				}
			}
			if (_seenAwake.Add(text) && (_fire.ContainsKey(text) || _melee.ContainsKey(text)))
			{
				_log.LogInfo((object)("[Sounds] Awake: " + ((Object)((Component)weapon).gameObject).name + " received clips (fireClip=" + Name(weapon.fireClip) + ")"));
			}
		}

		internal static void OnShoot(Weapon weapon)
		{
			if ((Object)(object)weapon == (Object)null)
			{
				return;
			}
			string text = StripClone(((Object)((Component)weapon).gameObject).name);
			if (_seenShoot.Add(text))
			{
				_log.LogInfo((object)("[Sounds] first OnShoot: " + ((Object)((Component)weapon).gameObject).name + " " + $"(prefab {text}), rotation: {_fire.ContainsKey(text)}"));
			}
			if (_fire.TryGetValue(text, out var value))
			{
				AudioClip val = value[_rng.Next(value.Count)];
				if (weapon.fireClip != val)
				{
					weapon.fireClip = val;
				}
			}
		}

		internal static void OnMeleeFire(MeleeWeapon melee)
		{
			if ((Object)(object)melee == (Object)null)
			{
				return;
			}
			string text = StripClone(((Object)((Component)melee).gameObject).name);
			if (_seenMelee.Add(text))
			{
				_log.LogInfo((object)("[Sounds] first MeleeWeapon.Fire: " + ((Object)((Component)melee).gameObject).name + " " + $"(prefab {text}), rotation: {_melee.ContainsKey(text)}"));
				DumpMeleeClips(melee);
			}
			if (_melee.TryGetValue(text, out var value))
			{
				AudioClip val = value[_rng.Next(value.Count)];
				if (((Weapon)melee).fireClip != val)
				{
					((Weapon)melee).fireClip = val;
				}
				SetPrivateClip(melee, "secondAttackClip", value[_rng.Next(value.Count)]);
			}
		}

		private static void DumpMeleeClips(MeleeWeapon melee)
		{
			string[] obj = new string[3] { "secondAttackClip", "firstAttackStartClip", "secondAttackStartClip" };
			_log.LogInfo((object)("[Sounds]   fireClip = " + Name(((Weapon)melee).fireClip)));
			string[] array = obj;
			foreach (string text in array)
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(MeleeWeapon), text);
				if (fieldInfo == null)
				{
					_log.LogInfo((object)("[Sounds]   " + text + " = <no such field>"));
					continue;
				}
				try
				{
					? val = _log;
					object? value = fieldInfo.GetValue(melee);
					((ManualLogSource)val).LogInfo((object)("[Sounds]   " + text + " = " + Name((AudioClip)((value is AudioClip) ? value : null))));
				}
				catch
				{
					_log.LogInfo((object)("[Sounds]   " + text + " = <unreadable>"));
				}
			}
		}

		private static void SetPrivateClip(MeleeWeapon melee, string field, AudioClip clip)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(MeleeWeapon), field);
			if (fieldInfo == null)
			{
				_log.LogError((object)("[Sounds] MeleeWeapon." + field + " field is missing"));
				return;
			}
			try
			{
				fieldInfo.SetValue(melee, clip);
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Sounds] " + field + ": " + ex.Message));
			}
		}

		private static string StripClone(string name)
		{
			if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, name.Length - "(Clone)".Length);
		}

		private static string Name(AudioClip c)
		{
			if (!((Object)(object)c == (Object)null))
			{
				return ((Object)c).name;
			}
			return "<none>";
		}
	}
	[HarmonyPatch(typeof(Weapon))]
	internal static class WeaponSoundsShootPatch
	{
		[HarmonyPatch("OnShoot")]
		[HarmonyPrefix]
		private static void Prefix(Weapon __instance)
		{
			try
			{
				WeaponSounds.OnShoot(__instance);
			}
			catch (Exception ex)
			{
				StraftatCMRPlugin.Log.LogError((object)("[Sounds] OnShoot failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Weapon))]
	internal static class WeaponSoundsAwakePatch
	{
		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		private static void Postfix(Weapon __instance)
		{
			try
			{
				WeaponSounds.OnWeaponAwake(__instance);
			}
			catch (Exception ex)
			{
				StraftatCMRPlugin.Log.LogError((object)("[Sounds] Weapon.Awake failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(MeleeWeapon))]
	internal static class MeleeSoundsAwakePatch
	{
		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		private static void Postfix(MeleeWeapon __instance)
		{
			try
			{
				WeaponSounds.OnWeaponAwake((Weapon)(object)__instance);
			}
			catch (Exception ex)
			{
				StraftatCMRPlugin.Log.LogError((object)("[Sounds] MeleeWeapon.Awake failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(MeleeWeapon))]
	internal static class WeaponSoundsMeleePatch
	{
		[HarmonyPatch("Fire")]
		[HarmonyPrefix]
		private static void Prefix(MeleeWeapon __instance)
		{
			try
			{
				WeaponSounds.OnMeleeFire(__instance);
			}
			catch (Exception ex)
			{
				StraftatCMRPlugin.Log.LogError((object)("[Sounds] MeleeWeapon.Fire failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
}