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 HkVerbose { get; private set; } public static ConfigEntry WardenVerbose { get; private set; } public static void Bind(ConfigFile cfg) { HkVerbose = cfg.Bind("HK_G11", "VerboseLog", false, "Debug logging."); WardenVerbose = cfg.Bind("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>)[] obj = new(string, Func>)[2] { ("Weapons", WeaponTweaks.All), ("Projectiles and explosives", ProjectileTweaks.All) }; List list = new List(); int num = 0; (string, Func>)[] array = obj; for (int i = 0; i < array.Length; i++) { (string, Func>) tuple = array[i]; IEnumerable 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 All() { yield return Tweak.Spawned("Shrapnel").Private("damage", (object)1.5f); yield return Tweak.Spawned("GlandGrenade").Private("explosionRadius", (object)16f).Private("numberOfRays", (object)400f); yield return Tweak.Spawned("OBUSGROS").OnLayer("Grenade").Private("explosionRadius", (object)4f) .Private("explosionTimer", (object)0.9f) .Private("timeBeforeExplosion", (object)0.7f); yield return Tweak.Spawned("Kanye").OnLayer("Default").Private("damage", (object)1.4f) .Private("MOVE_RATE", (object)100f); yield return Tweak.Spawned("BeamLoadBullet").OnLayer("Default").Private("damage", (object)999f) .Private("MOVE_RATE", (object)0.5f) .Private("radius", (object)0.5f); yield return Tweak.Spawned("ProphetProj").OnLayer("Default").Private("damage", (object)0.22f) .Private("MOVE_RATE", (object)31f) .Private("maxDistance", (object)67f); } } internal static class WeaponTweaks { public static IEnumerable All() { yield return Tweak.Pickup("AAA12").Weapon((Action)delegate(Weapon w) { w.currentAmmo = 90; }).Rename("AA12"); yield return Tweak.Pickup("AK-K").Weapon((Action)delegate(Weapon w) { w.headMultiplier = 3f; }).Rename("Kaka-K3"); yield return Tweak.Pickup("APMine").Weapon((Action)delegate(Weapon w) { w.currentAmmo = 5; }).Rename("ghep mine"); yield return Tweak.Pickup("AR15").Weapon((Action)delegate(Weapon w) { w.needsAmmo = false; w.headMultiplier = 2f; w.damage = 0.5f; }).Rename("BLEP GEN 1"); yield return Tweak.Pickup("BaseballBat").Weapon((Action)delegate(Weapon w) { w.movementFactor = 1.2f; w.damage = 2f; }).Private("baseAttackDamage", (object)1f) .Private("secondAttackDamage", (object)1f) .Rename("tung tung sahur"); yield return Tweak.Pickup("BeamLoad").Weapon((Action)delegate(Weapon w) { w.timeBetweenFire = 1.5f; }).Rename("dreamloader"); yield return Tweak.Pickup("Bender").Weapon((Action)delegate(Weapon w) { w.damage = 2f; }).Pin("our Bender damage is 2, regardless of the developers' balancing"); yield return Tweak.Pickup("BigFattyBro").Weapon((Action)delegate(Weapon w) { w.damage = 2.8f; w.headMultiplier = 1.57f; w.movementFactor = 1.5f; }).Private("baseAttackDamage", (object)2.8f) .Private("secondAttackDamage", (object)2.8f) .Rename("buvala"); yield return Tweak.Pickup("BlankState").Weapon((Action)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)delegate(Weapon w) { w.timeBetweenFire = 0.125f; w.damage = 0.3125f; w.onePressShoot = false; w.movementFactor = 0.4f; }).Private("baseAttackDamage", (object)0.375f) .Private("secondAttackDamage", (object)0.625f) .Private("timeBetweenBaseAttack", (object)0.125f) .Private("timeBetweenSecondAttack", (object)0.15f) .Rename("COQUETA"); yield return Tweak.Pickup("Crisis").Weapon((Action)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)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)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)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)delegate(Weapon w) { w.damage = 2f; }).Rename("kazn gen 2"); yield return Tweak.Pickup("Gamma").Weapon((Action)delegate(Weapon w) { w.movementFactor = 0.7f; w.timeBetweenFire = 0.135f; }).Rename("Ghep gen 1"); yield return Tweak.Pickup("GammaGen2").Weapon((Action)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)delegate(Weapon w) { w.damage = 0.8f; w.currentAmmo = 27; w.timeBetweenFire = 0.099f; }).Rename("de_glock"); yield return Tweak.Pickup("Gun").Weapon((Action)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)delegate(Weapon w) { w.movementFactor = 0.1f; }).Rename("BFG9000"); yield return Tweak.Pickup("Hill_H15").Weapon((Action)delegate(Weapon w) { w.damage = 0.4f; }).Rename("Hill Coleman"); yield return Tweak.Pickup("HK_G11").Weapon((Action)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)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)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)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)delegate(Weapon w) { w.damage = 2f; }).Private("baseAttackDamage", (object)2f) .Private("secondAttackDamage", (object)4f) .Rename("nizehzao"); yield return Tweak.Pickup("Nugget").Weapon((Action)delegate(Weapon w) { w.currentAmmo = 12; w.jumpFactor = 3f; }).Rename("SERUM"); yield return Tweak.Pickup("Phoenix").Weapon((Action)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)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)delegate(Weapon w) { w.damage = 1f; }).Pin("our QCW05 damage is 1, regardless of the developers' balancing"); yield return Tweak.Pickup("Repulsar").Weapon((Action)delegate(Weapon w) { w.movementFactor = 1.2f; w.jumpFactor = 3f; }).Rename("RAILPULSAR"); yield return Tweak.Pickup("Revolver").Weapon((Action)delegate(Weapon w) { w.damage = 2.4f; }).Rename("revolvstrel"); yield return Tweak.Pickup("RocketLauncher").Weapon((Action)delegate(Weapon w) { w.movementFactor = 0.8f; }).Rename("Quadicabus Launcher"); yield return Tweak.Pickup("SMG").Weapon((Action)delegate(Weapon w) { w.currentAmmo = 29; w.damage = 0.6f; }).Rename("NG-SMG"); yield return Tweak.Pickup("SawedOff").Weapon((Action)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)delegate(Weapon w) { w.movementFactor = 1.35f; w.jumpFactor = 1.35f; }).Rename("Shtylus"); yield return Tweak.Pickup("Taser").Weapon((Action)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)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 _bips = new List(); 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 ""; } } 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 _damageByWeapon = new Dictionary(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 _seen = new HashSet(); 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)(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(true); if (!((Object)(object)componentInChildren == (Object)null)) { Dictionary 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 _states = new Dictionary(); 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(); } 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(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(); 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 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 orig) => orig.TrimEnd(Array.Empty()) + "\r\nCMR " + modVersion)); log.LogInfo((object)("[Menu] text patches queued, mod version " + modVersion)); log.LogInfo((object)("[Menu] game version per SteamLobby: " + (ReadGameVersion(log) ?? ""))); } 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 _bips = new List(); private static readonly Random _rng = new Random(); private static readonly HashSet _randomBipPrefabs = new HashSet(); private static readonly HashSet _seenAtStart = new HashSet(); 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(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(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 ""; } } 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(((Component)val3).gameObject); ((Object)val4).name = "CMR_ModeText"; val4.transform.SetParent(val, false); RectTransform component = val4.GetComponent(); 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() : null); if ((Object)(object)_text == (Object)null) { _text = val4.GetComponentInChildren(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 queue = new Queue(); 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 _states = new Dictionary(); public static void Init(ManualLogSource log, bool verbose) { _log = log; _verbose = verbose; GenericWriter.Write = Write; GenericReader.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((Action)OnClientToggle); _log.LogInfo((object)"[Flashlight] client broadcast registered"); flag = true; } if ((Object)(object)InstanceFinder.ServerManager != (Object)null) { InstanceFinder.ServerManager.RegisterBroadcast((Action)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(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(); } 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(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(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(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> _fire = new Dictionary>(); private static readonly Dictionary> _melee = new Dictionary>(); private static readonly HashSet _seenShoot = new HashSet(); private static readonly HashSet _seenMelee = new HashSet(); private static readonly HashSet _seenAwake = new HashSet(); public static void Apply(ManualLogSource log) { _log = log; SoundSet[] sets = Sets; foreach (SoundSet soundSet in sets) { List 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 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 LoadAll(string[] resources) { List list = new List(); 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(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 + " = ")); 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 + " = ")); } } } 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 ""; } } [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)); } } } }