using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using Fairoots.Core; using Fairoots.Core.Presets; using Fairoots.Creatures; using Fairoots.Diagnostics; using Fairoots.Networking; using Fairoots.SporeAreas; using Fairoots.SporeBombs; using Fairoots.Spores; using Fairoots.Ui; using Fairoots.Wind; using HarmonyLib; using Microsoft.CodeAnalysis; using Peak.Afflictions; using Peak.UI; using Photon.Pun; using Photon.Realtime; using TMPro; using Unity.Mathematics; using UnityEngine; using UnityEngine.Animations.Rigging; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.UI; using Zorro.Core; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Fairoots")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+9323b671618d00c20c84a5679b5ed23d15ab02d7")] [assembly: AssemblyProduct("Fairoots")] [assembly: AssemblyTitle("Fairoots")] [assembly: AssemblyVersion("1.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 Fairoots { internal static class GameUnits { internal static float UnitsToMetersFactor => WorldUnits.SafeFactor(CharacterStats.unitsToMeters); internal static float MetersToUnits(float meters) { return WorldUnits.MetersToUnits(meters, CharacterStats.unitsToMeters); } internal static float ToMeters(float units) { return WorldUnits.UnitsToMeters(units, CharacterStats.unitsToMeters); } } internal static class ParticleOpacity { private static readonly HashSet OpacityProperties = new HashSet(StringComparer.OrdinalIgnoreCase) { "_Opacity", "_Alpha", "_Transparency" }; private static readonly Dictionary VanillaStartColors = new Dictionary(); private static readonly Dictionary<(int RendererId, int PropertyId), float> VanillaOpacities = new Dictionary<(int, int), float>(); private static readonly HashSet LoggedMaterials = new HashSet(); internal static int Apply(GameObject root, double multiplier) { int num = 0; ParticleSystem[] componentsInChildren = root.GetComponentsInChildren(true); foreach (ParticleSystem val in componentsInChildren) { ParticleSystemRenderer component = ((Component)val).GetComponent(); LogInventoryOnce(component); if (ApplyShaderOpacity(component, multiplier)) { ApplyStartColorAlpha(val, 1.0); } else { ApplyStartColorAlpha(val, multiplier); } num++; } return num; } private static bool ApplyShaderOpacity(ParticleSystemRenderer renderer, double multiplier) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Invalid comparison between Unknown and I4 //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown Material val = (((Object)(object)renderer != (Object)null) ? ((Renderer)renderer).sharedMaterial : null); Shader val2 = (((Object)(object)val != (Object)null) ? val.shader : null); if ((Object)(object)val2 == (Object)null) { return false; } int instanceID = ((Object)renderer).GetInstanceID(); MaterialPropertyBlock val3 = null; int propertyCount = val2.GetPropertyCount(); for (int i = 0; i < propertyCount; i++) { ShaderPropertyType propertyType = val2.GetPropertyType(i); if (((int)propertyType == 2 || (int)propertyType == 3) && OpacityProperties.Contains(val2.GetPropertyName(i))) { int propertyNameId = val2.GetPropertyNameId(i); (int, int) key = (instanceID, propertyNameId); if (!VanillaOpacities.TryGetValue(key, out var value)) { value = val.GetFloat(propertyNameId); VanillaOpacities[key] = value; } if (val3 == null) { val3 = new MaterialPropertyBlock(); ((Renderer)renderer).GetPropertyBlock(val3); } val3.SetFloat(propertyNameId, SporeCloudOpacity.IsVanilla(multiplier) ? value : SporeCloudOpacity.ScaleAlpha(value, multiplier)); } } if (val3 == null) { return false; } ((Renderer)renderer).SetPropertyBlock(val3); return true; } private static void ApplyStartColorAlpha(ParticleSystem ps, double multiplier) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) int instanceID = ((Object)ps).GetInstanceID(); MainModule main = ps.main; if (!VanillaStartColors.TryGetValue(instanceID, out var value)) { value = ((MainModule)(ref main)).startColor; VanillaStartColors[instanceID] = value; } ((MainModule)(ref main)).startColor = (SporeCloudOpacity.IsVanilla(multiplier) ? value : ScaleAlpha(value, multiplier)); } private static MinMaxGradient ScaleAlpha(MinMaxGradient authored, double multiplier) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected I4, but got Unknown //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) ParticleSystemGradientMode mode = ((MinMaxGradient)(ref authored)).mode; switch ((int)mode) { case 0: return new MinMaxGradient(WithScaledAlpha(((MinMaxGradient)(ref authored)).color, multiplier)); case 2: return new MinMaxGradient(WithScaledAlpha(((MinMaxGradient)(ref authored)).colorMin, multiplier), WithScaledAlpha(((MinMaxGradient)(ref authored)).colorMax, multiplier)); case 3: return new MinMaxGradient(ScaleAlpha(((MinMaxGradient)(ref authored)).gradientMin, multiplier), ScaleAlpha(((MinMaxGradient)(ref authored)).gradientMax, multiplier)); case 1: case 4: { MinMaxGradient result = default(MinMaxGradient); ((MinMaxGradient)(ref result))..ctor(ScaleAlpha(((MinMaxGradient)(ref authored)).gradient, multiplier)); ((MinMaxGradient)(ref result)).mode = ((MinMaxGradient)(ref authored)).mode; return result; } default: return authored; } } private static Gradient ScaleAlpha(Gradient authored, double multiplier) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown GradientAlphaKey[] alphaKeys = authored.alphaKeys; for (int i = 0; i < alphaKeys.Length; i++) { alphaKeys[i].alpha = SporeCloudOpacity.ScaleAlpha(alphaKeys[i].alpha, multiplier); } Gradient val = new Gradient { mode = authored.mode }; val.SetKeys(authored.colorKeys, alphaKeys); return val; } private static Color WithScaledAlpha(Color authored, double multiplier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) return new Color(authored.r, authored.g, authored.b, SporeCloudOpacity.ScaleAlpha(authored.a, multiplier)); } private static void LogInventoryOnce(ParticleSystemRenderer renderer) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Invalid comparison between Unknown and I4 //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (!Diag.Enabled || (Object)(object)renderer == (Object)null) { return; } Material sharedMaterial = ((Renderer)renderer).sharedMaterial; Shader val = (((Object)(object)sharedMaterial != (Object)null) ? sharedMaterial.shader : null); if (!((Object)(object)val == (Object)null) && LoggedMaterials.Add(((Object)sharedMaterial).GetInstanceID())) { List list = new List(); int propertyCount = val.GetPropertyCount(); for (int i = 0; i < propertyCount; i++) { ShaderPropertyType propertyType = val.GetPropertyType(i); string propertyName = val.GetPropertyName(i); bool flag = ((int)propertyType == 2 || (int)propertyType == 3) && OpacityProperties.Contains(propertyName); list.Add(string.Format("{0}:{1}{2}", propertyName, propertyType, flag ? " <-- opacity lever" : string.Empty)); } Diag.V("[ParticleOpacity] material \"" + ((Object)sharedMaterial).name + "\" on \"" + ((Object)renderer).name + "\": shader \"" + ((Object)val).name + "\", " + $"render mode {renderer.renderMode}, declares: " + ((list.Count == 0) ? "(nothing)" : string.Join(", ", list))); } } } [BepInPlugin("OnlyCook.Fairoots", "Fairoots", "1.0.0")] public class Plugin : BaseUnityPlugin { private Harmony _harmony; internal static Plugin Instance { get; private set; } internal static PluginConfig Cfg { get; private set; } private void Awake() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_0461: Unknown result type (might be due to invalid IL or missing references) //IL_0466: Unknown result type (might be due to invalid IL or missing references) //IL_046c: Expected O, but got Unknown //IL_046c: Unknown result type (might be due to invalid IL or missing references) //IL_0473: Unknown result type (might be due to invalid IL or missing references) //IL_047f: Expected O, but got Unknown //IL_0523: Unknown result type (might be due to invalid IL or missing references) Instance = this; Cfg = new PluginConfig(((BaseUnityPlugin)this).Config); Diag.Source = ((BaseUnityPlugin)this).Logger; _harmony = new Harmony("OnlyCook.Fairoots"); _harmony.PatchAll(typeof(Plugin).Assembly); TriggerRadiusOverlay.Sync(); Cfg.EnableDebugLogging.SettingChanged += delegate { SyncDebugOverlays(); }; Cfg.ShowSporeBombTriggerRadius.SettingChanged += delegate { TriggerRadiusOverlay.Sync(); }; Cfg.ShowRemovedSporeBombMarkers.SettingChanged += delegate { DebugOverlayHost.Sync(); }; Cfg.KeepVanillaTriggerRadius.SettingChanged += delegate { SporeBombCullPatch.ReapplyTriggerRadiusToAll(); }; Cfg.SporeBombTriggerRadiusMultiplierOverride.SettingChanged += delegate { if (Cfg.LiveUpdatesActive) { SporeBombCullPatch.ReapplyTriggerRadiusToAll(); } }; Cfg.Preset.SettingChanged += delegate { if (Cfg.LiveUpdatesActive) { SporeBombCullPatch.ReapplyTriggerRadiusToAll(); WindChillZoneTuningPatch.ReapplyAll(); SporeAreaTuningPatch.ReapplyToAll(); CreatureSpeedPatch.ReapplyToAll(); CreatureKnockbackPatch.ReapplyToAll(); CreatureRagdollPatch.ReapplyToAll(); SporeDecayPatch.ReapplyToAll(); } }; EventHandler eventHandler = delegate { if (Cfg.LiveUpdatesActive) { WindChillZoneTuningPatch.ReapplyAll(); } }; Cfg.WindForceMultiplierOverride.SettingChanged += eventHandler; Cfg.WindGustDurationMultiplierOverride.SettingChanged += eventHandler; Cfg.WindItemForceMultiplierOverride.SettingChanged += eventHandler; Cfg.WindObstacleOcclusionRangeMultiplierOverride.SettingChanged += eventHandler; Cfg.DisableWindEntirely.SettingChanged += delegate { WindChillZoneTuningPatch.ReapplyAll(); }; Cfg.DisableSporeAreas.SettingChanged += delegate { SporeAreaDisablePatch.ReapplyToAll(); }; EventHandler eventHandler2 = delegate { CreatureDisablePatch.ReapplyToAll(); }; Cfg.DisableZombies.SettingChanged += eventHandler2; Cfg.DisableBeetles.SettingChanged += eventHandler2; Cfg.DisableSpiders.SettingChanged += eventHandler2; EventHandler eventHandler3 = delegate { if (Cfg.LiveUpdatesActive) { CreatureSpeedPatch.ReapplyToAll(); } }; Cfg.ZombieSpeedMultiplierOverride.SettingChanged += eventHandler3; Cfg.BeetleSpeedMultiplierOverride.SettingChanged += eventHandler3; Cfg.BeetleKnockbackMultiplierOverride.SettingChanged += delegate { if (Cfg.LiveUpdatesActive) { CreatureKnockbackPatch.ReapplyToAll(); } }; Cfg.CreatureRagdollMultiplierOverride.SettingChanged += delegate { if (Cfg.LiveUpdatesActive) { CreatureRagdollPatch.ReapplyToAll(); } }; EventHandler eventHandler4 = delegate { if (Cfg.LiveUpdatesActive) { SporeAreaTuningPatch.ReapplyToAll(); } }; Cfg.SporeAreaRadiusMultiplierOverride.SettingChanged += eventHandler4; Cfg.SporeAreaStatusRateMultiplierOverride.SettingChanged += eventHandler4; Cfg.SporeClearTimeMultiplierOverride.SettingChanged += delegate { if (Cfg.LiveUpdatesActive) { SporeDecayPatch.ReapplyToAll(); } }; Cfg.CoverMouthPoseEmote.SettingChanged += delegate { CoverMouthPose.InvalidateEmote(); }; Cfg.CoverMouthPoseEmoteTime.SettingChanged += delegate { CoverMouthPose.InvalidateEmote(); }; Cfg.RecolorSporeBombs.SettingChanged += delegate { SporeBombRecolorPatch.ReapplyToAll(); }; Cfg.SporeAreaCloudOpacity.SettingChanged += delegate { SporeCloudOpacityPatch.ReapplyToAll(); }; ((BaseUnityPlugin)this).Config.SettingChanged += delegate { HostAuthority.PublishAll(); }; GameObject val = new GameObject("FairootsNetworking"); Object.DontDestroyOnLoad((Object)val); val.AddComponent(); val.AddComponent(); DebugOverlayHost.Bind(val); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Fairoots 1.0.0 loaded. " + $"seed={Cfg.Seed.Value}, preset={Cfg.Preset.Value}, " + $"spore-bomb cull fraction={Cfg.SporeBombCullFraction:0.##}")); if (Cfg.EnableDebugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)($"Debug logging ON. Auto scene scan on load={Cfg.LogSceneScanOnLoad.Value}, " + $"scan hotkey={Cfg.SceneScanHotkey.Value}.")); } } private void Update() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (Cfg == null) { return; } RootsLevelWatcher.CheckAndRun(); CoverMouthController.Tick(); SporeBombCloudWarning.Tick(); SporeWarningLabel.Tick(); SpiderWarningLabel.Tick(); CoverMouthPose.Prewarm(); if (Cfg.EnableDebugLogging.Value) { KeyCode value = Cfg.SceneScanHotkey.Value; if ((int)value != 0 && Input.GetKeyDown(value)) { SceneDiagnostics.DumpReport($"hotkey {value}"); } KeyCode value2 = Cfg.FoliageProbeHotkey.Value; if ((int)value2 != 0 && Input.GetKeyDown(value2)) { SceneDiagnostics.ProbeFoliageNearestSporeBomb(); } KeyCode value3 = Cfg.MaterialProbeHotkey.Value; if ((int)value3 != 0 && Input.GetKeyDown(value3)) { MaterialProbe.DumpLookedAt(); } } } private static void SyncDebugOverlays() { TriggerRadiusOverlay.Sync(); DebugOverlayHost.Sync(); } } public class PluginConfig { private bool _snapshotTaken; private double _snapCullFraction; private double _snapTriggerRadiusMultiplier; private double _snapKnockbackMultiplier; private float _snapScreenshakeRangeCapMeters; private double _snapVfxCountMultiplier; private double _snapTriggerHeightMultiplier; private double _snapSporeAreaRadiusMultiplier; private double _snapSporeAreaRemovalFraction; private double _snapSporeAreaRadiusMultiplierValue; private double _snapSporeAreaStatusRateMultiplier; private double _snapSporeClearTimeMultiplier; private double _snapSporeBuildUpMultiplier; private double _snapZombieSpeedMultiplier; private double _snapBeetleSpeedMultiplier; private double _snapBeetleKnockbackMultiplier; private double _snapCreatureRagdollMultiplier; private bool _snapZombieDeaggroEnabled; private double _snapZombieDeaggroMultiplier; private double _snapBeetleDeaggroMultiplier; private double _snapWindForceMultiplier; private double _snapWindGustDurationMultiplier; private double _snapWindItemForceMultiplier; private double _snapWindObstacleOcclusionRangeMultiplier; private double _snapWindFallCameraDampenClamp; private double _snapClimbWindSpeedMultiplier; private double _snapClimbWindUpwardSpeedMultiplier; private double _snapClimbWindIntoWindSpeedMultiplier; private double _snapClimbWindGraceForceMultiplier; private bool _snapLiveUpdates; public ConfigEntry Seed { get; } public ConfigEntry Preset { get; } public ConfigEntry ApplyPurePreset { get; } public ConfigEntry RecolorSporeBombs { get; } public ConfigEntry SporeAreaCloudOpacity { get; } public ConfigEntry SporeBombCloudOpacity { get; } public ConfigEntry ShowOverlayInSporeBombClouds { get; } public ConfigEntry ShowSporeCloudLabel { get; } public ConfigEntry ShowSpiderWarningLabel { get; } public ConfigEntry SporeBombCullFractionOverride { get; } public ConfigEntry EnableFoliageRemovalOverride { get; } public ConfigEntry SporeBombTriggerRadiusMultiplierOverride { get; } public ConfigEntry SporeBombKnockbackMultiplierOverride { get; } public ConfigEntry SporeBombScreenshakeRangeCapOverride { get; } public ConfigEntry SporeBombVfxCountMultiplierOverride { get; } public ConfigEntry SporeBombTriggerHeightMultiplierOverride { get; } public ConfigEntry SporeBombSporeAreaRadiusMultiplierOverride { get; } public ConfigEntry CoverMouthKey { get; } public ConfigEntry CoverMouthHold { get; } public ConfigEntry CoverMouthUseBonusStamina { get; } public ConfigEntry DisableSporeAreas { get; } public ConfigEntry SporeAreaRemovalFractionOverride { get; } public ConfigEntry SporeAreaRadiusMultiplierOverride { get; } public ConfigEntry SporeAreaStatusRateMultiplierOverride { get; } public ConfigEntry SporeClearTimeMultiplierOverride { get; } public ConfigEntry SporeBuildUpMultiplierOverride { get; } public ConfigEntry DisableZombies { get; } public ConfigEntry DisableBeetles { get; } public ConfigEntry DisableSpiders { get; } public ConfigEntry ZombieSpeedMultiplierOverride { get; } public ConfigEntry BeetleSpeedMultiplierOverride { get; } public ConfigEntry BeetleKnockbackMultiplierOverride { get; } public ConfigEntry CreatureRagdollMultiplierOverride { get; } public ConfigEntry ZombieDeaggroEnabledOverride { get; } public ConfigEntry ZombieDeaggroMultiplierOverride { get; } public ConfigEntry BeetleDeaggroMultiplierOverride { get; } public ConfigEntry ZombieKnockoutSecondsOverride { get; } public ConfigEntry BeetleKnockoutSecondsOverride { get; } public ConfigEntry CreatureKnockoutMinThrowSpeedOverride { get; } public ConfigEntry CreatureKnockoutMaxThrowDistanceOverride { get; } public ConfigEntry BlowgunAffectsCreaturesOverride { get; } public ConfigEntry BlowgunCreatureStunSecondsOverride { get; } public ConfigEntry ZombieWindMultiplierOverride { get; } public ConfigEntry BeetleWindSusceptibilityOverride { get; } public ConfigEntry EnableCoverMouthOverride { get; } public ConfigEntry CoverMouthStaminaPerSecondOverride { get; } public ConfigEntry CoverMouthBlocksSporeBombsOverride { get; } public ConfigEntry DisableWindEntirely { get; } public ConfigEntry WindBackpackAlwaysImmuneOverride { get; } public ConfigEntry WindForceMultiplierOverride { get; } public ConfigEntry WindGustDurationMultiplierOverride { get; } public ConfigEntry WindItemForceMultiplierOverride { get; } public ConfigEntry WindObstacleOcclusionRangeMultiplierOverride { get; } public ConfigEntry WindFallCameraDampenClampOverride { get; } public ConfigEntry WindRecentForceWindowSecondsOverride { get; } public ConfigEntry PreventWindRagdollOverride { get; } public ConfigEntry ClimbSheltersFromWindOverride { get; } public ConfigEntry ClimbWindSpeedMultiplierOverride { get; } public ConfigEntry ClimbWindUpwardSpeedMultiplierOverride { get; } public ConfigEntry ClimbWindIntoWindSpeedMultiplierOverride { get; } public ConfigEntry ClimbShelterGraceSecondsOverride { get; } public ConfigEntry ClimbWindGraceForceMultiplierOverride { get; } public ConfigEntry ApplyChangesLive { get; } public ConfigEntry EnableDebugLogging { get; } public ConfigEntry LogSceneScanOnLoad { get; } public ConfigEntry SceneScanHotkey { get; } public ConfigEntry LogScreenshakeSources { get; } public ConfigEntry FoliageProbeHotkey { get; } public ConfigEntry MaterialProbeHotkey { get; } public ConfigEntry ShowRemovedSporeBombMarkers { get; } public ConfigEntry ShowSporeBombTriggerRadius { get; } public ConfigEntry CoverMouthPosePreview { get; } public ConfigEntry CoverMouthPoseEmote { get; } public ConfigEntry CoverMouthPoseEmoteTime { get; } public ConfigEntry CoverMouthPoseForwardCm { get; } public ConfigEntry CoverMouthPoseBelowHeadCm { get; } public ConfigEntry CoverMouthPoseHandGapCm { get; } public ConfigEntry CoverMouthPoseSideCm { get; } public ConfigEntry CoverMouthPoseHandYawDeg { get; } public ConfigEntry CoverMouthPoseHandRollDeg { get; } public ConfigEntry CoverMouthPoseHandPitchDeg { get; } public ConfigEntry KeepVanillaTriggerRadius { get; } public double SporeBombCullFraction => OverrideResolution.Resolve(PresetCatalog.SporeBombCullFraction(Preset.Value), SporeBombCullFractionOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value); public double SporeBombTriggerRadiusMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombTriggerRadiusMultiplier(Preset.Value), SporeBombTriggerRadiusMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double SporeBombKnockbackMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombKnockbackMultiplier(Preset.Value), SporeBombKnockbackMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public float SporeBombScreenshakeRangeCapMeters => (float)OverrideResolution.Resolve(PresetCatalog.SporeBombScreenshakeRangeCapMeters(Preset.Value), SporeBombScreenshakeRangeCapOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value); public double SporeBombVfxCountMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombVfxCountMultiplier(Preset.Value), SporeBombVfxCountMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double SporeBombTriggerHeightMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombTriggerHeightMultiplier(Preset.Value), SporeBombTriggerHeightMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double SporeBombSporeAreaRadiusMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBombSporeAreaRadiusMultiplier(Preset.Value), SporeBombSporeAreaRadiusMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double SporeAreaRemovalFraction => OverrideResolution.Resolve(PresetCatalog.SporeAreaRemovalFraction(Preset.Value), SporeAreaRemovalFractionOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value); public double SporeAreaRadiusMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeAreaRadiusMultiplier(Preset.Value), SporeAreaRadiusMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double SporeAreaStatusRateMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeAreaStatusRateMultiplier(Preset.Value), SporeAreaStatusRateMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double SporeClearTimeMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeClearTimeMultiplier(Preset.Value), SporeClearTimeMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double SporeBuildUpMultiplier => OverrideResolution.Resolve(PresetCatalog.SporeBuildUpMultiplier(Preset.Value), SporeBuildUpMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double ZombieSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.ZombieSpeedMultiplier(Preset.Value), ZombieSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double BeetleSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.BeetleSpeedMultiplier(Preset.Value), BeetleSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double BeetleKnockbackMultiplier => OverrideResolution.Resolve(PresetCatalog.BeetleKnockbackMultiplier(Preset.Value), BeetleKnockbackMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double CreatureRagdollMultiplier => OverrideResolution.Resolve(PresetCatalog.CreatureRagdollMultiplier(Preset.Value), CreatureRagdollMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public bool ZombieDeaggroEnabled => OverrideResolution.Resolve(PresetCatalog.ZombieDeaggroEnabled(Preset.Value), ZombieDeaggroEnabledOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value); public double ZombieDeaggroMultiplier => ZombieDeaggro.ClampMultiplier(OverrideResolution.Resolve(PresetCatalog.ZombieDeaggroMultiplier(Preset.Value), ZombieDeaggroMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value)); public double BeetleDeaggroMultiplier => BeetleDeaggro.ClampMultiplier(OverrideResolution.Resolve(PresetCatalog.BeetleDeaggroMultiplier(Preset.Value), BeetleDeaggroMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value)); public double WindForceMultiplier => OverrideResolution.Resolve(PresetCatalog.WindForceMultiplier(Preset.Value), WindForceMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double WindGustDurationMultiplier => OverrideResolution.Resolve(PresetCatalog.WindGustDurationMultiplier(Preset.Value), WindGustDurationMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double WindItemForceMultiplier => OverrideResolution.Resolve(PresetCatalog.WindItemForceMultiplier(Preset.Value), WindItemForceMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double WindObstacleOcclusionRangeMultiplier => OverrideResolution.Resolve(PresetCatalog.WindObstacleOcclusionRangeMultiplier(Preset.Value), WindObstacleOcclusionRangeMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double WindFallCameraDampenClamp => OverrideResolution.Resolve(PresetCatalog.WindFallCameraDampenClamp(Preset.Value), WindFallCameraDampenClampOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value); public double ClimbWindSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.ClimbWindSpeedMultiplier(Preset.Value), ClimbWindSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double ClimbWindUpwardSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.ClimbWindUpwardSpeedMultiplier(Preset.Value), ClimbWindUpwardSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double ClimbWindIntoWindSpeedMultiplier => OverrideResolution.Resolve(PresetCatalog.ClimbWindIntoWindSpeedMultiplier(Preset.Value), ClimbWindIntoWindSpeedMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double ClimbWindGraceForceMultiplier => OverrideResolution.Resolve(PresetCatalog.ClimbWindGraceForceMultiplier(Preset.Value), ClimbWindGraceForceMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); internal bool LiveUpdatesActive { get { if (_snapshotTaken) { return _snapLiveUpdates; } return true; } } private bool UseLiveValue => LiveUpdatesActive; public int EffectiveSeed => HostAuthority.Resolve("Seed", Seed.Value); public double EffectiveSporeBombCullFraction => HostAuthority.Resolve("SporeBombCullFraction", UseLiveValue ? SporeBombCullFraction : _snapCullFraction); public double EffectiveSporeBombTriggerRadiusMultiplier => HostAuthority.Resolve("SporeBombTriggerRadiusMultiplier", UseLiveValue ? SporeBombTriggerRadiusMultiplier : _snapTriggerRadiusMultiplier); public double EffectiveSporeBombKnockbackMultiplier => HostAuthority.Resolve("SporeBombKnockbackMultiplier", UseLiveValue ? SporeBombKnockbackMultiplier : _snapKnockbackMultiplier); public float EffectiveSporeBombScreenshakeRangeCapMeters => HostAuthority.Resolve("SporeBombScreenshakeRangeCapMeters", UseLiveValue ? SporeBombScreenshakeRangeCapMeters : _snapScreenshakeRangeCapMeters); public double EffectiveSporeBombVfxCountMultiplier => HostAuthority.Resolve("SporeBombVfxCountMultiplier", UseLiveValue ? SporeBombVfxCountMultiplier : _snapVfxCountMultiplier); public double EffectiveSporeBombTriggerHeightMultiplier => HostAuthority.Resolve("SporeBombTriggerHeightMultiplier", UseLiveValue ? SporeBombTriggerHeightMultiplier : _snapTriggerHeightMultiplier); public double EffectiveSporeBombSporeAreaRadiusMultiplier => HostAuthority.Resolve("SporeBombSporeAreaRadiusMultiplier", UseLiveValue ? SporeBombSporeAreaRadiusMultiplier : _snapSporeAreaRadiusMultiplier); public double EffectiveSporeAreaRemovalFraction => HostAuthority.Resolve("SporeAreaRemovalFraction", UseLiveValue ? SporeAreaRemovalFraction : _snapSporeAreaRemovalFraction); public double EffectiveSporeAreaRadiusMultiplier => HostAuthority.Resolve("SporeAreaRadiusMultiplier", UseLiveValue ? SporeAreaRadiusMultiplier : _snapSporeAreaRadiusMultiplierValue); public double EffectiveSporeAreaStatusRateMultiplier => HostAuthority.Resolve("SporeAreaStatusRateMultiplier", UseLiveValue ? SporeAreaStatusRateMultiplier : _snapSporeAreaStatusRateMultiplier); public double EffectiveSporeClearTimeMultiplier => HostAuthority.Resolve("SporeClearTimeMultiplier", UseLiveValue ? SporeClearTimeMultiplier : _snapSporeClearTimeMultiplier); public double EffectiveSporeBuildUpMultiplier => HostAuthority.Resolve("SporeBuildUpMultiplier", UseLiveValue ? SporeBuildUpMultiplier : _snapSporeBuildUpMultiplier); public double EffectiveWindForceMultiplier => HostAuthority.Resolve("WindForceMultiplier", UseLiveValue ? WindForceMultiplier : _snapWindForceMultiplier); public double EffectiveWindGustDurationMultiplier => HostAuthority.Resolve("WindGustDurationMultiplier", UseLiveValue ? WindGustDurationMultiplier : _snapWindGustDurationMultiplier); public double EffectiveWindItemForceMultiplier => HostAuthority.Resolve("WindItemForceMultiplier", UseLiveValue ? WindItemForceMultiplier : _snapWindItemForceMultiplier); public double EffectiveWindObstacleOcclusionRangeMultiplier => HostAuthority.Resolve("WindObstacleOcclusionRangeMultiplier", UseLiveValue ? WindObstacleOcclusionRangeMultiplier : _snapWindObstacleOcclusionRangeMultiplier); public bool EffectiveWindBackpackAlwaysImmune => HostAuthority.Resolve("WindBackpackAlwaysImmune", WindBackpackAlwaysImmune); public bool WindBackpackAlwaysImmune => OverrideResolution.Resolve(PresetCatalog.WindBackpackAlwaysImmune(Preset.Value), WindBackpackAlwaysImmuneOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value); public bool EffectiveCoverMouthBlocksSporeBombs => HostAuthority.Resolve("CoverMouthBlocksSporeBombs", CoverMouthBlocksSporeBombs); public bool CoverMouthBlocksSporeBombs => OverrideResolution.Resolve(PresetCatalog.CoverMouthBlocksSporeBombs(Preset.Value), CoverMouthBlocksSporeBombsOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value); public bool EnableCoverMouth => OverrideResolution.Resolve(PresetCatalog.EnableCoverMouth(Preset.Value), EnableCoverMouthOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value); public bool EffectiveEnableCoverMouth => HostAuthority.Resolve("EnableCoverMouth", EnableCoverMouth); public float EffectiveCoverMouthStaminaPerSecond => HostAuthority.Resolve("CoverMouthStaminaPerSecond", CoverMouthStaminaPerSecond); public float CoverMouthStaminaPerSecond => OverrideResolution.Resolve(PresetCatalog.CoverMouthStaminaPerSecond(Preset.Value), CoverMouthStaminaPerSecondOverride.Value, 0.03f, Preset.Value, ApplyPurePreset.Value); public bool EffectiveDisableSporeAreas => HostAuthority.Resolve("DisableSporeAreas", DisableSporeAreas.Value); public double EffectiveZombieSpeedMultiplier => HostAuthority.Resolve("ZombieSpeedMultiplier", UseLiveValue ? ZombieSpeedMultiplier : _snapZombieSpeedMultiplier); public double EffectiveBeetleSpeedMultiplier => HostAuthority.Resolve("BeetleSpeedMultiplier", UseLiveValue ? BeetleSpeedMultiplier : _snapBeetleSpeedMultiplier); public double EffectiveBeetleKnockbackMultiplier => HostAuthority.Resolve("BeetleKnockbackMultiplier", UseLiveValue ? BeetleKnockbackMultiplier : _snapBeetleKnockbackMultiplier); public double EffectiveCreatureRagdollMultiplier => HostAuthority.Resolve("CreatureRagdollMultiplier", UseLiveValue ? CreatureRagdollMultiplier : _snapCreatureRagdollMultiplier); public double EffectiveZombieKnockoutSeconds => HostAuthority.Resolve("ZombieKnockoutSeconds", ZombieKnockoutSeconds); public double ZombieKnockoutSeconds => OverrideResolution.Resolve(PresetCatalog.ZombieKnockoutSeconds(Preset.Value), ZombieKnockoutSecondsOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value); public double EffectiveBeetleKnockoutSeconds => HostAuthority.Resolve("BeetleKnockoutSeconds", BeetleKnockoutSeconds); public double BeetleKnockoutSeconds => OverrideResolution.Resolve(PresetCatalog.BeetleKnockoutSeconds(Preset.Value), BeetleKnockoutSecondsOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value); public double EffectiveCreatureKnockoutMinThrowSpeed => HostAuthority.Resolve("CreatureKnockoutMinThrowSpeed", CreatureKnockoutMinThrowSpeed); public double CreatureKnockoutMinThrowSpeed => OverrideResolution.Resolve(PresetCatalog.CreatureKnockoutMinThrowSpeed(Preset.Value), CreatureKnockoutMinThrowSpeedOverride.Value, 36.0, Preset.Value, ApplyPurePreset.Value); public double EffectiveCreatureKnockoutMaxThrowDistance => HostAuthority.Resolve("CreatureKnockoutMaxThrowDistance", CreatureKnockoutMaxThrowDistance); public double CreatureKnockoutMaxThrowDistance => OverrideResolution.Resolve(PresetCatalog.CreatureKnockoutMaxThrowDistance(Preset.Value), CreatureKnockoutMaxThrowDistanceOverride.Value, 12.0, Preset.Value, ApplyPurePreset.Value); public bool EffectiveBlowgunAffectsCreatures => HostAuthority.Resolve("BlowgunAffectsCreatures", BlowgunAffectsCreatures); public bool BlowgunAffectsCreatures => OverrideResolution.Resolve(PresetCatalog.BlowgunAffectsCreatures(Preset.Value), BlowgunAffectsCreaturesOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value); public double EffectiveBlowgunCreatureStunSeconds => HostAuthority.Resolve("BlowgunCreatureStunSeconds", BlowgunCreatureStunSeconds); public double BlowgunCreatureStunSeconds => OverrideResolution.Resolve(PresetCatalog.BlowgunCreatureStunSeconds(Preset.Value), BlowgunCreatureStunSecondsOverride.Value, 45.0, Preset.Value, ApplyPurePreset.Value); public double EffectiveZombieWindMultiplier => HostAuthority.Resolve("ZombieWindMultiplier", ZombieWindMultiplier); public double ZombieWindMultiplier => OverrideResolution.Resolve(PresetCatalog.ZombieWindMultiplier(Preset.Value), ZombieWindMultiplierOverride.Value, 1.0, Preset.Value, ApplyPurePreset.Value); public double EffectiveBeetleWindSusceptibility => HostAuthority.Resolve("BeetleWindSusceptibility", BeetleWindSusceptibility); public double BeetleWindSusceptibility => OverrideResolution.Resolve(PresetCatalog.BeetleWindSusceptibility(Preset.Value), BeetleWindSusceptibilityOverride.Value, 0.0, Preset.Value, ApplyPurePreset.Value); public bool EffectiveZombieDeaggroEnabled => HostAuthority.Resolve("ZombieDeaggroEnabled", UseLiveValue ? ZombieDeaggroEnabled : _snapZombieDeaggroEnabled); public double EffectiveZombieDeaggroMultiplier => HostAuthority.Resolve("ZombieDeaggroMultiplier", UseLiveValue ? ZombieDeaggroMultiplier : _snapZombieDeaggroMultiplier); public double EffectiveBeetleDeaggroMultiplier => HostAuthority.Resolve("BeetleDeaggroMultiplier", UseLiveValue ? BeetleDeaggroMultiplier : _snapBeetleDeaggroMultiplier); public bool EffectiveDisableZombies => HostAuthority.Resolve("DisableZombies", DisableZombies.Value); public bool EffectiveDisableBeetles => HostAuthority.Resolve("DisableBeetles", DisableBeetles.Value); public bool EffectiveDisableSpiders => HostAuthority.Resolve("DisableSpiders", DisableSpiders.Value); public bool EffectiveDisableWindEntirely => HostAuthority.Resolve("DisableWindEntirely", DisableWindEntirely.Value); public double EffectiveWindFallCameraDampenClamp => HostAuthority.Resolve("WindFallCameraDampenClamp", UseLiveValue ? WindFallCameraDampenClamp : _snapWindFallCameraDampenClamp); public float EffectiveWindRecentForceWindowSeconds => HostAuthority.Resolve("WindRecentForceWindowSeconds", WindRecentForceWindowSeconds); public float WindRecentForceWindowSeconds => OverrideResolution.Resolve(PresetCatalog.WindRecentForceWindowSeconds(Preset.Value), WindRecentForceWindowSecondsOverride.Value, 1.5f, Preset.Value, ApplyPurePreset.Value); public bool EffectivePreventWindRagdoll => HostAuthority.Resolve("PreventWindRagdoll", PreventWindRagdoll); public bool PreventWindRagdoll => OverrideResolution.Resolve(PresetCatalog.PreventWindRagdoll(Preset.Value), PreventWindRagdollOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value); public bool EnableFoliageRemoval => OverrideResolution.Resolve(PresetCatalog.EnableFoliageRemoval(Preset.Value), EnableFoliageRemovalOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value); public bool EffectiveEnableFoliageRemoval => HostAuthority.Resolve("EnableFoliageRemoval", EnableFoliageRemoval); public bool ClimbSheltersFromWind => OverrideResolution.Resolve(PresetCatalog.ClimbSheltersFromWind(Preset.Value), ClimbSheltersFromWindOverride.Value, defaultValue: false, Preset.Value, ApplyPurePreset.Value); public bool EffectiveClimbSheltersFromWind => HostAuthority.Resolve("ClimbSheltersFromWind", ClimbSheltersFromWind); public double EffectiveClimbWindSpeedMultiplier => HostAuthority.Resolve("ClimbWindSpeedMultiplier", UseLiveValue ? ClimbWindSpeedMultiplier : _snapClimbWindSpeedMultiplier); public double EffectiveClimbWindUpwardSpeedMultiplier => HostAuthority.Resolve("ClimbWindUpwardSpeedMultiplier", UseLiveValue ? ClimbWindUpwardSpeedMultiplier : _snapClimbWindUpwardSpeedMultiplier); public double EffectiveClimbWindIntoWindSpeedMultiplier => HostAuthority.Resolve("ClimbWindIntoWindSpeedMultiplier", UseLiveValue ? ClimbWindIntoWindSpeedMultiplier : _snapClimbWindIntoWindSpeedMultiplier); public double EffectiveClimbWindGraceForceMultiplier => HostAuthority.Resolve("ClimbWindGraceForceMultiplier", UseLiveValue ? ClimbWindGraceForceMultiplier : _snapClimbWindGraceForceMultiplier); public float EffectiveClimbShelterGraceSeconds => HostAuthority.Resolve("ClimbShelterGraceSeconds", ClimbShelterGraceSeconds); public float ClimbShelterGraceSeconds => OverrideResolution.Resolve(PresetCatalog.ClimbShelterGraceSeconds(Preset.Value), ClimbShelterGraceSecondsOverride.Value, 0.5f, Preset.Value, ApplyPurePreset.Value); public PluginConfig(ConfigFile config) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Expected O, but got Unknown //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Expected O, but got Unknown //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Expected O, but got Unknown //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Expected O, but got Unknown //IL_0354: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Expected O, but got Unknown //IL_0399: Unknown result type (might be due to invalid IL or missing references) //IL_03a3: Expected O, but got Unknown //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_0420: Expected O, but got Unknown //IL_045b: Unknown result type (might be due to invalid IL or missing references) //IL_0465: Expected O, but got Unknown //IL_04a0: Unknown result type (might be due to invalid IL or missing references) //IL_04aa: Expected O, but got Unknown //IL_04e5: Unknown result type (might be due to invalid IL or missing references) //IL_04ef: Expected O, but got Unknown //IL_052a: Unknown result type (might be due to invalid IL or missing references) //IL_0534: Expected O, but got Unknown //IL_05c3: Unknown result type (might be due to invalid IL or missing references) //IL_05cd: Expected O, but got Unknown //IL_0608: Unknown result type (might be due to invalid IL or missing references) //IL_0612: Expected O, but got Unknown //IL_064d: Unknown result type (might be due to invalid IL or missing references) //IL_0657: Expected O, but got Unknown //IL_0692: Unknown result type (might be due to invalid IL or missing references) //IL_069c: Expected O, but got Unknown //IL_06f3: Unknown result type (might be due to invalid IL or missing references) //IL_06fd: Expected O, but got Unknown //IL_0738: Unknown result type (might be due to invalid IL or missing references) //IL_0742: Expected O, but got Unknown //IL_077d: Unknown result type (might be due to invalid IL or missing references) //IL_0787: Expected O, but got Unknown //IL_07c2: Unknown result type (might be due to invalid IL or missing references) //IL_07cc: Expected O, but got Unknown //IL_0807: Unknown result type (might be due to invalid IL or missing references) //IL_0811: Expected O, but got Unknown //IL_084c: Unknown result type (might be due to invalid IL or missing references) //IL_0856: Expected O, but got Unknown //IL_08ad: Unknown result type (might be due to invalid IL or missing references) //IL_08b7: Expected O, but got Unknown //IL_08f2: Unknown result type (might be due to invalid IL or missing references) //IL_08fc: Expected O, but got Unknown //IL_0937: Unknown result type (might be due to invalid IL or missing references) //IL_0941: Expected O, but got Unknown //IL_098c: Unknown result type (might be due to invalid IL or missing references) //IL_0996: Expected O, but got Unknown //IL_0a09: Unknown result type (might be due to invalid IL or missing references) //IL_0a13: Expected O, but got Unknown //IL_0a4e: Unknown result type (might be due to invalid IL or missing references) //IL_0a58: Expected O, but got Unknown //IL_0a93: Unknown result type (might be due to invalid IL or missing references) //IL_0a9d: Expected O, but got Unknown //IL_0ad8: Unknown result type (might be due to invalid IL or missing references) //IL_0ae2: Expected O, but got Unknown //IL_0b1d: Unknown result type (might be due to invalid IL or missing references) //IL_0b27: Expected O, but got Unknown //IL_0b56: Unknown result type (might be due to invalid IL or missing references) //IL_0b60: Expected O, but got Unknown //IL_0bd3: Unknown result type (might be due to invalid IL or missing references) //IL_0bdd: Expected O, but got Unknown //IL_0c18: Unknown result type (might be due to invalid IL or missing references) //IL_0c22: Expected O, but got Unknown //IL_0c5d: Unknown result type (might be due to invalid IL or missing references) //IL_0c67: Expected O, but got Unknown //IL_0c96: Unknown result type (might be due to invalid IL or missing references) //IL_0ca0: Expected O, but got Unknown //IL_0cdb: Unknown result type (might be due to invalid IL or missing references) //IL_0ce5: Expected O, but got Unknown //IL_0e4c: Unknown result type (might be due to invalid IL or missing references) //IL_0e56: Expected O, but got Unknown //IL_0e85: Unknown result type (might be due to invalid IL or missing references) //IL_0e8f: Expected O, but got Unknown //IL_0ebe: Unknown result type (might be due to invalid IL or missing references) //IL_0ec8: Expected O, but got Unknown //IL_0ef7: Unknown result type (might be due to invalid IL or missing references) //IL_0f01: Expected O, but got Unknown //IL_0f30: Unknown result type (might be due to invalid IL or missing references) //IL_0f3a: Expected O, but got Unknown //IL_0f69: Unknown result type (might be due to invalid IL or missing references) //IL_0f73: Expected O, but got Unknown //IL_0fa2: Unknown result type (might be due to invalid IL or missing references) //IL_0fac: Expected O, but got Unknown //IL_0fdb: Unknown result type (might be due to invalid IL or missing references) //IL_0fe5: Expected O, but got Unknown RecolorSporeBombs = config.Bind("General", "recolor-spore-bombs", true, "Tints spore bombs (and explosive spore bombs) toward the pink/red the game's own Spores status effect uses, instead of leaving them vanilla green - a green hazard on green grass camouflages into the terrain, which is exactly what a hazard shouldn't do. Purely cosmetic and PER-PLAYER: unlike every other gameplay setting here, this one is NOT host-authoritative - it only changes what you see on your own screen, so set it however you like regardless of what the host or anyone else in the lobby has. Applies immediately, regardless of apply-changes-live."); SporeAreaCloudOpacity = config.Bind("General", "spore-area-cloud-opacity", 0.35, new ConfigDescription("How opaque the mushroom spore clouds are drawn, as a fraction of vanilla (1.0 = untouched, 0 = fully invisible). Lower makes them see-through, so the game's own green Spores screen overlay is readable through them - in vanilla the cloud and the overlay are the same colour and blend together, so it's hard to tell whether you're actually inside one and taking spores or just standing next to it. The cloud stays visible enough to spot and walk around; the hazard itself (its size and how fast it applies spores) is NOT changed - that's what the Spore-Areas settings are for. Purely cosmetic and PER-PLAYER: not host-authoritative, set it however you like. Applies immediately, regardless of apply-changes-live.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 1.0), Array.Empty())); SporeBombCloudOpacity = config.Bind("General", "spore-bomb-cloud-opacity", 0.5, new ConfigDescription("The same see-through-cloud setting as spore-area-cloud-opacity, but for the temporary spore cloud a spore bomb leaves when it goes off (1.0 = vanilla, 0 = fully invisible). Separate from the spore-area one because a bomb's cloud erupts right on top of you while a spore area is a landmark seen from a distance. Cosmetic only - the blast, the knockback and the spores it applies are unchanged. Purely cosmetic and PER-PLAYER: not host-authoritative. Applies immediately, regardless of apply-changes-live.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 1.0), Array.Empty())); ShowOverlayInSporeBombClouds = config.Bind("General", "show-overlay-in-spore-bomb-clouds", true, "Keeps the game's green 'you're standing in spores' screen overlay up for as long as you're inside the cloud a spore bomb leaves behind - the same way it already stays up inside a mushroom spore cloud. In vanilla a bomb's cloud only gives you the brief flash each time it damages you, with nothing in between telling you you're still standing in it. The damage flash is untouched, so it still stands out on top of the steady overlay. Purely a readability fix and PER-PLAYER: not host-authoritative, and it changes nothing about the cloud itself. Applies immediately, regardless of apply-changes-live."); ShowSporeCloudLabel = config.Bind("General", "show-spore-cloud-label", false, "Shows a text warning on screen, in the game's own font and in the Spores status colour, whenever you're standing in spores - both the mushroom spore clouds and the cloud a spore bomb leaves. Off by default: the other readability settings just make the game's own feedback legible, while this adds a HUD element PEAK doesn't have, which is more a matter of taste. Purely cosmetic and PER-PLAYER: not host-authoritative. Applies immediately, regardless of apply-changes-live."); ShowSpiderWarningLabel = config.Bind("General", "show-spider-warning-label", false, "Shows a text warning on screen, in the game's own font and in the Poison status colour, while a spider is dropping down on you, and hides it again about a second after the spider lands without catching anyone. Worth turning on if spiders keep getting you from above: the game gives you no warning at all for this - the only spider sound plays at the same moment the drop starts, and being grabbed is instant on contact - so this turns the time the spider spends descending into time you can actually react in. Off by default like the other on-screen labels, since it adds a HUD element PEAK doesn't have. Purely cosmetic and PER-PLAYER: not host-authoritative. Applies immediately, regardless of apply-changes-live."); CoverMouthKey = config.Bind("General", "cover-mouth-key", (KeyCode)120, "Key to cover your mouth with both hands, making you immune to spore areas while you hold it. Your hands are busy while covering: you can't climb, pick things up, or switch items, and anything you're holding is put away (an item you're carrying in your hands with no free pocket for it gets dropped). Set to None to disable the mechanic entirely. PER-PLAYER: this and cover-mouth-hold below are yours alone - unlike most settings, the host's value has no effect on you (how much stamina it costs IS host-controlled, though)."); CoverMouthHold = config.Bind("General", "cover-mouth-hold", true, "How cover-mouth-key behaves: on (default) means hold the key to keep your mouth covered and let go to stop; off makes it a toggle - press once to start, press again to stop. PER-PLAYER, same as cover-mouth-key above."); CoverMouthUseBonusStamina = config.Bind("General", "cover-mouth-use-bonus-stamina", false, "Whether covering your mouth is allowed to eat into your bonus stamina (the extra you get from food) once your normal stamina runs out. Off by default - covering simply stops when you run out of normal stamina, leaving your bonus intact for climbing. PER-PLAYER: this is your own call, like the keybind; how fast covering drains stamina in the first place is still the host's setting."); Seed = config.Bind("Host", "seed", 0, "Deterministic seed for every random decision Fairoots makes (which spore bombs get culled, etc.). Same seed + same Roots level = identical result, every load. HOST-AUTHORITATIVE: everyone in the lobby must have Fairoots installed, but only the HOST's seed is ever actually used - non-host players' own seed value here is ignored entirely, so there's nothing to coordinate manually (see ROADMAP.md's Host authority section)."); Preset = config.Bind("Host", "preset", PresetId.Balanced, "Overall balance preset. Subtle (1) is the lightest touch, Tame (4) the heaviest, Balanced (2) is the default. Custom (5) ignores the hard-coded preset numbers entirely and uses your own Spore-Bombs/Wind settings directly instead. Under presets 1-4, the per-mechanic settings below are ignored entirely, even if you've changed them, UNLESS apply-pure-preset (also in this Host section) is turned off - see that setting to change just one or two things about a preset without switching all the way to Custom. HOST-AUTHORITATIVE: only the host's preset (and, under Custom, the host's own per-mechanic values) is ever actually used for the whole lobby."); ApplyPurePreset = config.Bind("Host", "apply-pure-preset", true, "When a preset other than Custom is active (on by default): if true, the preset applies as a single all-or-nothing block, exactly as before - every per-mechanic Custom-only setting below is ignored, even if you've changed it. If false, the preset still supplies every setting you've left at its vanilla default, but any setting you've actually changed from its default keeps your own value instead - so you can pick, say, Subtle, turn this off, change only climb-shelters-from-wind, and get Subtle with exactly that one difference, without copying every one of Subtle's numbers into Custom first. Has no effect under Custom, where your own values already always apply. HOST-AUTHORITATIVE, like preset itself."); SporeBombCullFractionOverride = config.Bind("Spore-Bombs", "cull-fraction", 0.0, new ConfigDescription("Fraction of spore bombs to remove overall (foliage removal + seeded cull combined), e.g. 0.5 cuts them in half. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. 0 (the default) = remove none beyond whatever the foliage pass takes, which is itself off by default, so an untouched Custom preset removes nothing at all.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 1.0), Array.Empty())); EnableFoliageRemovalOverride = config.Bind("Spore-Bombs", "enable-foliage-removal", false, "Removes spore bombs that spawned hidden inside a bush or clump of ferns, because a bomb you physically cannot see before setting it off isn't a hazard you can play around. Off = vanilla, where the game leaves them wherever they landed. Turning it on does NOT mean fewer spore bombs overall than cull-fraction asks for: that removal target still applies, the seeded removal just picks the hidden ones first. Only takes effect when preset is set to Custom (5) - every preset 1-4 has it on. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby. Takes effect on the next Roots level load (spore-bomb removal happens once, when the biome is placed)."); SporeBombTriggerRadiusMultiplierOverride = config.Bind("Spore-Bombs", "trigger-radius-multiplier", 1.0, new ConfigDescription("Multiplier applied to every kept spore bomb's trigger hitbox, e.g. 0.7 shrinks it to 70% of vanilla size, 2.0 doubles it. 1.0 always means vanilla size, no matter what else you change - handy for A/B-testing against unmodified behavior. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); SporeBombKnockbackMultiplierOverride = config.Bind("Spore-Bombs", "knockback-multiplier", 1.0, new ConfigDescription("Multiplier applied to a spore bomb's knockback/explosion force on detonation, e.g. 0.6 cuts it to 60% of vanilla, 2.0 doubles it. 1.0 always means vanilla force, no matter what else you change - handy for A/B-testing against unmodified behavior. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 5.0), Array.Empty())); SporeBombScreenshakeRangeCapOverride = config.Bind("Spore-Bombs", "screenshake-range-cap-meters", 0.0, new ConfigDescription("Caps how far away (in meters) a spore-bomb detonation's screen-shake can still be felt. 0 leaves the vanilla range (~75m, uncapped) alone; a positive value caps it, e.g. 20 means no shake past 20m. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 100.0), Array.Empty())); SporeBombVfxCountMultiplierOverride = config.Bind("Spore-Bombs", "vfx-count-multiplier", 1.0, new ConfigDescription("Multiplier applied to a spore bomb's particle/VFX orb count on detonation, e.g. 0.5 halves it, 2.0 doubles it. 1.0 always means vanilla count, no matter what else you change - handy for A/B-testing against unmodified behavior. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 5.0), Array.Empty())); SporeBombTriggerHeightMultiplierOverride = config.Bind("Spore-Bombs", "trigger-height-multiplier", 1.0, new ConfigDescription("Multiplier controlling how high above its base a player can be and still set off a \"Spore Bomb\" or \"Poison Spore Bomb\" (not the round \"Explosive Spore Bomb\", which is unaffected) - fixes vanilla's oversized trigger sphere reaching absurdly far above the actual mushroom mesh, which made it impossible to jump over one without setting it off. 1.0 always means vanilla (cutoff disabled), no matter what else you change - lower values engage the fix more aggressively. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); SporeBombSporeAreaRadiusMultiplierOverride = config.Bind("Spore-Bombs", "spore-area-radius-multiplier", 1.0, new ConfigDescription("Multiplier applied to the radius (and proportionally, the inner/outer fade) of the temporary spore area a regular \"Spore Bomb\"/\"Poison Spore Bomb\" creates when triggered, e.g. 0.5 halves how far it reaches, 2.0 doubles it. 1.0 always means vanilla size. Doesn't affect the \"Explosive Spore Bomb\" variant, which has no spore area. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 5.0), Array.Empty())); CoverMouthBlocksSporeBombsOverride = config.Bind("Spore-Bombs", "cover-mouth-blocks-spore-bombs", false, "Whether covering your mouth (see General/cover-mouth-key) also protects you from the small spore cloud a spore bomb leaves behind when it goes off. Off by default: the mechanic is meant for the biome's spore areas, which you can see coming and choose to walk into, whereas a spore bomb is a surprise you've already set off. Either way this only stops the spores - the blast still knocks you around. Only takes effect when preset is set to Custom (5) - it is off under presets 1-4 as well, for now. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby."); DisableSporeAreas = config.Bind("Spore-Areas", "disable-spore-areas", false, "Master switch: when on, the Roots biome's spore areas (\"Mushroom Spore Clouds\") are removed entirely - no Spores status, no green screen filter, and the emitter mushroom in the middle of the cloud plus the cloud itself disappear with them. Doesn't touch the small temporary spore area a spore bomb leaves behind when it goes off (that's the Spore-Bombs section). HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby. Off by default; no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live."); SporeAreaRemovalFractionOverride = config.Bind("Spore-Areas", "removal-fraction", 0.0, new ConfigDescription("Fraction of the level's spore areas to remove entirely, e.g. 0.25 removes a quarter of them. Which ones is decided by the seed, and always starts with the spore area closest to another one - so overlapping clouds (the ones you can't get past without taking spores) get thinned first, and isolated ones you can simply walk around are left alone. 0 = remove none. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4, which remove none at all on Subtle/Balanced. Only applies on the next Roots level load either way, since a spore area that's already gone can't come back mid-level.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 1.0), Array.Empty())); SporeAreaRadiusMultiplierOverride = config.Bind("Spore-Areas", "radius-multiplier", 1.0, new ConfigDescription("Multiplier applied to how far every spore area reaches, e.g. 0.7 shrinks it to 70% of vanilla, 1.5 makes it half again as big. The visible cloud is resized to match, so what you can see is what actually gives you spores. 1.0 always means vanilla size (radius 16, about 26m across from the middle). How quickly the spores themselves are applied is a separate setting - this only changes the size. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); SporeAreaStatusRateMultiplierOverride = config.Bind("Spore-Areas", "status-rate-multiplier", 1.0, new ConfigDescription("Multiplier applied to how often/quickly you're given spores while standing in a spore area, e.g. 0.5 means the Spores meter fills half as fast, 2.0 twice as fast, 0 means a spore area never gives you spores at all (its cloud is still there - use disable-spore-areas to remove them outright). 1.0 always means vanilla. How far the area reaches is the separate radius-multiplier above; this only changes the rate. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); SporeClearTimeMultiplierOverride = config.Bind("Spores", "clear-time-multiplier", 1.0, new ConfigDescription("Multiplier applied to how long it takes for spores to wear off once you're out of them, e.g. 0.5 means they clear in half the time, 2.0 means twice as long. Covers the whole wait, including the short pause before the meter starts going down at all. 1.0 always means vanilla. This only affects recovery - how fast you GET spores is build-up-multiplier below. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05, 5.0), Array.Empty())); SporeBuildUpMultiplierOverride = config.Bind("Spores", "build-up-multiplier", 1.0, new ConfigDescription("Multiplier applied to every dose of spores you're given, no matter what gave it to you - a spore area, a spore bomb's cloud, or a zombie's bite. 0.5 means every dose counts for half, 0 means you never get spores at all, 2.0 means double. 1.0 always means vanilla. NOTE: this stacks on top of the per-hazard settings - if Spore-Areas/status-rate-multiplier is 0.5 and this is 0.5, standing in a spore area gives you a quarter of vanilla. Because of that this one is fully OPT-IN: it defaults to 1.0 (vanilla) and no preset changes it, so it does nothing at all until you set it yourself. The presets tune each hazard separately instead. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); DisableZombies = config.Bind("Creatures", "disable-zombies", false, "Master switch: when on, the Roots biome's mushroom zombies never spawn, and any already wandering around are removed. A zombie raised from a dead player is NOT affected - that's a player's death, not a hazard. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby (the game only ever spawns zombies on the host in the first place). Off by default; no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live."); DisableBeetles = config.Bind("Creatures", "disable-beetles", false, "Master switch: when on, the Roots biome's beetles (about 15 per level) are removed entirely - nothing to walk into, nothing to knock you off a ledge. Turning it back off brings back exactly the beetles this mod removed. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby. Off by default; no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live."); DisableSpiders = config.Bind("Creatures", "disable-spiders", false, "Master switch: when on, the Roots biome's ceiling spiders (about 90 per level) never drop and never grab you, and their webs and bodies are hidden. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby. Off by default; no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live."); ZombieSpeedMultiplierOverride = config.Bind("Creatures", "zombie-speed-multiplier", 1.0, new ConfigDescription("Multiplier applied to how fast mushroom zombies move, e.g. 0.5 makes them half as fast, 1.5 half again as fast. 1.0 always means vanilla. Affects both their walk and their sprint, since the sprint is a multiple of the same speed. 0 means a zombie can still notice you, turn towards you and bite you if you stand next to it, but never closes any distance. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); BeetleSpeedMultiplierOverride = config.Bind("Creatures", "beetle-speed-multiplier", 1.0, new ConfigDescription("Multiplier applied to how fast beetles move, e.g. 0.5 makes them half as fast, 1.5 half again as fast. 1.0 always means vanilla. 0 means a beetle still turns to face you and still hits you if you walk into it, but never chases. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); BeetleKnockbackMultiplierOverride = config.Bind("Creatures", "beetle-knockback-multiplier", 1.0, new ConfigDescription("Multiplier applied to how hard a beetle's hit throws you, e.g. 0.5 halves the shove, 0 means it doesn't move you at all. 1.0 always means vanilla. This is only the force - whether the hit knocks you off your feet is the separate creature-ragdoll-resistance-multiplier below. Zombies have no equivalent setting because the game gives them no knockback of their own: what a zombie does is ragdoll you (see that other setting) and shove you with its body, which is ordinary physics. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); CreatureRagdollMultiplierOverride = config.Bind("Creatures", "creature-ragdoll-multiplier", 1.0, new ConfigDescription("Multiplier applied to how long a beetle's hit or a zombie's bite knocks you off your feet, e.g. 0.5 gets you back up twice as fast. 0 means you are never ragdolled by either creature and always keep control. 1.0 always means vanilla (2 seconds for a beetle, 3 for a zombie bite). The hit still lands either way: you still take the injury and spores, and a beetle still shoves you (that's beetle-knockback-multiplier above) - this only decides whether you lose control of your character. Zombies have no separate knockback setting because this ragdoll IS what a zombie's hit costs you. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); ZombieDeaggroEnabledOverride = config.Bind("Creatures", "zombie-deaggro-enabled", false, "Whether zombies can ever lose track of you. In the unmodded game they CANNOT: once a zombie has seen you it chases you forever, at any distance, with no way to shake it. Turn this off to keep that vanilla behavior. Only takes effect when preset is set to Custom (5) - under presets 1-4 this is off on Subtle (which is meant to be vanilla) and on for the other three."); ZombieDeaggroMultiplierOverride = config.Bind("Creatures", "zombie-deaggro-multiplier", 1.0, new ConfigDescription("How hard it is to shake a zombie once it's after you. NOTE: unlike every other multiplier in this file, 1.0 does NOT mean vanilla - vanilla zombies never lose you at all, so there's no vanilla number to scale. Here 1.0 is the TOUGHEST setting (you must stay out of the zombie's sight for a full 30 seconds, or get about 120m away) and 0.1 is the most forgiving (about 3 seconds or 12m). Either escape works on its own. 0 is not allowed - a zombie that deaggros instantly is a disabled zombie, which is what disable-zombies is for. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1, 1.0), Array.Empty())); BeetleDeaggroMultiplierOverride = config.Bind("Creatures", "beetle-deaggro-multiplier", 1.0, new ConfigDescription("How hard it is to shake a beetle once it's after you, as a multiplier on the distance it will keep chasing you from. 1.0 means vanilla (about 22m in Roots), 0.5 is twice as easy to escape, 2.0 twice as hard. Unlike zombies, beetles DO give up on their own in the unmodded game - both by distance and by losing sight of you - so this only tunes how sticky that already is. It doesn't change how far away a beetle can first notice you. Once a beetle does give up, it ignores everyone for 5 seconds, so it can't instantly re-notice you. 0.1 is the minimum - a beetle that can never hold a target at all is a disabled beetle, which is what disable-beetles is for. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1, 3.0), Array.Empty())); ZombieKnockoutSecondsOverride = config.Bind("Creatures", "zombie-knockout-seconds", 0.0, new ConfigDescription("How many seconds a zombie is knocked out for when you hit it with a thrown item, the way you can already stun a spider by throwing something at it. The unmodded game already knocks a zombie down for about a second, so this mostly decides how long that lasts; 0 (the default) leaves the vanilla behaviour alone. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. Deliberately less than the 5 seconds a spider gets. Note the zombie also needs a moment to get up and re-orient afterwards, so the total time it's out of the fight is a few seconds longer than this. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 60.0), Array.Empty())); BeetleKnockoutSecondsOverride = config.Bind("Creatures", "beetle-knockout-seconds", 0.0, new ConfigDescription("How many seconds a beetle is knocked onto its back for when you hit it with a thrown item. Unlike zombies and spiders, beetles are completely immune to thrown items in the unmodded game - nothing happens at all - so this adds the interaction outright; 0 (the default) turns it back off. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. Shortest of the three on purpose: a beetle has a shell, so it should shrug off a thrown rock better than a zombie or a spider does. A knocked-out beetle can't chase or attack, and rights itself with its own flip animation afterwards. Only counts if the item is actually thrown hard - dropping something on it does nothing. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 60.0), Array.Empty())); CreatureKnockoutMinThrowSpeedOverride = config.Bind("Creatures", "creature-knockout-min-throw-speed", 36.0, new ConfigDescription("How fast a thrown item has to be travelling, in meters per second, to knock out a beetle or a zombie - so a genuine throw does it and gently lobbing or dropping something doesn't. Applies to both creatures, so \"a hard throw\" means the same thing for each. Lower it if throws that feel hard aren't landing, raise it if soft ones are. 0 means any contact counts at all, however gentle. Turn on Debug/enable-debug-logging to see the measured speed of each throw next to this threshold in the log, which is the easy way to pick a value. The default is set from measured throws: medium throws land around 23-31 m/s and near-full-strength ones around 37-43, and the default of 36 was picked by play-testing as the point where a knockout needs a genuinely committed throw. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 50.0), Array.Empty())); CreatureKnockoutMaxThrowDistanceOverride = config.Bind("Creatures", "creature-knockout-max-throw-distance", 12.0, new ConfigDescription("How close you have to be to a beetle or zombie, in meters, for a thrown item to knock it out. Together with creature-knockout-min-throw-speed above this is what the knockout costs you: a charged throw from close range, which takes time to wind up and usually loses you the item. Without a distance limit a hard throw is still fast a long way out, so you could pick creatures off from somewhere safe. 0 removes the distance requirement. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 200.0), Array.Empty())); BlowgunAffectsCreaturesOverride = config.Bind("Creatures", "blowgun-affects-creatures", false, "When on, shooting a creature with a blowgun dart takes it out of the fight: a zombie dies (exactly the way it already does on its own after two minutes, skeleton included), while a spider or a beetle is knocked out for a long time (see blowgun-creature-stun-seconds). Spiders and beetles get stunned rather than killed because the game has no death state for them at all. In the unmodded game a dart passes straight through a spider or beetle and merely poisons a zombie, which is what off (the default) restores. Only takes effect when preset is set to Custom (5) - every preset 1-4 has it on, since darts are consumable and the blowgun is uncommon, so this can't be spammed. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby."); BlowgunCreatureStunSecondsOverride = config.Bind("Creatures", "blowgun-creature-stun-seconds", 45.0, new ConfigDescription("How many seconds a blowgun dart knocks out a spider or a beetle for. Doesn't apply to zombies, which die outright instead; set this to 0 if you want darts to kill zombies but leave spiders and beetles alone. Much longer than a thrown item's knockout on purpose - a dart costs you a consumable and only works if you actually hit. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 600.0), Array.Empty())); ZombieWindMultiplierOverride = config.Bind("Creatures", "zombie-wind-multiplier", 1.0, new ConfigDescription("Multiplier on how hard the wind pushes zombies around, e.g. 2.0 pushes them twice as hard as normal, 0 makes them immune. 1.0 means vanilla - note that vanilla is NOT zero: the game already pushes zombies at 60% of the force it uses on you, because a zombie counts as a bot character. Useful for making a storm a real hazard for the things chasing you and not just for you. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 5.0), Array.Empty())); BeetleWindSusceptibilityOverride = config.Bind("Creatures", "beetle-wind-susceptibility", 0.0, new ConfigDescription("How much the wind slides beetles around, as a fraction of their own walking speed - 1.0 means wind moves a beetle about as fast as it walks, 0.5 half that. NOTE: 0 is the vanilla value here, not 1.0, because beetles are completely immune to wind in the unmodded game and can't be made otherwise by scaling anything - the game resets a walking beetle's velocity every physics tick, so any wind force on it is erased before it moves. Set 0 to restore that. Only applies while a beetle is walking normally, never while it's tumbling, flipped or knocked out. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 5.0), Array.Empty())); EnableCoverMouthOverride = config.Bind("Spore-Areas", "enable-cover-mouth", false, "Master switch for the cover-your-mouth mechanic: hold the key (see General/cover-mouth-key) to breathe through your hands and take no spores while inside a spore area, at the cost of stamina and both hands. Off = vanilla, where the key does nothing at all. Only takes effect when preset is set to Custom (5) - every preset 1-4 has it on. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all; only the host's value counts for the whole lobby. If you just don't want to use the mechanic yourself, set cover-mouth-key to None in General instead - that works regardless of the host. Applies immediately, regardless of apply-changes-live."); CoverMouthStaminaPerSecondOverride = config.Bind("Spore-Areas", "cover-mouth-stamina-per-second", 0.03f, new ConfigDescription("How much stamina covering your mouth drains per second. Small by default - for scale, climbing a wall costs up to 0.2 per second, so this is about a sixth of that. You stop covering automatically when you run out of stamina. 0 makes it free. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4, which get cheaper the more forgiving they are. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby (the keybind itself is per-player - see General).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.5f), Array.Empty())); DisableWindEntirely = config.Bind("Wind", "disable-wind-entirely", false, "Master switch: when on, wind should NEVER occur at all - not vanilla-strength wind, genuinely no wind, ever. HOST-AUTHORITATIVE: if you're not the host, this has no effect at all - only the host's value counts for the whole lobby (same for everyone in the run, no exceptions - see ROADMAP.md's Host authority section). Any gust already blowing stops immediately when the host turns this on, and none can start again while it stays on. Off by default - no preset ever turns this on automatically. Applies immediately, regardless of apply-changes-live."); WindBackpackAlwaysImmuneOverride = config.Bind("Wind", "backpack-always-immune", false, "Whether backpacks are fully immune to wind force, regardless of item-force-multiplier. Off = vanilla, where a gust sends a dropped backpack down the mountain like anything else. Only takes effect when preset is set to Custom (5) - every preset 1-4 has it on. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby, regardless of what non-host players have set locally. Applies immediately, regardless of apply-changes-live."); WindForceMultiplierOverride = config.Bind("Wind", "force-multiplier", 1.0, new ConfigDescription("Multiplier applied to wind's push force only (see gust-duration-multiplier below for timing), e.g. 0.6 cuts it to 60% of vanilla, 2.0 doubles it, 0 means no push at all. 1.0 always means vanilla force, no matter what else you change. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); WindGustDurationMultiplierOverride = config.Bind("Wind", "gust-duration-multiplier", 1.0, new ConfigDescription("Multiplier applied to how long a gust lasts once it starts (in the same direction, the calm period between gusts scales inversely) - independent of force-multiplier above, so you can test gust timing/frequency without changing push strength. E.g. 0.6 makes gusts noticeably shorter and less frequent. 1.0 always means vanilla timing. Gust duration is always floored at 1 second regardless of how low this goes - a genuinely zero-length gust breaks the game's own wind on/off timer (confirmed live 2026-07-22). Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); WindItemForceMultiplierOverride = config.Bind("Wind", "item-force-multiplier", 1.0, new ConfigDescription("Multiplier applied to wind's push force on dropped items other than backpacks (backpacks are always fully immune to wind, on every preset), e.g. 0.4 cuts it to 40% of vanilla, 0.0 makes every item fully immune too. 1.0 always means vanilla force. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 3.0), Array.Empty())); WindObstacleOcclusionRangeMultiplierOverride = config.Bind("Wind", "obstacle-occlusion-range-multiplier", 1.0, new ConfigDescription("Multiplier applied to the existing obstacle-occlusion raycast's min/max distance (already enabled in Roots vanilla) - widening it lets standing behind an obstacle block wind from further away, e.g. 1.6 widens both distances by 60%. 1.0 always means vanilla range. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5, 4.0), Array.Empty())); WindFallCameraDampenClampOverride = config.Bind("Wind", "fall-camera-dampen-clamp", 0.0, new ConfigDescription("Floor applied to camera-control while falling, but only when the fall was preceded by recent wind force (see fall-camera-dampen-window-seconds below) - keeps the camera partially player-controlled instead of fully surrendering to ragdoll-head physics, so you have a chance to grab a wall or use a Rescue Hook after wind blows you off a ledge. 0 disables it (vanilla - full ragdoll camera on every fall). An ordinary fall that wasn't wind-preceded is never affected. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 1.0), Array.Empty())); WindRecentForceWindowSecondsOverride = config.Bind("Wind", "fall-camera-dampen-window-seconds", 1.5f, new ConfigDescription("How many seconds after wind last pushed you that a fall still counts as wind-preceded for fall-camera-dampen-clamp above and for prevent-wind-ragdoll below. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4, and it does nothing while both of those settings are off anyway. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())); PreventWindRagdollOverride = config.Bind("Wind", "prevent-wind-ragdoll", false, "Whether wind is allowed to ragdoll you. On (every preset 1-4): when wind blows you off a ledge you keep full control of your character on the way down, so you can grab a wall or use a Rescue Hook instead of flailing helplessly. Off: vanilla, where being pushed off an edge collapses you into physics - fall-camera-dampen-clamp above then still softens it partway, if the preset sets one. Only ever applies to a fall wind actually caused (see fall-camera-dampen-window-seconds); an ordinary fall you walked into yourself ragdolls exactly like vanilla either way. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby. Applies immediately, regardless of apply-changes-live."); ClimbSheltersFromWindOverride = config.Bind("Wind", "climb-shelters-from-wind", false, "Whether holding onto something shelters you from wind: while climbing a wall, a rope, a vine or a climb handle, wind can't push you at all - instead climbing gets much slower for as long as the wind is actually blowing on you (see the three climb-*-multiplier settings below). Vanilla only shelters you on a climb handle, so a gust mid-climb normally rips you off the wall, which is why walking into the wind is the only reliable tactic. Off = vanilla, and the default. Only takes effect when preset is set to Custom (5) - under presets 1-4 it is on everywhere except Subtle (1). If the wind can't reach you anyway - behind a rock, no gust - you climb at full speed, so this never costs you anything when it isn't protecting you. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby. Applies immediately, regardless of apply-changes-live."); ClimbWindSpeedMultiplierOverride = config.Bind("Wind", "climb-speed-multiplier-in-wind", 1.0, new ConfigDescription("How fast you climb while wind is pushing on you, as a fraction of normal climbing speed - the price of climb-shelters-from-wind above. E.g. 0.90 means a tenth slower at full wind pressure; 1.0 means no slowdown at all (free shelter). Fades in with how hard the wind is actually blowing, so partial cover means only a partial slowdown. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05, 1.0), Array.Empty())); ClimbWindUpwardSpeedMultiplierOverride = config.Bind("Wind", "climb-upward-speed-multiplier-in-wind", 1.0, new ConfigDescription("Extra slowdown on climbing UPWARD in wind, multiplied on top of climb-speed-multiplier-in-wind above (so 0.90 and 0.85 together mean about three quarters of normal speed climbing up through a full-strength gust). Climbing down is never slowed beyond the base multiplier. 1.0 means upward climbing costs no more than any other direction. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05, 1.0), Array.Empty())); ClimbWindIntoWindSpeedMultiplierOverride = config.Bind("Wind", "climb-into-wind-speed-multiplier", 1.0, new ConfigDescription("Extra slowdown on climbing INTO the wind (toward where it's blowing from), multiplied on top of climb-speed-multiplier-in-wind above. Climbing with the wind is never sped up. 1.0 means direction doesn't matter. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05, 1.0), Array.Empty())); ClimbShelterGraceSecondsOverride = config.Bind("Wind", "climb-shelter-grace-seconds", 0.5f, new ConfigDescription("How long wind stays much weaker after you let go of a climb - the window that stops finishing a climb mid-gust from catapulting you, and gives you time to start sprinting away or re-grab the wall if you let go by accident. Wind is held at climb-shelter-grace-force-multiplier below for most of the window, then ramps back to full over the tail of it so it doesn't end in a sudden shove. Set to 0 to switch the window off entirely (full wind force the instant you let go, like vanilla). Only takes effect when preset is set to Custom (5) - ignored under presets 1-4, and it does nothing while the climb shelter itself is off. HOST-AUTHORITATIVE: only the host's value counts for the whole lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); ClimbWindGraceForceMultiplierOverride = config.Bind("Wind", "climb-shelter-grace-force-multiplier", 1.0, new ConfigDescription("How strong wind is during the grace window above, as a fraction of normal - e.g. 0.15 means 15% force, close to immune but not quite. Deliberately not 0: full immunity would let you tap a wall over and over to cross an exposed stretch wind-free, turning the climb shelter into a movement exploit. 1.0 means no reduction at all. Only takes effect when preset is set to Custom (5) - ignored under presets 1-4.", (AcceptableValueBase)(object)new AcceptableValueRange(0.0, 1.0), Array.Empty())); ApplyChangesLive = config.Bind("Debug", "apply-changes-live", false, "OFF by default, and off is the fast path: Fairoots resolves every gameplay value once as the Roots biome loads and then leaves it alone for the rest of the run, which is why this lives in Debug - it is a testing convenience, not something normal play needs. Turn it on to have settings take effect the instant you change them in-game (e.g. via PEAKLib.ModConfig): kept spore bombs resize live, the next detonation uses the new knockback/VFX/shake numbers, and the jump-over-height cutoff updates immediately - useful while tuning, at the cost of re-resolving those values as the game asks for them instead of reading one frozen set. IMPORTANT: this is read once, when a Roots biome loads. Switching it on while already standing in Roots does nothing until the next Roots load - so turn it on first, then load in. The spore-bomb removal fraction and the seed are always level-load-only either way, since which spore bombs were already removed can't be undone mid-level; the spore-bomb recolor and the cloud-opacity sliders are per-player cosmetics and always immediate either way. This is a behavior override, not a diagnostic, so it works regardless of the debug-logging switch below."); EnableDebugLogging = config.Bind("Debug", "enable-debug-logging", false, "Master switch for Fairoots' verbose diagnostic logging. When on, the mod reports what it can and can't find in a loaded Roots level (spore bombs, wind zone, spore areas, zombies) so you can see what's working. Leave off for normal play - it's noisy and only useful for development/bug reports."); LogSceneScanOnLoad = config.Bind("Debug", "log-scene-scan-on-load", false, "When debug logging is on, automatically dump a scene diagnostics report each time a Roots biome finishes loading. Off by default, like everything else in this section - the dump walks the whole level, so it belongs behind a deliberate choice rather than riding along with the logging switch. Leave it off and trigger the report manually with the hotkey below instead."); LogScreenshakeSources = config.Bind("Debug", "log-screenshake-sources", false, "When debug logging is on, log every camera shake the game queues along with the code that asked for it, and how far away the source was. Use this to work out why a shake you expected to be distance-capped still happened. Very noisy (the game shakes the camera constantly while climbing) - leave off unless you're chasing a specific shake."); SceneScanHotkey = config.Bind("Debug", "scene-scan-hotkey", (KeyCode)0, "When debug logging is on, press this key in-game to dump a scene diagnostics report on demand (e.g. right when you're standing next to a spore bomb). Set to None to disable the hotkey."); FoliageProbeHotkey = config.Bind("Debug", "foliage-probe-hotkey", (KeyCode)0, "When debug logging is on, press this key while standing next to a spore bomb visibly camouflaged in bush/grass to probe it for a foliage-detection method (grass-blade density, nearby colliders/renderers). Research tool for Phase 4 (ROADMAP.md); not part of the shipped cull feature. Set to None to disable the hotkey."); MaterialProbeHotkey = config.Bind("Debug", "material-probe-hotkey", (KeyCode)0, "When debug logging is on, look at something and press this key to dump its material and shader setup: every color slot the shader declares, its value, and whether Fairoots is currently overriding it. Use it to work out why something looks miscolored - the report says outright whether the mod touched a given object or not. Set to None to disable the hotkey."); ShowRemovedSporeBombMarkers = config.Bind("Debug", "show-removed-spore-bomb-markers", false, "When debug logging is on, draw a 2D on-screen label over every spot a spore bomb was removed this level load, tagged by why (foliage vs. seeded cull) - useful for eyeballing what the cull actually touched. Off by default."); ShowSporeBombTriggerRadius = config.Bind("Debug", "show-spore-bomb-trigger-radius", false, "When debug logging is on, draw a red 3D wireframe around every kept spore bomb's current trigger hitbox (matching its exact shape/size after the configured radius multiplier is applied) for spore bombs within 10m - useful for eyeballing how much the trigger box was actually shrunk against the real prefab. Off by default."); CoverMouthPosePreview = config.Bind("Debug", "cover-mouth-pose-preview", false, "Holds the cover-your-mouth pose on permanently so you can tune the pose settings below without having to keep the key held down while you edit them. This is PURELY VISUAL: it doesn't make you immune to spores, doesn't drain stamina, doesn't tie up your hands, and other players don't see it - the mechanic itself behaves exactly as normal while this is on. Off by default."); CoverMouthPoseEmote = config.Bind("Debug", "cover-mouth-pose-emote", string.Empty, "Which animation the cover-your-mouth pose borrows its hand and finger shape from. Leave empty to auto-pick the \"it's so over\" emote, which is what the pose is designed around. With debug logging on, every available emote is listed in the log the first time you cover your mouth, so you can copy an exact name from there if you'd rather use a different one. Pose-tuning knob."); CoverMouthPoseEmoteTime = config.Bind("Debug", "cover-mouth-pose-emote-time", 0.6f, new ConfigDescription("Which moment of the pose animation to hold, from 0 (its first frame) to 1 (its last). The pose is frozen on a single frame rather than played, so this picks which one. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); CoverMouthPoseForwardCm = config.Bind("Debug", "cover-mouth-pose-forward-cm", 80f, new ConfigDescription("How far in front of the face your hands are held while covering your mouth, in centimetres. Default is the maintainer's own playtest-tuned value. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 80f), Array.Empty())); CoverMouthPoseBelowHeadCm = config.Bind("Debug", "cover-mouth-pose-below-head-cm", 0f, new ConfigDescription("How far below eye level your hands are held while covering your mouth, in centimetres - i.e. mouth height. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange(-20f, 40f), Array.Empty())); CoverMouthPoseHandGapCm = config.Bind("Debug", "cover-mouth-pose-hand-gap-cm", 7f, new ConfigDescription("Vertical gap between your two hands while covering your mouth, in centimetres - they're stacked one above the other so they don't intersect. Default is the maintainer's own playtest-tuned value. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 40f), Array.Empty())); CoverMouthPoseSideCm = config.Bind("Debug", "cover-mouth-pose-side-cm", 13f, new ConfigDescription("How far to each side of centre your hands sit while covering your mouth, in centimetres. Near 0 means both hands are on the centre line, which is what makes the pose read as covering your mouth rather than gesturing. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 25f), Array.Empty())); CoverMouthPoseHandYawDeg = config.Bind("Debug", "cover-mouth-pose-hand-yaw-deg", 0f, new ConfigDescription("Turns both hands outward (or inward) while covering your mouth, in degrees - this is the one that decides where your thumbs point. 0 leaves the hands as the borrowed animation has them, which is palm-to-palm like praying; turning them opens the palms toward your face so the thumbs point away from it. Mirrored, so the two hands always stay symmetric. The sign is what decides which side of your hands faces you: negative turns the palms toward your face (correct for covering your mouth), positive turns them away. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange(-180f, 180f), Array.Empty())); CoverMouthPoseHandRollDeg = config.Bind("Debug", "cover-mouth-pose-hand-roll-deg", 10f, new ConfigDescription("Tips both hands so the thumbs ride higher or lower while covering your mouth, in degrees - use it with cover-mouth-pose-hand-yaw-deg to aim the thumbs up-and-out rather than straight out. Mirrored between the two hands; negate it if the thumbs end up pointing down instead of up. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange(-180f, 180f), Array.Empty())); CoverMouthPoseHandPitchDeg = config.Bind("Debug", "cover-mouth-pose-hand-pitch-deg", -10f, new ConfigDescription("Tilts both hands' fingers up or down while covering your mouth, in degrees. Not mirrored - both hands tilt the same way. Pose-tuning knob.", (AcceptableValueBase)(object)new AcceptableValueRange(-180f, 180f), Array.Empty())); KeepVanillaTriggerRadius = config.Bind("Debug", "keep-vanilla-trigger-radius", false, "For before/after comparison screenshots: when on, spore-bomb trigger hitboxes are left at their original (vanilla) size instead of being shrunk by the configured trigger-radius multiplier, and the trigger-height cutoff (trigger-height-multiplier) is bypassed too, so jumping over one behaves like vanilla again. This is a gameplay override, so it takes effect regardless of the debug-logging master switch above. Off by default."); } internal void CaptureLevelSnapshot() { _snapCullFraction = SporeBombCullFraction; _snapTriggerRadiusMultiplier = SporeBombTriggerRadiusMultiplier; _snapKnockbackMultiplier = SporeBombKnockbackMultiplier; _snapScreenshakeRangeCapMeters = SporeBombScreenshakeRangeCapMeters; _snapVfxCountMultiplier = SporeBombVfxCountMultiplier; _snapTriggerHeightMultiplier = SporeBombTriggerHeightMultiplier; _snapSporeAreaRadiusMultiplier = SporeBombSporeAreaRadiusMultiplier; _snapSporeAreaRemovalFraction = SporeAreaRemovalFraction; _snapSporeAreaRadiusMultiplierValue = SporeAreaRadiusMultiplier; _snapSporeAreaStatusRateMultiplier = SporeAreaStatusRateMultiplier; _snapSporeClearTimeMultiplier = SporeClearTimeMultiplier; _snapSporeBuildUpMultiplier = SporeBuildUpMultiplier; _snapZombieSpeedMultiplier = ZombieSpeedMultiplier; _snapBeetleSpeedMultiplier = BeetleSpeedMultiplier; _snapBeetleKnockbackMultiplier = BeetleKnockbackMultiplier; _snapCreatureRagdollMultiplier = CreatureRagdollMultiplier; _snapZombieDeaggroEnabled = ZombieDeaggroEnabled; _snapZombieDeaggroMultiplier = ZombieDeaggroMultiplier; _snapBeetleDeaggroMultiplier = BeetleDeaggroMultiplier; _snapWindForceMultiplier = WindForceMultiplier; _snapWindGustDurationMultiplier = WindGustDurationMultiplier; _snapWindItemForceMultiplier = WindItemForceMultiplier; _snapWindObstacleOcclusionRangeMultiplier = WindObstacleOcclusionRangeMultiplier; _snapWindFallCameraDampenClamp = WindFallCameraDampenClamp; _snapClimbWindSpeedMultiplier = ClimbWindSpeedMultiplier; _snapClimbWindUpwardSpeedMultiplier = ClimbWindUpwardSpeedMultiplier; _snapClimbWindIntoWindSpeedMultiplier = ClimbWindIntoWindSpeedMultiplier; _snapClimbWindGraceForceMultiplier = ClimbWindGraceForceMultiplier; _snapLiveUpdates = ApplyChangesLive.Value; _snapshotTaken = true; } } public static class PluginInfo { public const string Guid = "OnlyCook.Fairoots"; public const string Name = "Fairoots"; public const string Version = "1.0.0"; } internal static class RootsLevelWatcher { private const float OverlayFadeSpeed = 6f; private static Transform _processed; private static bool _levelLoaded; private static Coroutine _setup; internal static bool RootsBiomeIsLive => IsInLiveRootsBiome(); internal static void CheckAndRun() { bool flag = IsInLiveRootsBiome(); if (_levelLoaded && (!flag || (Object)(object)_processed == (Object)null)) { ExitLevel(); } else if (!_levelLoaded && flag && !((Object)(object)Character.localCharacter == (Object)null)) { Transform val = ResolveSegmentRoot(); if (!((Object)(object)val == (Object)null)) { EnterLevel(val); } } } private static bool IsInLiveRootsBiome() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 try { if (!MapHandler.ExistsAndInitialized) { return false; } if ((int)Singleton.Instance.GetCurrentBiome() != 7) { return false; } MapSegment currentMapSegment = MapHandler.CurrentMapSegment; GameObject val = ((currentMapSegment != null) ? currentMapSegment.segmentParent : null); return (Object)(object)val != (Object)null && val.activeInHierarchy; } catch (Exception ex) { Diag.Error("[RootsLevelWatcher] biome check threw: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static Transform ResolveSegmentRoot() { GameObject val = GameObject.Find("Roots Segment"); if ((Object)(object)val != (Object)null && val.activeInHierarchy) { return val.transform; } MapSegment currentMapSegment = MapHandler.CurrentMapSegment; GameObject val2 = ((currentMapSegment != null) ? currentMapSegment.segmentParent : null); if ((Object)(object)val2 == (Object)null) { return null; } Diag.Warn("[RootsLevelWatcher] no active \"Roots Segment\" object found - falling back to the biome's segment parent \"" + ((Object)val2).name + "\". If spore-bomb counts look wrong, this is why."); return val2.transform; } private static void EnterLevel(Transform rootsSegment) { _processed = rootsSegment; _levelLoaded = true; RootsState.EnterLevel(); Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null) { Diag.Warn("[RootsLevelWatcher] no plugin instance to drive the setup coroutine - running the passes inline."); RunPassesInline(rootsSegment); RootsState.SetupFinished(); } else { _setup = ((MonoBehaviour)instance).StartCoroutine(RunSetup(rootsSegment)); } } private static void ExitLevel() { _levelLoaded = false; _processed = null; if (_setup != null && (Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StopCoroutine(_setup); } _setup = null; RootsLoadingOverlay.Hide(); RootsState.ExitLevel(); RestoreVanilla(); ClearLevelState(); } private static void RestoreVanilla() { Run("wind", WindChillZoneTuningPatch.ReapplyAll); Run("spore recovery", SporeDecayPatch.ReapplyToAll); Run("creature speed", CreatureSpeedPatch.ReapplyToAll); Run("creature knockback", CreatureKnockbackPatch.ReapplyToAll); Run("creature ragdoll", CreatureRagdollPatch.ReapplyToAll); Run("creature kill switches", CreatureDisablePatch.ReapplyToAll); Run("spore-area kill switch", SporeAreaDisablePatch.ReapplyToAll); Diag.Info("[RootsLevelWatcher] Roots Segment unloaded - Fairoots is inactive and vanilla values are restored."); } private static void ClearLevelState() { SporeBombCullPatch.RemovedPositions.Clear(); SporeBombCullPatch.KeptTriggerColliders.Clear(); SporeAreaDisablePatch.ClearLevelState(); SporeAreaCullPatch.ClearLevelState(); CoverMouthImmunityPatch.ClearLevelState(); CreatureDisablePatch.ClearLevelState(); CreatureSpeedPatch.ClearLevelState(); CreatureKnockbackPatch.ClearLevelState(); CreatureRagdollPatch.ClearLevelState(); BeetleDeaggroPatch.ClearLevelState(); ZombieAggroLogPatch.ClearLevelState(); CreatureAggroLog.ClearLevelState(); SpiderStrikeWarning.ClearLevelState(); CreatureKnockoutPatch.ClearLevelState(); SpiderStunIndicatorPatch.ClearLevelState(); SporeBombCloudWarning.ClearLevelState(); SporePresence.ClearLevelState(); } private static IEnumerator RunSetup(Transform rootsSegment) { try { RootsLoadingOverlay.Show(); while (!RootsLoadingOverlay.Fade(1f, 6f)) { yield return null; } yield return (object)new WaitForEndOfFrame(); Action[] array = SetupSteps(rootsSegment); for (int i = 0; i < array.Length; i++) { array[i](); yield return null; } while (!RootsLoadingOverlay.Fade(0f, 6f)) { yield return null; } } finally { RootsLoadingOverlay.Hide(); RootsState.SetupFinished(); _setup = null; } } private static void RunPassesInline(Transform rootsSegment) { Action[] array = SetupSteps(rootsSegment); for (int i = 0; i < array.Length; i++) { array[i](); } } private static Action[] SetupSteps(Transform rootsSegment) { return new Action[13] { delegate { Plugin.Cfg.CaptureLevelSnapshot(); HostAuthority.PublishAll(); DetonationScreenshakeRegistry.Clear(); }, delegate { SporeBombCullPatch.Run(rootsSegment); }, delegate { SporeAreaCullPatch.Run(rootsSegment); }, delegate { SporeAreaDisablePatch.Run(rootsSegment); }, delegate { SporeAreaTuningPatch.Run(rootsSegment); }, delegate { SporeCloudOpacityPatch.Run(rootsSegment); }, WindChillZoneTuningPatch.ReapplyAll, delegate { CreatureDisablePatch.Run(rootsSegment); }, CreatureSpeedPatch.ReapplyToAll, CreatureKnockbackPatch.ReapplyToAll, CreatureRagdollPatch.ReapplyToAll, SporeDecayPatch.ReapplyToAll, delegate { SporePresence.CaptureLevel(rootsSegment); if (Plugin.Cfg.EnableDebugLogging.Value && Plugin.Cfg.LogSceneScanOnLoad.Value) { SceneDiagnostics.AutoDump("Roots Segment detected"); } } }; } private static void Run(string what, Action pass) { try { pass(); } catch (Exception ex) { Diag.Error("[RootsLevelWatcher] restoring " + what + " threw: " + ex.GetType().Name + ": " + ex.Message); } } } internal static class RootsState { internal static bool Active { get; private set; } internal static bool Busy { get; private set; } internal static void EnterLevel() { Active = true; Busy = true; } internal static void SetupFinished() { Busy = false; } internal static void ExitLevel() { Active = false; Busy = false; } } public static class FairootsInterop { public static bool RootsActive => RootsState.Active; public static bool ShouldHoldLoadingScreen() { if (RootsState.Busy) { return true; } if (RootsState.Active) { return false; } return RootsLevelWatcher.RootsBiomeIsLive; } } internal static class SporePresence { private static readonly List LevelAreas = new List(); internal static void CaptureLevel(Transform rootsSegment) { LevelAreas.Clear(); LevelAreas.AddRange(SporeAreaScan.FilterSporeAreas(((Component)rootsSegment).GetComponentsInChildren(true))); } internal static void ClearLevelState() { LevelAreas.Clear(); } internal static bool InAnySpores() { if (!InSporeArea()) { return SporeBombCloudWarning.InBombCloud(); } return true; } internal static bool InSporeArea() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || LevelAreas.Count == 0) { return false; } Vector3 center = localCharacter.Center; for (int i = 0; i < LevelAreas.Count; i++) { StatusEmitter val = LevelAreas[i]; if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled && !val.emitterDisabledByWind && !(val.amount <= 0f) && Vector3.Distance(center, ((Component)val).transform.position) < val.radius + val.outerFade) { return true; } } return false; } } } namespace Fairoots.Wind { internal static class ClimbWindShelter { private static float _pressure; private static Vector3 _windDirection = Vector3.zero; private static float _recordedAt = -1f; private static float _lastHeldOnAt = -1f; private static bool _loggedSheltered; private static bool _loggedGrace; internal static bool Enabled { get { if (RootsState.Active && Plugin.Cfg != null && !Plugin.Cfg.EffectiveDisableWindEntirely) { return Plugin.Cfg.EffectiveClimbSheltersFromWind; } return false; } } internal static bool IsHoldingOn(Character character) { if ((Object)(object)character == (Object)null || (Object)(object)character.data == (Object)null) { return false; } CharacterData data = character.data; if (!data.isClimbing && !data.isRopeClimbing && !data.isVineClimbing) { return (Object)(object)data.currentClimbHandle != (Object)null; } return true; } internal static void NoteHoldingOn() { _lastHeldOnAt = Time.time; } internal static float GraceForceMultiplier() { if (!Enabled) { return 1f; } float num = ClimbWindResistance.GraceForceMultiplier(_lastHeldOnAt, Time.time, Plugin.Cfg.EffectiveClimbShelterGraceSeconds, Plugin.Cfg.EffectiveClimbWindGraceForceMultiplier); if (Diag.Enabled && num < 1f != _loggedGrace) { _loggedGrace = num < 1f; Diag.V(string.Format("[ClimbWindShelter] let-go grace window {0} (wind ×{1:0.###})", _loggedGrace ? "started" : "ended", num)); } return num; } internal static void Record(float pressure, Vector3 windDirection) { //IL_000b: 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) _pressure = ClimbWindResistance.ClampPressure(pressure); _windDirection = windDirection; _recordedAt = Time.time; if (Diag.Enabled && _pressure > 0f != _loggedSheltered) { _loggedSheltered = _pressure > 0f; Diag.V(string.Format("[ClimbWindShelter] climbing wind pressure {0} ({1:0.###})", _loggedSheltered ? "engaged" : "released", _pressure)); } } internal static bool TryGetPressure(out float pressure, out Vector3 windDirection) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) pressure = 0f; windDirection = Vector3.zero; if (!Enabled || !ClimbWindResistance.IsPressureCurrent(_recordedAt, Time.time) || _pressure <= 0f) { return false; } pressure = _pressure; windDirection = _windDirection; return true; } internal static bool IsLocalClimber(Character character) { if ((Object)(object)character != (Object)null && (Object)(object)Character.localCharacter != (Object)null) { return (Object)(object)character == (Object)(object)Character.localCharacter; } return false; } internal static float FlatSlowdown(Character character, float pressure) { bool flag = (Object)(object)character.input != (Object)null && character.input.movementInput.y > 0.01f; return ClimbWindResistance.Resist(new ClimbMove(0f, 1f), 0f, 0f, pressure, Plugin.Cfg.EffectiveClimbWindSpeedMultiplier, flag ? Plugin.Cfg.EffectiveClimbWindUpwardSpeedMultiplier : 1.0, 1.0).Up; } } [HarmonyPatch(typeof(WindChillZone), "AddWindForceToCharacter")] internal static class ClimbWindShelterPatch { private static bool Prefix(WindChillZone __instance, Character character, float mult, Vector3 ___currentWindDirection, out float __state) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) __state = __instance.windForce; try { if (!ClimbWindShelter.Enabled) { return true; } if ((Object)(object)character == (Object)null || (Object)(object)((MonoBehaviourPun)character).photonView == (Object)null || !((MonoBehaviourPun)character).photonView.IsMine) { return true; } bool flag = ClimbWindShelter.IsLocalClimber(character); if (ClimbWindShelter.IsHoldingOn(character)) { if (flag) { ClimbWindShelter.NoteHoldingOn(); ClimbWindShelter.Record(ComputePressure(__instance, character, mult, ___currentWindDirection), ___currentWindDirection); } return false; } if (flag) { float num = ClimbWindShelter.GraceForceMultiplier(); if (num < 1f) { __instance.windForce = __state * num; } } return true; } catch (Exception ex) { Diag.Error("[ClimbWindShelter] prefix threw: " + ex.GetType().Name + ": " + ex.Message); return true; } } private static void Postfix(WindChillZone __instance, float __state) { __instance.windForce = __state; } private static float ComputePressure(WindChillZone zone, Character character, float mult, Vector3 windDirection) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) float num = (zone.useIntensityCurve ? (Mathf.Clamp01(zone.windIntensity - 0.5f) * 2f) : 1f); Affliction val = default(Affliction); if (character.refs.afflictions.HasAfflictionType((AfflictionType)17, ref val)) { num = 0f; } RaycastHit val2 = default(RaycastHit); if (zone.useRaycast && Physics.Raycast(character.Center, -windDirection, ref val2, zone.maxRaycastDistance, LayerMask.op_Implicit(HelperFunctions.GetMask((LayerType)1)))) { float num2 = Mathf.InverseLerp(zone.minRaycastDistance, zone.maxRaycastDistance, ((RaycastHit)(ref val2)).distance); float num3 = Mathf.Clamp01(Vector3.Dot(((RaycastHit)(ref val2)).normal, windDirection)); num *= Mathf.Clamp01(num2 + 1f - num3); } return ClimbWindResistance.ClampPressure(num * mult * zone.windPlayerFactor); } } [HarmonyPatch(typeof(CharacterClimbing), "GetRequestedPostition")] internal static class ClimbWindWallSlowdownPatch { private static void Postfix(Character ___character, ref Vector3 __result) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)___character == (Object)null || !ClimbWindShelter.IsLocalClimber(___character) || !ClimbWindShelter.TryGetPressure(out var pressure, out var windDirection)) { return; } Vector3 climbPos = ___character.data.climbPos; Vector3 val = __result - climbPos; Vector3 climbNormal = ___character.data.climbNormal; Vector3 val2 = Vector3.ProjectOnPlane(Vector3.up, climbNormal); if (((Vector3)(ref val2)).sqrMagnitude < 1E-06f) { float num = ClimbWindShelter.FlatSlowdown(___character, pressure); __result = climbPos + val * num; return; } val2 = ((Vector3)(ref val2)).normalized; Vector3 val3 = Vector3.Cross(val2, climbNormal); Vector3 normalized = ((Vector3)(ref val3)).normalized; float num2 = Vector3.Dot(val, val2); float num3 = Vector3.Dot(val, normalized); Vector3 val4 = val - val2 * num2 - normalized * num3; Vector3 val5 = Vector3.ProjectOnPlane(windDirection, climbNormal); if (((Vector3)(ref val5)).sqrMagnitude > 1E-06f) { val5 = ((Vector3)(ref val5)).normalized; } ClimbMove climbMove = ClimbWindResistance.Resist(new ClimbMove(num3, num2), Vector3.Dot(val5, normalized), Vector3.Dot(val5, val2), pressure, Plugin.Cfg.EffectiveClimbWindSpeedMultiplier, Plugin.Cfg.EffectiveClimbWindUpwardSpeedMultiplier, Plugin.Cfg.EffectiveClimbWindIntoWindSpeedMultiplier); __result = climbPos + val2 * climbMove.Up + normalized * climbMove.Lateral + val4; } catch (Exception ex) { Diag.Error("[ClimbWindShelter] wall-climb postfix threw: " + ex.GetType().Name + ": " + ex.Message); } } } [HarmonyPatch(typeof(CharacterRopeHandling), "Update")] internal static class ClimbWindRopeSlowdownPatch { private static void Prefix(CharacterRopeHandling __instance, Character ___character, out float __state) { __state = __instance.climbSpeedMod; try { if (!((Object)(object)___character == (Object)null) && ClimbWindShelter.IsLocalClimber(___character) && ClimbWindShelter.TryGetPressure(out var pressure, out var _)) { __instance.climbSpeedMod = __state * ClimbWindShelter.FlatSlowdown(___character, pressure); } } catch (Exception ex) { Diag.Error("[ClimbWindShelter] rope prefix threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void Postfix(CharacterRopeHandling __instance, float __state) { __instance.climbSpeedMod = __state; } } [HarmonyPatch(typeof(CharacterVineClimbing), "FixedUpdate")] internal static class ClimbWindVineSlowdownPatch { private static void Prefix(CharacterVineClimbing __instance, Character ___character, out float __state) { __state = __instance.climbSpeedMod; try { if (!((Object)(object)___character == (Object)null) && ClimbWindShelter.IsLocalClimber(___character) && ClimbWindShelter.TryGetPressure(out var pressure, out var _)) { __instance.climbSpeedMod = __state * ClimbWindShelter.FlatSlowdown(___character, pressure); } } catch (Exception ex) { Diag.Error("[ClimbWindShelter] vine prefix threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void Postfix(CharacterVineClimbing __instance, float __state) { __instance.climbSpeedMod = __state; } } [HarmonyPatch(typeof(WindChillZone), "Awake")] internal static class WindChillZoneTuningPatch { private sealed class Baseline { public float WindForce; public Vector2 WindTimeRangeOn; public Vector2 WindTimeRangeOff; public float WindItemFactor; public float MinRaycastDistance; public float MaxRaycastDistance; } private static readonly Dictionary Baselines = new Dictionary(); private static void Postfix(WindChillZone __instance) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)__instance == (Object)null)) { Baseline baseline = new Baseline { WindForce = __instance.windForce, WindTimeRangeOn = __instance.windTimeRangeOn, WindTimeRangeOff = __instance.windTimeRangeOff, WindItemFactor = __instance.windItemFactor, MinRaycastDistance = __instance.minRaycastDistance, MaxRaycastDistance = __instance.maxRaycastDistance }; Baselines[((Object)__instance).GetInstanceID()] = baseline; Apply(__instance, baseline); Diag.Info("[WindTuning] captured baseline, then " + (RootsState.Active ? "applied tuning" : "left it at vanilla (not in Roots)") + " " + $"(vanilla windForce={baseline.WindForce}, " + $"on={baseline.WindTimeRangeOn}, off={baseline.WindTimeRangeOff}, " + $"itemFactor={baseline.WindItemFactor}, raycast=[{baseline.MinRaycastDistance}," + $"{baseline.MaxRaycastDistance}])"); } } catch (Exception ex) { Diag.Error("[WindTuning] Awake postfix threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void Apply(WindChillZone zone, Baseline baseline) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: 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_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) if (!RootsState.Active) { Restore(zone, baseline); Diag.V($"[WindTuning] not in Roots - WindChillZone#{((Object)zone).GetInstanceID()} restored to vanilla"); return; } if (Plugin.Cfg.EffectiveDisableWindEntirely) { Restore(zone, baseline); zone.windActive = false; Diag.V($"[WindTuning] disable-wind-entirely is ON - WindChillZone#{((Object)zone).GetInstanceID()} reverted to pure vanilla and forced inactive"); return; } double effectiveWindForceMultiplier = Plugin.Cfg.EffectiveWindForceMultiplier; double effectiveWindGustDurationMultiplier = Plugin.Cfg.EffectiveWindGustDurationMultiplier; double effectiveWindItemForceMultiplier = Plugin.Cfg.EffectiveWindItemForceMultiplier; double effectiveWindObstacleOcclusionRangeMultiplier = Plugin.Cfg.EffectiveWindObstacleOcclusionRangeMultiplier; zone.windForce = WindTuning.ScaleWindForce(baseline.WindForce, effectiveWindForceMultiplier); zone.windTimeRangeOn = new Vector2(WindTuning.ScaleWindActiveDuration(baseline.WindTimeRangeOn.x, effectiveWindGustDurationMultiplier), WindTuning.ScaleWindActiveDuration(baseline.WindTimeRangeOn.y, effectiveWindGustDurationMultiplier)); zone.windTimeRangeOff = new Vector2(WindTuning.ScaleWindRestDuration(baseline.WindTimeRangeOff.x, effectiveWindGustDurationMultiplier), WindTuning.ScaleWindRestDuration(baseline.WindTimeRangeOff.y, effectiveWindGustDurationMultiplier)); zone.windItemFactor = WindTuning.ScaleItemForceFactor(baseline.WindItemFactor, effectiveWindItemForceMultiplier); zone.minRaycastDistance = WindTuning.ScaleRaycastDistance(baseline.MinRaycastDistance, effectiveWindObstacleOcclusionRangeMultiplier); zone.maxRaycastDistance = WindTuning.ScaleRaycastDistance(baseline.MaxRaycastDistance, effectiveWindObstacleOcclusionRangeMultiplier); Diag.V($"[WindTuning] applied to WindChillZone#{((Object)zone).GetInstanceID()}: " + $"windForce {baseline.WindForce}->{zone.windForce}, " + $"on {baseline.WindTimeRangeOn}->{zone.windTimeRangeOn}, " + $"off {baseline.WindTimeRangeOff}->{zone.windTimeRangeOff}, " + $"itemFactor {baseline.WindItemFactor}->{zone.windItemFactor}, " + $"raycast [{baseline.MinRaycastDistance},{baseline.MaxRaycastDistance}]->" + $"[{zone.minRaycastDistance},{zone.maxRaycastDistance}]"); } private static void Restore(WindChillZone zone, Baseline baseline) { //IL_000e: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) zone.windForce = baseline.WindForce; zone.windTimeRangeOn = baseline.WindTimeRangeOn; zone.windTimeRangeOff = baseline.WindTimeRangeOff; zone.windItemFactor = baseline.WindItemFactor; zone.minRaycastDistance = baseline.MinRaycastDistance; zone.maxRaycastDistance = baseline.MaxRaycastDistance; } internal static void ReapplyAll() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Cfg == null) { return; } try { int num = 0; WindChillZone[] array = Object.FindObjectsOfType(true); foreach (WindChillZone val in array) { if (!Baselines.TryGetValue(((Object)val).GetInstanceID(), out var value)) { value = new Baseline { WindForce = val.windForce, WindTimeRangeOn = val.windTimeRangeOn, WindTimeRangeOff = val.windTimeRangeOff, WindItemFactor = val.windItemFactor, MinRaycastDistance = val.minRaycastDistance, MaxRaycastDistance = val.maxRaycastDistance }; Baselines[((Object)val).GetInstanceID()] = value; } Apply(val, value); num++; } Diag.Info(RootsState.Active ? $"[WindTuning] reapplied tuning to {num} WindChillZone instance(s)" : $"[WindTuning] not in Roots - vanilla wind restored on {num} WindChillZone instance(s)"); } catch (Exception ex) { Diag.Error("[WindTuning] ReapplyAll threw: " + ex.GetType().Name + ": " + ex.Message); } } } [HarmonyPatch(typeof(WindChillZone), "RPCA_ToggleWind")] internal static class WindToggleSuppressionPatch { private static void Prefix(ref bool set) { if (RootsState.Active && Plugin.Cfg.EffectiveDisableWindEntirely) { set = false; } } } [HarmonyPatch(typeof(WindChillZone), "AddWindForceToItem")] internal static class WindBackpackImmunityPatch { private static bool Prefix(Item item) { if (!RootsState.Active || Plugin.Cfg.EffectiveDisableWindEntirely || !Plugin.Cfg.EffectiveWindBackpackAlwaysImmune) { return true; } return !(item is Backpack); } } [HarmonyPatch(typeof(WindChillZone), "AddWindForceToCharacter")] internal static class WindRecentForceTrackerPatch { private static float _lastWindForceTime = -1f; internal static float LastWindForceTime => _lastWindForceTime; private static void Postfix(Character character) { if (RootsState.Active && !Plugin.Cfg.EffectiveDisableWindEntirely && !((Object)(object)character == (Object)null) && !((Object)(object)character != (Object)(object)Character.localCharacter) && ((MonoBehaviourPun)character).photonView.IsMine && !((Object)(object)character.data.currentClimbHandle != (Object)null) && (!ClimbWindShelter.Enabled || !ClimbWindShelter.IsHoldingOn(character))) { _lastWindForceTime = Time.time; } } } [HarmonyPatch(typeof(CharacterData), "GetTargetRagdollControll")] internal static class WindFallCameraDampingPatch { private static void Postfix(CharacterData __instance, ref float __result) { try { if (RootsState.Active && !Plugin.Cfg.EffectiveDisableWindEntirely && !((Object)(object)Character.localCharacter == (Object)null) && !((Object)(object)__instance != (Object)(object)Character.localCharacter.data) && !(__instance.fallSeconds <= 0f)) { float effectiveWindRecentForceWindowSeconds = Plugin.Cfg.EffectiveWindRecentForceWindowSeconds; bool fallIsWindPreceded = WindTuning.IsWindForceStillRecent(WindRecentForceTrackerPatch.LastWindForceTime, Time.time, effectiveWindRecentForceWindowSeconds); __result = WindTuning.ApplyWindRagdollImmunity(__result, fallIsWindPreceded, Plugin.Cfg.EffectivePreventWindRagdoll); float dampenClampValue = (float)Plugin.Cfg.EffectiveWindFallCameraDampenClamp; __result = WindTuning.ApplyFallCameraDampening(__result, fallIsWindPreceded, dampenClampValue); } } catch (Exception ex) { Diag.Error("[WindFallCameraDamping] threw: " + ex.GetType().Name + ": " + ex.Message); } } } } namespace Fairoots.Ui { internal static class NativeUiAssets { private const string OutlineMaterialName = "DarumaDropOne-Regular SDF Outline"; internal static TMP_FontAsset Font { get; private set; } internal static Material OutlineMaterial { get; private set; } internal static bool Ready { get { if ((Object)(object)Font != (Object)null) { return (Object)(object)OutlineMaterial != (Object)null; } return false; } } internal static bool TryResolve() { if (Ready) { return true; } TextMeshProUGUI[] array = Resources.FindObjectsOfTypeAll(); foreach (TextMeshProUGUI val in array) { Material materialForRendering = ((Graphic)val).materialForRendering; if ((Object)(object)materialForRendering != (Object)null && ((Object)materialForRendering).name.Contains("DarumaDropOne-Regular SDF Outline")) { Font = ((TMP_Text)val).font; OutlineMaterial = materialForRendering; return true; } } return false; } } internal static class RootsLoadingOverlay { private static readonly Color DimColor = new Color(0f, 0f, 0f, 0.78f); private const int SortingOrder = 30500; private static GameObject _root; private static CanvasGroup _group; private static TextMeshProUGUI _text; private static Material _material; internal static bool Exists => (Object)(object)_root != (Object)null; internal static void Show() { try { if ((Object)(object)_root == (Object)null) { Build(); } _root.SetActive(true); ApplyText(); _group.alpha = 0f; } catch (Exception ex) { Diag.Error("[RootsLoadingOverlay] Show threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static bool Fade(float target, float perSecond) { if ((Object)(object)_group == (Object)null) { return true; } _group.alpha = Mathf.MoveTowards(_group.alpha, target, Time.unscaledDeltaTime * perSecond); return Mathf.Approximately(_group.alpha, target); } internal static void Hide() { if (!((Object)(object)_root == (Object)null)) { try { Object.Destroy((Object)(object)_root); } catch (Exception ex) { Diag.Error("[RootsLoadingOverlay] Hide threw: " + ex.GetType().Name + ": " + ex.Message); } _root = null; _group = null; _text = null; _material = null; } } private static void Build() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected O, but got Unknown //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Expected O, but got Unknown _root = new GameObject("FairootsRootsLoadingOverlay"); Object.DontDestroyOnLoad((Object)(object)_root); Canvas obj = _root.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 30500; CanvasScaler obj2 = _root.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 1f; _group = _root.AddComponent(); _group.alpha = 0f; _group.blocksRaycasts = false; _group.interactable = false; GameObject val = new GameObject("Dim", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_root.transform, false); Image obj3 = val.AddComponent(); ((Graphic)obj3).color = DimColor; ((Graphic)obj3).raycastTarget = false; StretchFull((RectTransform)val.transform); if (!NativeUiAssets.TryResolve()) { Diag.Warn("[RootsLoadingOverlay] no native font found yet - showing the dim without a label."); return; } GameObject val2 = new GameObject("LoadingText", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(_root.transform, false); StretchFull((RectTransform)val2.transform); _text = val2.AddComponent(); ((TMP_Text)_text).font = NativeUiAssets.Font; ((TMP_Text)_text).fontSize = 44f; ((TMP_Text)_text).alignment = (TextAlignmentOptions)514; ((Graphic)_text).raycastTarget = false; ((TMP_Text)_text).enableWordWrapping = false; ((Graphic)_text).color = new Color(0.98f, 0.99f, 1f); _material = new Material(NativeUiAssets.OutlineMaterial); ((TMP_Text)_text).fontSharedMaterial = _material; } private static void ApplyText() { if ((Object)(object)_text != (Object)null) { ((TMP_Text)_text).text = WarningLabelLocalization.Get(WarningLabelKey.PreparingRoots); } } private static void StretchFull(RectTransform rect) { //IL_0001: 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_0017: 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) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } } internal static class SpiderWarningLabel { private const float TopOffsetPixels = 330f; private const float FadeSeconds = 0.12f; private static GameObject _root; private static CanvasGroup _group; private static TextMeshProUGUI _text; private static Material _material; private static int _outlineColorId = -1; private static Color _appliedColor; private static Language? _appliedLanguage; private static Rgb? _poisonColor; private static readonly Rgb FallbackPoisonColor = new Rgb(0.62, 0.31, 0.78); internal static void Tick() { try { bool flag = RootsState.Active && Plugin.Cfg.ShowSpiderWarningLabel.Value && SpiderStrikeWarning.StrikeIncoming(); if (!flag && (Object)(object)_root == (Object)null) { return; } if ((Object)(object)_root == (Object)null) { if (!NativeUiAssets.TryResolve()) { return; } Build(); } ApplyText(); ApplyColor(); Fade(flag); } catch (Exception ex) { Diag.Error("[SpiderWarningLabel] Tick threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void Build() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Expected O, but got Unknown _root = new GameObject("FairootsSpiderWarningLabel"); Object.DontDestroyOnLoad((Object)(object)_root); Canvas obj = _root.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = (((Object)(object)GUIManager.instance != (Object)null && (Object)(object)GUIManager.instance.hudCanvas != (Object)null) ? (GUIManager.instance.hudCanvas.sortingOrder + 1) : 100); CanvasScaler obj2 = _root.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 0.5f; _group = _root.AddComponent(); _group.alpha = 0f; _group.blocksRaycasts = false; _group.interactable = false; GameObject val = new GameObject("Text", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_root.transform, false); RectTransform val2 = (RectTransform)val.transform; val2.anchorMin = new Vector2(0.5f, 1f); val2.anchorMax = new Vector2(0.5f, 1f); val2.pivot = new Vector2(0.5f, 0.5f); val2.sizeDelta = new Vector2(900f, 60f); val2.anchoredPosition = new Vector2(0f, -330f); _text = val.AddComponent(); ((TMP_Text)_text).font = NativeUiAssets.Font; ((TMP_Text)_text).fontSize = 40f; ((TMP_Text)_text).alignment = (TextAlignmentOptions)514; ((Graphic)_text).raycastTarget = false; ((TMP_Text)_text).enableWordWrapping = false; _material = new Material(NativeUiAssets.OutlineMaterial); ((TMP_Text)_text).fontSharedMaterial = _material; _outlineColorId = Shader.PropertyToID("_OutlineColor"); Diag.Info("[SpiderWarningLabel] built (native font + outlined material found)"); } private static void ApplyText() { //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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) Language currentLanguage = WarningLabelLocalization.CurrentLanguage; if (_appliedLanguage != (Language?)currentLanguage) { _appliedLanguage = currentLanguage; ((TMP_Text)_text).text = WarningLabelLocalization.Get(WarningLabelKey.SpiderDropping); } } private static void ApplyColor() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) Rgb foreground = ResolvePoisonColor(); Color val = default(Color); ((Color)(ref val))..ctor((float)foreground.R, (float)foreground.G, (float)foreground.B, 1f); if (!(val == _appliedColor)) { _appliedColor = val; ((Graphic)_text).color = val; Rgb rgb = LabelColors.Outline(foreground); _material.SetColor(_outlineColorId, new Color((float)rgb.R, (float)rgb.G, (float)rgb.B, 1f)); } } private static Rgb ResolvePoisonColor() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (_poisonColor.HasValue) { return _poisonColor.Value; } try { Character localCharacter = Character.localCharacter; CharacterAfflictions val = (((Object)(object)localCharacter != (Object)null && localCharacter.refs != null) ? localCharacter.refs.afflictions : null); if ((Object)(object)val != (Object)null && ((Color)(ref val.colorPoison)).maxColorComponent > 0f) { Color colorPoison = val.colorPoison; Rgb rgb = new Rgb(colorPoison.r, colorPoison.g, colorPoison.b); _poisonColor = rgb; Diag.Info($"[SpiderWarningLabel] read live Poison status color {rgb}"); return rgb; } } catch (Exception ex) { Diag.V("[SpiderWarningLabel] could not read the live Poison status color (" + ex.GetType().Name + ") - using the fallback"); } return FallbackPoisonColor; } private static void Fade(bool visible) { float num = (visible ? 1f : 0f); _group.alpha = Mathf.MoveTowards(_group.alpha, num, Time.unscaledDeltaTime / 0.12f); bool flag = _group.alpha > 0f; if (_root.activeSelf != flag) { _root.SetActive(flag); } } } internal static class SporeWarningLabel { private const float TopOffsetPixels = 270f; private const float FadeSeconds = 0.15f; private static GameObject _root; private static CanvasGroup _group; private static TextMeshProUGUI _text; private static Material _material; private static int _outlineColorId = -1; private static Color _appliedColor; private static Language? _appliedLanguage; internal static void Tick() { try { bool flag = RootsState.Active && Plugin.Cfg.ShowSporeCloudLabel.Value && SporePresence.InAnySpores(); if (!flag && (Object)(object)_root == (Object)null) { return; } if ((Object)(object)_root == (Object)null) { if (!NativeUiAssets.TryResolve()) { return; } Build(); } ApplyText(); ApplyColor(); Fade(flag); } catch (Exception ex) { Diag.Error("[SporeWarningLabel] Tick threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void Build() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Expected O, but got Unknown _root = new GameObject("FairootsSporeWarningLabel"); Object.DontDestroyOnLoad((Object)(object)_root); Canvas obj = _root.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = (((Object)(object)GUIManager.instance != (Object)null && (Object)(object)GUIManager.instance.hudCanvas != (Object)null) ? (GUIManager.instance.hudCanvas.sortingOrder + 1) : 100); CanvasScaler obj2 = _root.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 0.5f; _group = _root.AddComponent(); _group.alpha = 0f; _group.blocksRaycasts = false; _group.interactable = false; GameObject val = new GameObject("Text", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_root.transform, false); RectTransform val2 = (RectTransform)val.transform; val2.anchorMin = new Vector2(0.5f, 1f); val2.anchorMax = new Vector2(0.5f, 1f); val2.pivot = new Vector2(0.5f, 0.5f); val2.sizeDelta = new Vector2(900f, 60f); val2.anchoredPosition = new Vector2(0f, -270f); _text = val.AddComponent(); ((TMP_Text)_text).font = NativeUiAssets.Font; ((TMP_Text)_text).fontSize = 40f; ((TMP_Text)_text).alignment = (TextAlignmentOptions)514; ((Graphic)_text).raycastTarget = false; ((TMP_Text)_text).enableWordWrapping = false; _material = new Material(NativeUiAssets.OutlineMaterial); ((TMP_Text)_text).fontSharedMaterial = _material; _outlineColorId = Shader.PropertyToID("_OutlineColor"); Diag.Info("[SporeWarningLabel] built (native font + outlined material found)"); } private static void ApplyText() { //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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) Language currentLanguage = WarningLabelLocalization.CurrentLanguage; if (_appliedLanguage != (Language?)currentLanguage) { _appliedLanguage = currentLanguage; ((TMP_Text)_text).text = WarningLabelLocalization.Get(WarningLabelKey.BreathingInSpores); } } private static void ApplyColor() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) Rgb foreground = SporeBombRecolorPatch.ResolveSporeColor(); Color val = default(Color); ((Color)(ref val))..ctor((float)foreground.R, (float)foreground.G, (float)foreground.B, 1f); if (!(val == _appliedColor)) { _appliedColor = val; ((Graphic)_text).color = val; Rgb rgb = LabelColors.Outline(foreground); _material.SetColor(_outlineColorId, new Color((float)rgb.R, (float)rgb.G, (float)rgb.B, 1f)); } } private static void Fade(bool visible) { float num = (visible ? 1f : 0f); _group.alpha = Mathf.MoveTowards(_group.alpha, num, Time.unscaledDeltaTime / 0.15f); bool flag = _group.alpha > 0f; if (_root.activeSelf != flag) { _root.SetActive(flag); } } } internal enum WarningLabelKey { BreathingInSpores, SpiderDropping, PreparingRoots } internal static class WarningLabelLocalization { private static readonly Dictionary Table = new Dictionary { [WarningLabelKey.BreathingInSpores] = new string[15] { "Breathing in spores!", "Vous respirez des spores !", "Stai respirando spore!", "Du atmest Sporen ein!", "¡Estás respirando esporas!", "¡Estás respirando esporas!", "Você está respirando esporos!", "Вы дышите спорами!", "Ви дихаєте спорами!", "正在吸入孢子!", "", "胞子を吸い込んでいます!", "포자를 흡입하고 있습니다!", "Wdychasz zarodniki!", "Spor soluyorsun!" }, [WarningLabelKey.SpiderDropping] = new string[15] { "Spider dropping on you!", "Une araignée descend sur vous !", "Un ragno ti sta cadendo addosso!", "Eine Spinne lässt sich auf dich herab!", "¡Una araña se descuelga sobre ti!", "¡Una araña se descuelga sobre ti!", "Uma aranha está descendo sobre você!", "На вас спускается паук!", "На вас спускається павук!", "蜘蛛正朝你落下!", "", "クモが降りてきます!", "거미가 당신을 향해 내려옵니다!", "Pająk spada na ciebie!", "Bir örümcek üstüne iniyor!" }, [WarningLabelKey.PreparingRoots] = new string[15] { "Preparing the Roots...", "Préparation des Racines...", "Preparazione delle Radici...", "Die Wurzeln werden vorbereitet...", "Preparando las Raíces...", "Preparando las Raíces...", "Preparando as Raízes...", "Подготовка Корней...", "Підготовка Коренів...", "正在准备根系...", "", "ルーツを準備しています...", "뿌리를 준비하는 중...", "Przygotowywanie Korzeni...", "Kökler hazırlanıyor..." } }; public static Language CurrentLanguage => LocalizedText.CURRENT_LANGUAGE; public static string Get(WarningLabelKey key) { return LocalizationHelper.Resolve(Table[key]); } } } namespace Fairoots.Spores { [HarmonyPatch(typeof(CharacterAfflictions), "AddStatus")] internal static class SporeBuildUpPatch { private const float LogIntervalSeconds = 2f; private static float _nextLogTime; [HarmonyPriority(200)] private static void Prefix(STATUSTYPE statusType, ref float amount) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 try { if (!RootsState.Active || (int)statusType != 10 || amount <= 0f || Plugin.Cfg == null) { return; } double effectiveSporeBuildUpMultiplier = Plugin.Cfg.EffectiveSporeBuildUpMultiplier; if (!SporeStatusTuning.IsVanilla(effectiveSporeBuildUpMultiplier)) { float num = SporeStatusTuning.ScaleBuildUp(amount, effectiveSporeBuildUpMultiplier); if (Diag.Enabled && Time.unscaledTime >= _nextLogTime) { _nextLogTime = Time.unscaledTime + 2f; Diag.V($"[Spores] build-up x{effectiveSporeBuildUpMultiplier:0.###}: +{amount:0.#####} -> +{num:0.#####} Spores"); } amount = num; } } catch (Exception ex) { Diag.Error("[Spores] build-up prefix threw: " + ex.GetType().Name + ": " + ex.Message); } } } internal static class SporeDecayPatch { private readonly struct DecayBaseline { internal readonly float PerSecond; internal readonly float Cooldown; internal DecayBaseline(float perSecond, float cooldown) { PerSecond = perSecond; Cooldown = cooldown; } } private static readonly Dictionary Baselines = new Dictionary(); private static bool _baselineLogged; internal static void ReapplyToAll() { if (Plugin.Cfg == null) { return; } try { int num = 0; foreach (Character allCharacter in Character.AllCharacters) { if ((Object)(object)allCharacter != (Object)null && Apply(allCharacter.refs?.afflictions)) { num++; } } Diag.Info(RootsState.Active ? $"[Spores] clear-time x{Plugin.Cfg.EffectiveSporeClearTimeMultiplier:0.###} applied to {num} character(s)" : $"[Spores] not in Roots - vanilla clear-time restored on {num} character(s)"); } catch (Exception ex) { Diag.Error("[Spores] decay ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static bool Apply(CharacterAfflictions afflictions) { if ((Object)(object)afflictions == (Object)null || Plugin.Cfg == null) { return false; } int instanceID = ((Object)afflictions).GetInstanceID(); if (!Baselines.TryGetValue(instanceID, out var value)) { value = new DecayBaseline(afflictions.sporesReductionPerSecond, afflictions.sporesReductionCooldown); Baselines[instanceID] = value; } double num = (RootsState.Active ? Plugin.Cfg.EffectiveSporeClearTimeMultiplier : 1.0); if (SporeStatusTuning.IsVanilla(num)) { afflictions.sporesReductionPerSecond = value.PerSecond; afflictions.sporesReductionCooldown = value.Cooldown; } else { afflictions.sporesReductionPerSecond = SporeStatusTuning.ScaleDecayRate(value.PerSecond, num); afflictions.sporesReductionCooldown = SporeStatusTuning.ScaleDecayCooldown(value.Cooldown, num); } LogBaselineOnce(value, num); Diag.V("[Spores] \"" + ((Object)((Component)afflictions).gameObject).name + "\" sporesReductionPerSecond " + $"{value.PerSecond:0.####} -> {afflictions.sporesReductionPerSecond:0.####}, " + $"cooldown {value.Cooldown:0.##}s -> {afflictions.sporesReductionCooldown:0.##}s"); return true; } private static void LogBaselineOnce(DecayBaseline vanilla, double multiplier) { if (!_baselineLogged) { _baselineLogged = true; if (vanilla.PerSecond <= 0f) { Diag.Warn("[Spores] vanilla sporesReductionPerSecond is 0 - this build never drains spores on its own, so clear-time-multiplier has nothing to scale and is inert."); return; } double num = SporeStatusTuning.SecondsToClear(vanilla.Cooldown, vanilla.PerSecond, 1f, 1.0); double num2 = SporeStatusTuning.SecondsToClear(vanilla.Cooldown, vanilla.PerSecond, 1f, multiplier); Diag.Info($"[Spores] vanilla spore recovery: {vanilla.Cooldown:0.##}s delay + " + $"{vanilla.PerSecond:0.####}/s = {num:0.#}s to clear a full meter. " + $"At x{multiplier:0.###} that becomes {num2:0.#}s."); } } } [HarmonyPatch(typeof(CharacterAfflictions), "Awake")] internal static class CharacterAfflictionsAwakeDecayPatch { private static void Postfix(CharacterAfflictions __instance) { SporeDecayPatch.Apply(__instance); } } } namespace Fairoots.SporeBombs { [HarmonyPatch(typeof(AOE), "Explode")] internal static class CoverMouthSporeBombPatch { private static void Prefix(AOE __instance, out (float StatusAmount, bool HasAffliction) __state) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) __state = (StatusAmount: __instance.statusAmount, HasAffliction: __instance.hasAffliction); if (ShouldSuppress(__instance)) { __instance.statusAmount = 0f; __instance.hasAffliction = false; Diag.V($"[CoverMouth] suppressed a spore bomb's spore cloud at {((Component)__instance).transform.position} (mouth covered)"); } } private static void Postfix(AOE __instance, (float StatusAmount, bool HasAffliction) __state) { (__instance.statusAmount, __instance.hasAffliction) = __state; } private static bool ShouldSuppress(AOE aoe) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (!RootsState.Active || !CoverMouthController.LocalCovering || !Plugin.Cfg.EffectiveCoverMouthBlocksSporeBombs || (int)aoe.statusType != 10 || aoe.statusAmount <= 0f) { return false; } if (!((Object)(object)((Component)aoe).GetComponentInParent() != (Object)null)) { return DetonationScreenshakeRegistry.IsFromRecentDetonation(((Component)aoe).transform.position); } return true; } } [HarmonyPatch(typeof(AddScreenshake), "Shake")] internal static class DetonationScreenshakePatch { private static void Prefix(AddScreenshake __instance) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) try { if (!RootsState.Active || (Object)(object)__instance == (Object)null) { return; } float effectiveSporeBombScreenshakeRangeCapMeters = Plugin.Cfg.EffectiveSporeBombScreenshakeRangeCapMeters; if (SporeBombExplosionTuning.ShouldForcePositionalScreenshake(effectiveSporeBombScreenshakeRangeCapMeters)) { Vector3 position = ((Component)__instance).transform.position; if (DetonationScreenshakeRegistry.IsFromRecentDetonation(position)) { bool positional = __instance.positional; float range = __instance.range; __instance.positional = true; __instance.range = SporeBombExplosionTuning.ResolveScreenshakeRange(range, positional, GameUnits.MetersToUnits(effectiveSporeBombScreenshakeRangeCapMeters)); Diag.V($"[SporeBombShake] capped detonation shake on '{((Object)__instance).name}' @ {position} " + $"(positional {positional}->true, range {GameUnits.ToMeters(range):0.#}m->" + $"{GameUnits.ToMeters(__instance.range):0.#}m)"); } } } catch (Exception ex) { Diag.Error("[SporeBombShake] threw: " + ex.GetType().Name + ": " + ex.Message); } } } internal static class DetonationScreenshakeRegistry { private const int Capacity = 16; private static readonly Vector3[] Positions = (Vector3[])(object)new Vector3[16]; private static readonly float[] Times = new float[16]; private static int _next; private static bool _any; internal static void Record(Vector3 position) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Positions[_next] = position; Times[_next] = Time.time; _next = (_next + 1) % 16; _any = true; } internal static bool IsFromRecentDetonation(Vector3 position) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (!_any) { return false; } float time = Time.time; for (int i = 0; i < 16; i++) { if (!(Times[i] <= 0f) && SporeBombExplosionTuning.IsDetonationScreenshake(time - Times[i], GameUnits.ToMeters(Vector3.Distance(Positions[i], position)))) { return true; } } return false; } internal static void Clear() { for (int i = 0; i < 16; i++) { Times[i] = 0f; } _next = 0; _any = false; } } internal sealed class SporeBombCloudOpacity : MonoBehaviour { private const float ReapplyIntervalSeconds = 0.25f; private static bool _structureLogged; private float _nextApplyTime; private void OnEnable() { _nextApplyTime = 0f; LogStructureOnce(); } private void Update() { if (RootsState.Active && Plugin.Cfg != null && !(Time.unscaledTime < _nextApplyTime)) { _nextApplyTime = Time.unscaledTime + 0.25f; ParticleOpacity.Apply(((Component)this).gameObject, Plugin.Cfg.SporeBombCloudOpacity.Value); } } private void LogStructureOnce() { if (_structureLogged || !Diag.Enabled) { return; } _structureLogged = true; Diag.V("[SporeBombCloudOpacity] structure of a live detonation \"" + ((Object)this).name + "\":"); Transform[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { List list = new List(); Component[] components = ((Component)val).GetComponents(); foreach (Component val2 in components) { list.Add(((Object)(object)val2 == (Object)null) ? "" : ((object)val2).GetType().Name); } Diag.V("[SporeBombCloudOpacity] \"" + ((Object)val).name + "\" : " + string.Join(", ", list)); } } } internal static class SporeBombCloudWarning { private static readonly List LiveClouds = new List(); private static bool _showing; internal static void Register(GameObject spawned) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 AOE[] componentsInChildren = spawned.GetComponentsInChildren(true); foreach (AOE val in componentsInChildren) { if ((int)val.statusType == 10 && val.statusAmount > 0f) { LiveClouds.Add(val); } } } internal static void Tick() { try { PruneDestroyed(); Resolve(RootsState.Active && Plugin.Cfg.ShowOverlayInSporeBombClouds.Value && InBombCloud()); } catch (Exception ex) { Diag.Error("[SporeBombCloudWarning] Tick threw: " + ex.GetType().Name + ": " + ex.Message); Resolve(inside: false); } } internal static void ClearLevelState() { LiveClouds.Clear(); Resolve(inside: false); } private static void PruneDestroyed() { for (int num = LiveClouds.Count - 1; num >= 0; num--) { if ((Object)(object)LiveClouds[num] == (Object)null) { LiveClouds.RemoveAt(num); } } } internal static bool InBombCloud() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (LiveClouds.Count == 0) { return false; } Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null || (Object)(object)localCharacter.data == (Object)null || localCharacter.data.dead) { return false; } if (CoverMouthController.LocalCovering && Plugin.Cfg.EffectiveCoverMouthBlocksSporeBombs) { return false; } Vector3 center = localCharacter.Center; foreach (AOE liveCloud in LiveClouds) { if (((Behaviour)liveCloud).isActiveAndEnabled && SporeBombCloudPresence.IsInsideStatusRange(Vector3.Distance(center, ((Component)liveCloud).transform.position), liveCloud.range, liveCloud.minFactor, liveCloud.factorPow)) { return true; } } return false; } private static void Resolve(bool inside) { if (inside == _showing) { return; } ScreenVFX val = (((Object)(object)GUIManager.instance != (Object)null) ? GUIManager.instance.sporesWarning : null); if ((Object)(object)val == (Object)null) { return; } if (inside) { _showing = true; if (SporePresence.InSporeArea()) { Diag.V("[SporeBombCloudWarning] entered a spore bomb's cloud inside a spore area - the warning is already up"); return; } val.StartFX(0.5f); Diag.V("[SporeBombCloudWarning] entered a spore bomb's cloud - holding the spores warning up"); } else { _showing = false; if (SporePresence.InSporeArea()) { Diag.V("[SporeBombCloudWarning] left a spore bomb's cloud inside a spore area - leaving the warning to the area"); return; } val.EndFX(); Diag.V("[SporeBombCloudWarning] left a spore bomb's cloud - lowering the spores warning"); } } } internal static class SporeBombCullPatch { private static readonly string[] FoliageNameSubstrings = new string[1] { "Fern" }; private const float FoliageProximityRadius = 0.75f; internal static readonly List<(Vector3 Pos, CullOutcome Outcome)> RemovedPositions = new List<(Vector3, CullOutcome)>(); internal static readonly List KeptTriggerColliders = new List(); private static readonly Dictionary VanillaSphereRadii = new Dictionary(); private static readonly Dictionary VanillaBoxSizes = new Dictionary(); private static readonly List<(Vector3 Pos, Transform Owner)> LastFoliageVertices = new List<(Vector3, Transform)>(); internal static void Run(Transform rootsSegment) { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) try { RemovedPositions.Clear(); KeptTriggerColliders.Clear(); List list = (from t in ((Component)rootsSegment).GetComponentsInChildren(true) where ClassifySporeBomb(((Object)t).name) select t).ToList(); if (list.Count == 0) { Diag.V("[SporeBombCull] no spore-bomb candidates found under Roots Segment"); return; } CollectFoliageVertices(rootsSegment); Diag.V($"[SporeBombCull] {LastFoliageVertices.Count} foliage vertex sample(s) collected for proximity test"); bool effectiveEnableFoliageRemoval = Plugin.Cfg.EffectiveEnableFoliageRemoval; List list2 = new List(list.Count); List list3 = new List(list.Count); foreach (Transform item5 in list) { list2.Add(GridPos.Round(item5.position.x, item5.position.y, item5.position.z)); list3.Add(IsNearFoliage(item5.position)); } CullOutcome[] array = SporeBombCull.Decide(list2, list3, Plugin.Cfg.EffectiveSporeBombCullFraction, Plugin.Cfg.EffectiveSeed, effectiveEnableFoliageRemoval); int num = 0; int num2 = 0; double num3 = ResolveTriggerRadiusMultiplier(); bool value = Plugin.Cfg.RecolorSporeBombs.Value; Rgb rgb = SporeBombRecolorPatch.ResolveSporeColor(); if (Plugin.Cfg.KeepVanillaTriggerRadius.Value) { Diag.Info("[SporeBombCull] keep-vanilla-trigger-radius is ON - trigger hitboxes left at vanilla size (for before/after comparison screenshots)"); } for (int num4 = 0; num4 < list.Count; num4++) { if (array[num4] != CullOutcome.Kept) { RemovedPositions.Add((list[num4].position, array[num4])); ((Component)list[num4]).gameObject.SetActive(false); num++; string text = string.Empty; if (array[num4] == CullOutcome.RemovedFoliage) { (float Distance, string Owner, int CountWithinRadius, float MaxHeightAbove) tuple = NearestFoliageVertex(list[num4].position); float item = tuple.Distance; string item2 = tuple.Owner; int item3 = tuple.CountWithinRadius; float item4 = tuple.MaxHeightAbove; text = $", nearestFoliageVertex={GameUnits.ToMeters(item):0.00}m countWithinRadius={item3} " + $"maxHeightAbove={GameUnits.ToMeters(item4):0.00}m owner=[{item2}]"; } Diag.V($"[SporeBombCull] removed \"{((Object)list[num4]).name}\" @ {list2[num4]} ({array[num4]}{text})"); } else { if (ShrinkTriggerRadius(list[num4], num3, out var largestCollider)) { num2++; KeptTriggerColliders.Add(largestCollider); } if (value) { SporeBombRecolorPatch.Apply(list[num4], rgb, enabled: true); } } } CullSummary cullSummary = SporeBombCull.Summarize(array); Diag.Info($"[SporeBombCull] {cullSummary.Total} candidate(s): removed {num} " + "(foliage=" + (effectiveEnableFoliageRemoval ? cullSummary.FoliageRemoved.ToString() : "off") + ", " + $"seeded={cullSummary.SeededRemoved}), kept {cullSummary.Kept}, " + $"trigger-radius shrunk on {num2} (multiplier={num3:0.##}), " + "recolor=" + (value ? $"ON target={rgb}" : "OFF")); } catch (Exception ex) { Diag.Error("[SporeBombCull] threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void CollectFoliageVertices(Transform rootsSegment) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) LastFoliageVertices.Clear(); Renderer[] componentsInChildren = ((Component)rootsSegment).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (!IsFoliageRenderer(((Component)val).transform)) { continue; } MeshFilter component = ((Component)val).GetComponent(); Mesh val2 = (((Object)(object)component != (Object)null) ? component.sharedMesh : null); if (!((Object)(object)val2 == (Object)null) && val2.isReadable) { Matrix4x4 localToWorldMatrix = ((Component)val).transform.localToWorldMatrix; Vector3[] vertices = val2.vertices; for (int j = 0; j < vertices.Length; j++) { LastFoliageVertices.Add((((Matrix4x4)(ref localToWorldMatrix)).MultiplyPoint3x4(vertices[j]), ((Component)val).transform)); } } } } internal static (float Distance, string Owner, int CountWithinRadius, float MaxHeightAbove) NearestFoliageVertex(Vector3 pos) { //IL_0028: 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_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) float num = float.MaxValue; Transform val = null; int num2 = 0; float num3 = float.NegativeInfinity; float num4 = 0.5625f; for (int i = 0; i < LastFoliageVertices.Count; i++) { Vector3 item = LastFoliageVertices[i].Pos; float num5 = Vector3.Distance(item, pos); if (num5 < num) { num = num5; val = LastFoliageVertices[i].Owner; } Vector3 val2 = item - pos; if (((Vector3)(ref val2)).sqrMagnitude <= num4) { num2++; float num6 = item.y - pos.y; if (num6 > num3) { num3 = num6; } } } if ((Object)(object)val == (Object)null) { return (Distance: float.MaxValue, Owner: "(no foliage vertices collected)", CountWithinRadius: 0, MaxHeightAbove: 0f); } List list = new List(); Transform val3 = val; while ((Object)(object)val3 != (Object)null) { list.Add(((Object)val3).name); if (((Object)val3).name == "Roots Segment") { break; } val3 = val3.parent; } return (Distance: num, Owner: string.Join(" < ", list), CountWithinRadius: num2, MaxHeightAbove: (num2 > 0) ? num3 : 0f); } private static bool IsFoliageRenderer(Transform t) { Transform val = t; while ((Object)(object)val != (Object)null) { for (int i = 0; i < FoliageNameSubstrings.Length; i++) { if (((Object)val).name.IndexOf(FoliageNameSubstrings[i], StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } if (((Object)val).name == "Roots Segment") { break; } val = val.parent; } return false; } private static bool IsNearFoliage(Vector3 pos) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) float num = 0.5625f; for (int i = 0; i < LastFoliageVertices.Count; i++) { Vector3 val = LastFoliageVertices[i].Pos - pos; if (((Vector3)(ref val)).sqrMagnitude <= num) { return true; } } return false; } private static double ResolveTriggerRadiusMultiplier() { if (RootsState.Active && !Plugin.Cfg.KeepVanillaTriggerRadius.Value) { return Plugin.Cfg.EffectiveSporeBombTriggerRadiusMultiplier; } return 1.0; } internal static void ReapplyTriggerRadiusToAll() { if (Plugin.Cfg == null) { return; } try { double num = ResolveTriggerRadiusMultiplier(); KeptTriggerColliders.Clear(); int num2 = 0; int num3 = 0; Transform[] array = Object.FindObjectsOfType(true); foreach (Transform val in array) { if (((Component)val).gameObject.activeInHierarchy && ClassifySporeBomb(((Object)val).name)) { num2++; if (ShrinkTriggerRadius(val, num, out var largestCollider)) { num3++; KeptTriggerColliders.Add(largestCollider); } } } Diag.Info($"[SporeBombCull] full trigger-radius refresh: {num2} active spore bomb(s) found scene-wide, " + $"{num3} resized (multiplier={num:0.##})"); } catch (Exception ex) { Diag.Error("[SporeBombCull] ReapplyTriggerRadiusToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } private static bool ShrinkTriggerRadius(Transform candidate, double multiplier, out Collider largestCollider) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) largestCollider = null; float num = 0f; Collider[] components = ((Component)candidate).GetComponents(); foreach (Collider val in components) { SphereCollider val2 = (SphereCollider)(object)((val is SphereCollider) ? val : null); float num2; if (val2 == null) { BoxCollider val3 = (BoxCollider)(object)((val is BoxCollider) ? val : null); if (val3 == null) { continue; } int instanceID = ((Object)val3).GetInstanceID(); if (!VanillaBoxSizes.TryGetValue(instanceID, out var value)) { value = val3.size; VanillaBoxSizes[instanceID] = value; } val3.size = value * (float)multiplier; Vector3 size = val3.size; num2 = ((Vector3)(ref size)).magnitude; } else { int instanceID2 = ((Object)val2).GetInstanceID(); if (!VanillaSphereRadii.TryGetValue(instanceID2, out var value2)) { value2 = val2.radius; VanillaSphereRadii[instanceID2] = value2; } val2.radius = SporeBombExplosionTuning.ScaleTriggerRadius(value2, multiplier); num2 = val2.radius; } if (num2 > num) { num = num2; largestCollider = val; } } return (Object)(object)largestCollider != (Object)null; } internal static bool ClassifySporeBomb(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.IndexOf("SporeFungus", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("SporeMushroom", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } internal static bool IsExplosiveVariant(string name) { if (!string.IsNullOrEmpty(name)) { return name.IndexOf("SporeMushroomExplo", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } internal sealed class SporeBombDetonationMarker : MonoBehaviour { } [HarmonyPatch(typeof(SpawnGameObject), "Go")] internal static class SporeBombExplosionPatch { private static bool Prefix(SpawnGameObject __instance) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) try { if (!RootsState.Active || (Object)(object)__instance == (Object)null || (Object)(object)__instance.toSpawn == (Object)null || !SporeBombCullPatch.ClassifySporeBomb(((Object)((Component)__instance).gameObject).name)) { return true; } ApplyTuning(Object.Instantiate(__instance.toSpawn, ((Component)__instance).transform.position, ((Component)__instance).transform.rotation)); return false; } catch (Exception ex) { Diag.Error("[SporeBombExplosion] threw: " + ex.GetType().Name + ": " + ex.Message); return true; } } private static void ApplyTuning(GameObject spawned) { //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) double effectiveSporeBombKnockbackMultiplier = Plugin.Cfg.EffectiveSporeBombKnockbackMultiplier; double effectiveSporeBombSporeAreaRadiusMultiplier = Plugin.Cfg.EffectiveSporeBombSporeAreaRadiusMultiplier; AOE[] componentsInChildren = spawned.GetComponentsInChildren(true); foreach (AOE val in componentsInChildren) { val.knockback = SporeBombExplosionTuning.ScaleKnockback(val.knockback, effectiveSporeBombKnockbackMultiplier); val.itemKnockbackMultiplier = SporeBombExplosionTuning.ScaleKnockback(val.itemKnockbackMultiplier, effectiveSporeBombKnockbackMultiplier); if (val.statusAmount != 0f) { val.range = SporeBombExplosionTuning.ScaleSporeAreaRadius(val.range, effectiveSporeBombSporeAreaRadiusMultiplier); } } int num = ScaleCloudVfx(spawned, effectiveSporeBombSporeAreaRadiusMultiplier); double effectiveSporeBombVfxCountMultiplier = Plugin.Cfg.EffectiveSporeBombVfxCountMultiplier; ExplosionEffect[] componentsInChildren2 = spawned.GetComponentsInChildren(true); foreach (ExplosionEffect obj in componentsInChildren2) { obj.explosionPointCount = SporeBombExplosionTuning.ScaleVfxCount(obj.explosionPointCount, effectiveSporeBombVfxCountMultiplier); obj.subExplosionPointCount = SporeBombExplosionTuning.ScaleVfxCount(obj.subExplosionPointCount, effectiveSporeBombVfxCountMultiplier); } float effectiveSporeBombScreenshakeRangeCapMeters = Plugin.Cfg.EffectiveSporeBombScreenshakeRangeCapMeters; DetonationScreenshakeRegistry.Record(spawned.transform.position); if ((Object)(object)spawned.GetComponent() == (Object)null) { spawned.AddComponent(); } if ((Object)(object)spawned.GetComponent() == (Object)null) { spawned.AddComponent(); } SporeBombCloudWarning.Register(spawned); bool flag = SporeBombExplosionTuning.ShouldForcePositionalScreenshake(effectiveSporeBombScreenshakeRangeCapMeters); float capUnits = GameUnits.MetersToUnits(effectiveSporeBombScreenshakeRangeCapMeters); int num2 = 0; AddScreenshake[] componentsInChildren3 = spawned.GetComponentsInChildren(true); foreach (AddScreenshake val2 in componentsInChildren3) { num2++; bool positional = val2.positional; val2.range = SporeBombExplosionTuning.ResolveScreenshakeRange(val2.range, positional, capUnits); if (flag) { val2.positional = true; } Diag.V($"[SporeBombExplosion] shake '{((Object)val2).name}': positional {positional}->" + $"{val2.positional}, range ->{GameUnits.ToMeters(val2.range):0.#}m " + $"({val2.range:0.#} world units)"); } Diag.V($"[SporeBombExplosion] tuned detonation @ {spawned.transform.position} " + $"(knockback x{effectiveSporeBombKnockbackMultiplier:0.##}, vfx x{effectiveSporeBombVfxCountMultiplier:0.##}, " + "shake-cap=" + ((effectiveSporeBombScreenshakeRangeCapMeters <= 0f) ? "vanilla" : $"{effectiveSporeBombScreenshakeRangeCapMeters:0}m") + " " + $"across {num2} AddScreenshake, " + $"spore-area-radius x{effectiveSporeBombSporeAreaRadiusMultiplier:0.##} across {num} cloud VFX transform(s))"); } private static int ScaleCloudVfx(GameObject spawned, double multiplier) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (multiplier == 1.0) { return 0; } float num = SporeAreaTuning.ScaleVisual(multiplier); int num2 = 0; ParticleSystem[] componentsInChildren = spawned.GetComponentsInChildren(true); foreach (ParticleSystem val in componentsInChildren) { Transform parent = ((Component)val).transform.parent; if (!((Object)(object)parent != (Object)null) || !((Object)(object)((Component)parent).GetComponentInParent(true) != (Object)null)) { Transform transform = ((Component)val).transform; transform.localScale *= num; num2++; } } return num2; } } [HarmonyPatch(typeof(TriggerEvent), "OnTriggerEnter")] internal static class SporeBombHeightGatePatch { private static bool Prefix(TriggerEvent __instance, Collider other) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (!RootsState.Active || Plugin.Cfg.KeepVanillaTriggerRadius.Value) { return true; } float num = SporeBombExplosionTuning.ResolveTriggerHeightCutoffMeters(Plugin.Cfg.EffectiveSporeBombTriggerHeightMultiplier); if (num <= 0f) { return true; } string name = ((Object)((Component)__instance).gameObject).name; if (!SporeBombCullPatch.ClassifySporeBomb(name) || SporeBombCullPatch.IsExplosiveVariant(name)) { return true; } Character val = default(Character); if (!CharacterRagdoll.TryGetCharacterFromCollider(other, ref val)) { return true; } float num2 = GameUnits.ToMeters(val.Center.y - ((Component)__instance).transform.position.y); if (SporeBombExplosionTuning.ShouldSuppressTriggerForHeight(num2, num)) { Diag.V($"[SporeBombHeightGate] suppressed trigger on \"{name}\" - player {num2:0.00}m above base (cutoff {num:0.00}m)"); return false; } return true; } } internal static class SporeBombRecolorPatch { private static readonly HashSet ExcludedProperties = new HashSet(StringComparer.Ordinal) { "_SpecColor", "_EmissionColor", "_RimColor", "_StatusColor", "_SkinColor" }; private static readonly Dictionary<(int RendererId, int MaterialIndex, int PropertyId), Color> VanillaColors = new Dictionary<(int, int, int), Color>(); private static readonly HashSet LoggedShaders = new HashSet(); private static Rgb? _sporeColor; internal static void Apply(Transform candidate, Rgb sporeColor, bool enabled) { Renderer[] componentsInChildren = ((Component)candidate).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (val is MeshRenderer || val is SkinnedMeshRenderer) { if ((Object)(object)((Component)val).GetComponentInParent() != (Object)null) { Diag.V("[SporeBombRecolor] skipped " + Path(((Component)val).transform) + " - it belongs to an Item, not scenery"); } else { ApplyToRenderer(val, sporeColor, enabled); } } } } private static void ApplyToRenderer(Renderer renderer, Rgb sporeColor, bool enabled) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) Material[] sharedMaterials = renderer.sharedMaterials; int instanceID = ((Object)renderer).GetInstanceID(); for (int i = 0; i < sharedMaterials.Length; i++) { Material val = sharedMaterials[i]; Shader val2 = (((Object)(object)val != (Object)null) ? val.shader : null); if ((Object)(object)val2 == (Object)null) { continue; } LogShaderInventoryOnce(val, val2); MaterialPropertyBlock val3 = new MaterialPropertyBlock(); renderer.GetPropertyBlock(val3, i); bool flag = false; int propertyCount = val2.GetPropertyCount(); for (int j = 0; j < propertyCount; j++) { if ((int)val2.GetPropertyType(j) != 0) { continue; } string propertyName = val2.GetPropertyName(j); if (!ExcludedProperties.Contains(propertyName)) { int propertyNameId = val2.GetPropertyNameId(j); (int, int, int) key = (instanceID, i, propertyNameId); if (!VanillaColors.TryGetValue(key, out var value)) { value = val.GetColor(propertyNameId); VanillaColors[key] = value; } Color val4 = (enabled ? ToColor(SporeBombRecolor.Recolor(ToRgb(value), sporeColor, 0.8), value.a) : value); val3.SetColor(propertyNameId, val4); flag = true; } } if (flag) { renderer.SetPropertyBlock(val3, i); } } } private static void LogShaderInventoryOnce(Material material, Shader shader) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (!Diag.Enabled || !LoggedShaders.Add(((Object)shader).GetInstanceID())) { return; } List list = new List(); int propertyCount = shader.GetPropertyCount(); for (int i = 0; i < propertyCount; i++) { if ((int)shader.GetPropertyType(i) == 0) { string propertyName = shader.GetPropertyName(i); Color color = material.GetColor(shader.GetPropertyNameId(i)); list.Add($"{propertyName}={ToRgb(color)}a={color.a:0.##}" + (ExcludedProperties.Contains(propertyName) ? " (excluded)" : string.Empty)); } } Diag.Info("[SporeBombRecolor] shader \"" + ((Object)shader).name + "\" (e.g. material \"" + ((Object)material).name + "\") color slots: " + ((list.Count == 0) ? "(none)" : string.Join(", ", list))); } internal static void ReapplyToAll() { if (Plugin.Cfg == null) { return; } try { bool flag = RootsState.Active && Plugin.Cfg.RecolorSporeBombs.Value; Rgb rgb = ResolveSporeColor(); int num = 0; Transform[] array = Object.FindObjectsOfType(true); foreach (Transform val in array) { if (SporeBombCullPatch.ClassifySporeBomb(((Object)val).name)) { num++; Apply(val, rgb, flag); } } Diag.Info($"[SporeBombRecolor] full refresh: {num} spore bomb(s) found scene-wide, " + string.Format("recolor={0}, target hue from {1}", flag ? "ON" : "OFF", rgb)); } catch (Exception ex) { Diag.Error("[SporeBombRecolor] ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static Rgb ResolveSporeColor() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (_sporeColor.HasValue) { return _sporeColor.Value; } try { Character localCharacter = Character.localCharacter; CharacterAfflictions val = (((Object)(object)localCharacter != (Object)null && localCharacter.refs != null) ? localCharacter.refs.afflictions : null); if ((Object)(object)val != (Object)null && ((Color)(ref val.colorSpores)).maxColorComponent > 0f) { Rgb rgb = ToRgb(val.colorSpores); _sporeColor = rgb; (double, double, double) tuple = SporeBombRecolor.ToHsv(rgb); Diag.Info($"[SporeBombRecolor] read live Spores status color {rgb} " + $"(hue={tuple.Item1:0.#}deg, saturation={tuple.Item2:0.##})"); return rgb; } } catch (Exception ex) { Diag.V("[SporeBombRecolor] could not read the live Spores status color (" + ex.GetType().Name + ") - using the fallback"); } return SporeBombRecolor.FallbackSporeColor; } private static string Path(Transform t) { string text = ((Object)t).name; Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; if (((Object)parent).name == "Roots Segment") { break; } parent = parent.parent; } return text; } private static Rgb ToRgb(Color c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) return new Rgb(c.r, c.g, c.b); } private static Color ToColor(Rgb c, float alpha) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) return new Color((float)c.R, (float)c.G, (float)c.B, alpha); } } } namespace Fairoots.SporeAreas { internal static class CoverMouthController { private const string CoveringProperty = "Fairoots.CoveringMouth"; private static readonly MethodInfo UseStaminaMethod = AccessTools.Method(typeof(Character), "UseStamina", new Type[2] { typeof(float), typeof(bool) }, (Type[])null); private static bool _loggedMissingUseStamina; private const byte TemporarySlotId = 250; internal static bool LocalCovering { get; private set; } internal static void Tick() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) try { PluginConfig cfg = Plugin.Cfg; KeyCode value = cfg.CoverMouthKey.Value; Character localCharacter = Character.localCharacter; if (!RootsState.Active || (int)value == 0 || (Object)(object)localCharacter == (Object)null || !cfg.EffectiveEnableCoverMouth) { SetCovering(covering: false, localCharacter, (!RootsState.Active) ? "not in the Roots biome" : ((!cfg.EffectiveEnableCoverMouth) ? "not enabled by host" : "no keybind set, or no local character")); return; } string reason; bool flag = IsAllowed(localCharacter, out reason); bool flag2 = CoverMouth.NextState(LocalCovering, Input.GetKeyDown(value), Input.GetKey(value), cfg.CoverMouthHold.Value, flag); if (flag2 != LocalCovering) { SetCovering(flag2, localCharacter, flag2 ? "key" : (flag ? "key" : reason)); } if (LocalCovering) { DrainStamina(localCharacter); } } catch (Exception ex) { Diag.Error("[CoverMouth] Tick threw: " + ex.GetType().Name + ": " + ex.Message); LocalCovering = false; } } private static bool IsAllowed(Character character, out string reason) { reason = string.Empty; if (character.data.dead || character.data.fullyPassedOut || !character.data.fullyConscious) { reason = "unconscious or dead"; return false; } if (character.data.isClimbing || character.data.isRopeClimbing || character.data.isVineClimbing || (Object)(object)character.data.currentClimbHandle != (Object)null) { reason = "holding onto something (climbing)"; return false; } if (character.refs.afflictions.isWebbed) { reason = "webbed"; return false; } if (Plugin.Cfg.EffectiveCoverMouthStaminaPerSecond > 0f && character.data.currentStamina < 0.005f && (!Plugin.Cfg.CoverMouthUseBonusStamina.Value || character.data.extraStamina < 0.001f)) { reason = "out of stamina"; return false; } if ((Object)(object)GUIManager.instance != (Object)null && (GUIManager.instance.windowBlockingInput || GUIManager.instance.wheelActive)) { reason = "a menu is open"; return false; } return true; } private static void SetCovering(bool covering, Character character, string reason) { if (covering != LocalCovering) { LocalCovering = covering; if (covering && (Object)(object)character != (Object)null) { FreeHands(character); } Publish(covering); Diag.V("[CoverMouth] local player " + (covering ? "covered" : "uncovered") + " their mouth (" + reason + "); " + $"{CoverMouthImmunityPatch.ParkedCount} spore area(s) with parked tick progress"); } } private static void FreeHands(Character character) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) CharacterItems items = character.refs.items; if (!((Object)(object)items == (Object)null) && !((Object)(object)character.data.currentItem == (Object)null) && items.currentSelectedSlot.IsSome) { byte value = items.currentSelectedSlot.Value; if (value == 250) { DropTemporaryItem(character, items); return; } items.EquipSlot(Optionable.None); Diag.V($"[CoverMouth] put away held item from slot {value} to free both hands"); } } private static void DropTemporaryItem(Character character, CharacterItems items) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) ItemSlot itemSlot = character.player.GetItemSlot((byte)250); if (itemSlot != null) { Vector3 position = ((Component)character.data.currentItem).transform.position; ((MonoBehaviourPun)character).photonView.RPC("DropItemRpc", (RpcTarget)0, new object[7] { 0f, (byte)250, position, Vector3.zero, ((Component)character.data.currentItem).transform.rotation, itemSlot.data, true }); Diag.V("[CoverMouth] dropped the temporarily-held (fourth) item to free both hands"); } } private static void DrainStamina(Character character) { float num = CoverMouth.StaminaCost(Plugin.Cfg.EffectiveCoverMouthStaminaPerSecond, Time.deltaTime); if (num <= 0f) { return; } if (UseStaminaMethod == null) { if (!_loggedMissingUseStamina) { _loggedMissingUseStamina = true; Diag.Warn("[CoverMouth] Character.UseStamina not found - covering your mouth will cost no stamina. Game update?"); } } else { UseStaminaMethod.Invoke(character, new object[2] { num, Plugin.Cfg.CoverMouthUseBonusStamina.Value }); } } private static void Publish(bool covering) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown //IL_002f: Expected O, but got Unknown Player localPlayer = PhotonNetwork.LocalPlayer; if (localPlayer != null && PhotonNetwork.InRoom) { Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"Fairoots.CoveringMouth", (object)covering); localPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); } } internal static bool IsCovering(Character character) { if ((Object)(object)character == (Object)null) { return false; } if ((Object)(object)character == (Object)(object)Character.localCharacter) { return LocalCovering; } Player val = (((Object)(object)((MonoBehaviourPun)character).photonView != (Object)null) ? ((MonoBehaviourPun)character).photonView.Owner : null); if (val == null) { return false; } bool flag = default(bool); int num; if (((Dictionary)(object)val.CustomProperties).TryGetValue((object)"Fairoots.CoveringMouth", out object value)) { if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static void RepublishOnJoin() { Publish(LocalCovering); } } [HarmonyPatch] internal static class CoverMouthImmunityPatch { private static readonly HashSet ForcedPlainEmitters = new HashSet(); private static readonly HashSet _loggedBombEmitters = new HashSet(); private static readonly Dictionary SavedTickProgress = new Dictionary(); private static readonly FieldRef TickProgressRef = AccessTools.FieldRefAccess("timeSinceLastTick"); internal static int ParkedCount => SavedTickProgress.Count; internal static void ClearLevelState() { SavedTickProgress.Clear(); ForcedPlainEmitters.Clear(); _loggedBombEmitters.Clear(); } [HarmonyPostfix] [HarmonyPatch(typeof(WindAffectedStatusEmitter), "FixedUpdate")] private static void WindAffectedFixedUpdatePostfix(WindAffectedStatusEmitter __instance) { if (ShouldSuppress((StatusEmitter)(object)__instance)) { ((StatusEmitter)__instance).emitterDisabledByWind = true; } } [HarmonyPrefix] [HarmonyPatch(typeof(StatusEmitter), "Update")] private static void UpdatePrefix(StatusEmitter __instance) { bool num = ShouldSuppress(__instance); int instanceID = ((Object)__instance).GetInstanceID(); if (num) { if (!(__instance is WindAffectedStatusEmitter)) { __instance.emitterDisabledByWind = true; ForcedPlainEmitters.Add(instanceID); } if (!IsFromSporeBomb(__instance) && IsPlayerInRange(__instance)) { SavedTickProgress[instanceID] = TickProgressRef.Invoke(__instance); } else { SavedTickProgress.Remove(instanceID); } } else if (!(__instance is WindAffectedStatusEmitter) && ForcedPlainEmitters.Remove(instanceID)) { __instance.emitterDisabledByWind = false; } } [HarmonyPostfix] [HarmonyPatch(typeof(StatusEmitter), "Update")] private static void UpdatePostfix(StatusEmitter __instance) { if (ShouldSuppress(__instance)) { return; } int instanceID = ((Object)__instance).GetInstanceID(); if (!SavedTickProgress.TryGetValue(instanceID, out var value) || __instance.emitterDisabledByWind) { return; } if (!IsPlayerInRange(__instance)) { SavedTickProgress.Remove(instanceID); return; } SavedTickProgress.Remove(instanceID); if (TickProgressRef.Invoke(__instance) < value) { TickProgressRef.Invoke(__instance) = value; Diag.V($"[CoverMouth] resumed spore tick progress at {value:0.###}s on \"{((Object)__instance).name}\" (uncovered)"); } } private static bool IsPlayerInRange(StatusEmitter emitter) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null) { return false; } return Vector3.Distance(localCharacter.Center, ((Component)emitter).transform.position) < emitter.radius + emitter.outerFade; } private static bool ShouldSuppress(StatusEmitter emitter) { if (!RootsState.Active || !CoverMouthController.LocalCovering || !SporeAreaScan.IsSporeArea(emitter)) { return false; } if (!IsFromSporeBomb(emitter)) { return true; } return Plugin.Cfg.EffectiveCoverMouthBlocksSporeBombs; } private static bool IsFromSporeBomb(StatusEmitter emitter) { bool num = SporeAreaScan.IsSporeBombSpawned(((Component)emitter).transform); if (num && _loggedBombEmitters.Add(((Object)emitter).GetInstanceID())) { Diag.V("[CoverMouth] spore-bomb detonation left a lingering spore emitter: " + $"\"{SporeAreaScan.DescribePath(((Component)emitter).transform)}\" (radius={emitter.radius}, amount={emitter.amount})"); } return num; } } internal static class CoverMouthPose { [HarmonyPatch(typeof(CharacterAnimations), "HandleIK")] internal static class HandleIKPatch { private static void Postfix(CharacterAnimations __instance) { Character val = CharacterRef.Invoke(__instance); if ((Object)(object)val == (Object)null) { return; } CharacterRefs refs = val.refs; if ((Object)(object)refs.ikRig == (Object)null || (Object)(object)refs.ikLeft == (Object)null || (Object)(object)refs.ikRight == (Object)null) { return; } bool flag = ShouldPose(val); ApplyRotationWeight(refs.ikLeft, flag); ApplyRotationWeight(refs.ikRight, flag); if (flag) { refs.ikRig.weight = 1f; ((RigConstraint>)(object)refs.ikLeft).weight = 1f; ((RigConstraint>)(object)refs.ikRight).weight = 1f; if (!HasCapturedPose && !_captureFailed) { Diag.Warn("[CoverMouthPose] pose wasn't pre-warmed at spawn - capturing now, which may jolt the character."); TryCapturePose(val); } } } } [HarmonyPatch(typeof(CharacterAnimations), "ConfigureIK")] internal static class ConfigureIKPatch { private static void Postfix(CharacterAnimations __instance) { Character val = CharacterRef.Invoke(__instance); if (!((Object)(object)val == (Object)null) && ShouldPose(val)) { CharacterRefs refs = val.refs; PlaceHands(val, refs); ApplyFingerPose(((Object)(object)refs.ikLeft != (Object)null) ? ((TwoBoneIKConstraintData)(ref ((RigConstraint>)(object)refs.ikLeft).data)).tip : null, _leftFingerPose); ApplyFingerPose(((Object)(object)refs.ikRight != (Object)null) ? ((TwoBoneIKConstraintData)(ref ((RigConstraint>)(object)refs.ikRight).data)).tip : null, _rightFingerPose); } } private static Quaternion HandTurn(bool mirrored) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) PluginConfig cfg = Plugin.Cfg; float value = cfg.CoverMouthPoseHandPitchDeg.Value; float value2 = cfg.CoverMouthPoseHandYawDeg.Value; float value3 = cfg.CoverMouthPoseHandRollDeg.Value; if (!mirrored) { return Quaternion.Euler(value, value2, value3); } return Quaternion.Euler(value, 0f - value2, 0f - value3); } private static void PlaceHands(Character character, CharacterRefs refs) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)refs.IKHandTargetLeft == (Object)null) && !((Object)(object)refs.IKHandTargetRight == (Object)null) && !((Object)(object)refs.animationHeadTransform == (Object)null) && !((Object)(object)refs.animationLookTransform == (Object)null)) { PluginConfig cfg = Plugin.Cfg; float num = GameUnits.MetersToUnits(cfg.CoverMouthPoseForwardCm.Value / 100f); float num2 = GameUnits.MetersToUnits(cfg.CoverMouthPoseBelowHeadCm.Value / 100f); float num3 = GameUnits.MetersToUnits(cfg.CoverMouthPoseHandGapCm.Value / 100f) * 0.5f; float num4 = GameUnits.MetersToUnits(cfg.CoverMouthPoseSideCm.Value / 100f); Vector3 position = refs.animationHeadTransform.position; Transform animationLookTransform = refs.animationLookTransform; refs.IKHandTargetLeft.position = position + animationLookTransform.TransformDirection(new Vector3(0f - num4, 0f - num2 + num3, num)); refs.IKHandTargetRight.position = position + animationLookTransform.TransformDirection(new Vector3(num4, 0f - num2 - num3, num)); Transform val = BodyRoot(character); if ((Object)(object)val != (Object)null && HasCapturedPose) { refs.IKHandTargetLeft.rotation = val.rotation * HandTurn(mirrored: true) * _leftWristBodyRelative; refs.IKHandTargetRight.rotation = val.rotation * HandTurn(mirrored: false) * _rightWristBodyRelative; } } } } [HarmonyPatch(typeof(CharacterAnimations), "PlayEmote")] internal static class EmotePlayedProbe { private static void Prefix(string emoteName) { Diag.V("[CoverMouthPose] emote-played: animator state \"" + emoteName + "\" - paste this into Debug/cover-mouth-pose-emote to pose from it"); } } private const string DefaultPoseState = "A_Scout_Emote_Defeat"; private static readonly FieldRef CharacterRef = AccessTools.FieldRefAccess("character"); private static readonly int AnimEmote = Animator.StringToHash("Emote"); private static readonly Dictionary OverriddenRotationWeights = new Dictionary(); private static Dictionary _leftFingerPose; private static Dictionary _rightFingerPose; private static Quaternion _leftWristBodyRelative = Quaternion.identity; private static Quaternion _rightWristBodyRelative = Quaternion.identity; private const float PrewarmDelaySeconds = 3f; private const float PrewarmRetrySeconds = 2f; private const int MaxPrewarmAttempts = 5; private static float _nextPrewarmAttempt; private static int _prewarmAttempts; private static bool _captureFailed; private static bool _layersLogged; private static string _poseState; private static bool _poseStateResolved; private static bool HasCapturedPose { get { if (_leftFingerPose != null) { return _rightFingerPose != null; } return false; } } private static bool ShouldPose(Character character) { if (!RootsState.Active) { return false; } if (CoverMouthController.IsCovering(character)) { return true; } if (Plugin.Cfg.CoverMouthPosePreview.Value) { return (Object)(object)character == (Object)(object)Character.localCharacter; } return false; } internal static void InvalidateEmote() { _poseStateResolved = false; _poseState = null; _leftFingerPose = null; _rightFingerPose = null; _captureFailed = false; _nextPrewarmAttempt = 0f; _prewarmAttempts = 0; } private static string ResolvePoseState(Animator animator) { if (_poseStateResolved) { return _poseState; } _poseStateResolved = true; string value = Plugin.Cfg.CoverMouthPoseEmote.Value; bool flag = !string.IsNullOrWhiteSpace(value); _poseState = (flag ? value.Trim() : "A_Scout_Emote_Defeat"); if ((Object)(object)animator != (Object)null && !animator.HasState(0, Animator.StringToHash(_poseState))) { Diag.Warn("[CoverMouthPose] animator has no state \"" + _poseState + "\" - the pose will have no hand/finger shape (arm IK only). It needs an animator state name like \"A_Scout_Emote_Defeat\", not an emote's on-screen label; play an emote in-game to have its real name logged."); LogEmoteCatalogue(); _poseState = null; return null; } Diag.V("[CoverMouthPose] capturing hand pose from \"" + _poseState + "\"" + (flag ? " (from config)" : " - the \"it's so over\" emote")); return _poseState; } private static void LogEmoteCatalogue() { EmoteWheelData[] array = Resources.FindObjectsOfTypeAll(); if (array == null || array.Length == 0) { Diag.Warn("[CoverMouthPose] no EmoteWheelData found to list."); return; } Diag.Info("[CoverMouthPose] available emotes (key | on-screen label | animator state): " + string.Join("; ", array.Select((EmoteWheelData c) => c.emoteName + " | \"" + LocalizedText.GetText(c.emoteName, false) + "\" | " + c.anim))); } private static void ApplyRotationWeight(TwoBoneIKConstraint constraint, bool covering) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)constraint == (Object)null) { return; } int instanceID = ((Object)constraint).GetInstanceID(); TwoBoneIKConstraintData data = ((RigConstraint>)(object)constraint).data; float value; if (covering) { if (!OverriddenRotationWeights.ContainsKey(instanceID)) { OverriddenRotationWeights[instanceID] = ((TwoBoneIKConstraintData)(ref data)).targetRotationWeight; } if (((TwoBoneIKConstraintData)(ref data)).targetRotationWeight != 1f) { ((TwoBoneIKConstraintData)(ref data)).targetRotationWeight = 1f; ((RigConstraint>)(object)constraint).data = data; } } else if (OverriddenRotationWeights.TryGetValue(instanceID, out value)) { OverriddenRotationWeights.Remove(instanceID); ((TwoBoneIKConstraintData)(ref data)).targetRotationWeight = value; ((RigConstraint>)(object)constraint).data = data; } } private static void CollectHandBones(Transform root, List into) { into.Add(root); for (int i = 0; i < root.childCount; i++) { CollectHandBones(root.GetChild(i), into); } } private static bool TryCapturePose(Character character) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_0113: 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_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) CharacterRefs refs = character.refs; Transform val = (((Object)(object)refs.ikLeft != (Object)null) ? ((TwoBoneIKConstraintData)(ref ((RigConstraint>)(object)refs.ikLeft).data)).tip : null); Transform val2 = (((Object)(object)refs.ikRight != (Object)null) ? ((TwoBoneIKConstraintData)(ref ((RigConstraint>)(object)refs.ikRight).data)).tip : null); Animator animator = refs.animator; Transform val3 = BodyRoot(character); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)animator == (Object)null || !animator.isInitialized || (Object)(object)val3 == (Object)null) { return false; } string text = ResolvePoseState(animator); if (string.IsNullOrEmpty(text)) { _captureFailed = true; return false; } LogAnimatorLayersOnce(animator); AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(0); float[] array = new float[animator.layerCount]; for (int i = 1; i < animator.layerCount; i++) { array[i] = animator.GetLayerWeight(i); animator.SetLayerWeight(i, 0f); } try { animator.Play(text, 0, Plugin.Cfg.CoverMouthPoseEmoteTime.Value); animator.Update(0f); _leftWristBodyRelative = Quaternion.Inverse(val3.rotation) * val.rotation; _rightWristBodyRelative = Quaternion.Inverse(val3.rotation) * val2.rotation; _leftFingerPose = FingerLocals(val); _rightFingerPose = FingerLocals(val2); } finally { for (int j = 1; j < animator.layerCount; j++) { animator.SetLayerWeight(j, array[j]); } animator.SetBool(AnimEmote, false); animator.Play(((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash, 0, ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime); animator.Update(0f); } Diag.V($"[CoverMouthPose] captured hand pose from \"{text}\" at t={Plugin.Cfg.CoverMouthPoseEmoteTime.Value:0.##}: " + $"{_leftFingerPose.Count} left finger bone(s), {_rightFingerPose.Count} right finger bone(s)"); return true; } internal static void Prewarm() { if (HasCapturedPose || _captureFailed || Plugin.Cfg == null) { return; } Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null) { _nextPrewarmAttempt = 0f; } else if (_nextPrewarmAttempt <= 0f) { _nextPrewarmAttempt = Time.time + 3f; } else if (!(Time.time < _nextPrewarmAttempt)) { _nextPrewarmAttempt = Time.time + 2f; _prewarmAttempts++; if (TryCapturePose(localCharacter)) { Diag.V($"[CoverMouthPose] pose pre-warmed at spawn after {_prewarmAttempts} attempt(s)"); } else if (_prewarmAttempts >= 5 && !_captureFailed) { _captureFailed = true; Diag.Warn($"[CoverMouthPose] gave up pre-warming the hand pose after {_prewarmAttempts} attempts - " + "covering your mouth will use arm IK only (no finger shape)."); } } } private static void LogAnimatorLayersOnce(Animator animator) { if (!_layersLogged && Diag.Enabled) { _layersLogged = true; List list = new List(); for (int i = 0; i < animator.layerCount; i++) { list.Add($"[{i}] \"{animator.GetLayerName(i)}\" weight={animator.GetLayerWeight(i):0.##}"); } Diag.V("[CoverMouthPose] animator \"" + (((Object)(object)animator.runtimeAnimatorController != (Object)null) ? ((Object)animator.runtimeAnimatorController).name : "none") + "\" " + string.Format("has {0} layer(s): {1}", animator.layerCount, string.Join(", ", list))); } } private static Transform BodyRoot(Character character) { if (!((Object)(object)character.refs.animator != (Object)null)) { return null; } return ((Component)character.refs.animator).transform; } private static Dictionary FingerLocals(Transform tip) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) List list = new List(); CollectHandBones(tip, list); Dictionary dictionary = new Dictionary(); foreach (Transform item in list.Skip(1)) { dictionary[((Object)item).name] = item.localRotation; } return dictionary; } private static void ApplyFingerPose(Transform tip, Dictionary pose) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)tip == (Object)null || pose == null) { return; } List list = new List(); CollectHandBones(tip, list); for (int i = 1; i < list.Count; i++) { if (pose.TryGetValue(((Object)list[i]).name, out var value)) { list[i].localRotation = value; } } } } internal static class CoverMouthRestrictions { internal static bool HandsBusy => CoverMouthController.LocalCovering; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class CoverMouthInteractionPatch { private static void Postfix(ref bool __result) { if (CoverMouthRestrictions.HandsBusy) { __result = false; } } } [HarmonyPatch(typeof(CharacterItems), "DoSwitching")] internal static class CoverMouthSwitchingPatch { private static bool Prefix(CharacterItems __instance) { if (!CoverMouthRestrictions.HandsBusy) { return true; } Character component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null)) { return (Object)(object)component != (Object)(object)Character.localCharacter; } return true; } } [HarmonyPatch(typeof(CharacterClimbing), "TryToStartWallClimb", new Type[] { typeof(bool), typeof(Vector3), typeof(bool), typeof(float) })] internal static class CoverMouthWallClimbPatch { private static bool Prefix(CharacterClimbing __instance) { if (!CoverMouthRestrictions.HandsBusy) { return true; } Character component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null)) { return (Object)(object)component != (Object)(object)Character.localCharacter; } return true; } } internal static class SporeAreaCullPatch { internal static readonly List RemovedPositions = new List(); internal static void ClearLevelState() { RemovedPositions.Clear(); } internal static void Run(Transform rootsSegment) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) try { RemovedPositions.Clear(); List list = SporeAreaScan.FilterSporeAreas(((Component)rootsSegment).GetComponentsInChildren(true)); if (list.Count == 0) { Diag.V("[SporeAreaCull] no spore areas found under Roots Segment"); return; } double effectiveSporeAreaRemovalFraction = Plugin.Cfg.EffectiveSporeAreaRemovalFraction; List list2 = new List(list.Count); foreach (StatusEmitter item in list) { Vector3 position = ((Component)item).transform.position; list2.Add(GridPos.Round(position.x, position.y, position.z)); } bool[] array = SporeAreaCull.Decide(list2, effectiveSporeAreaRemovalFraction, Plugin.Cfg.EffectiveSeed); int num = 0; List list3 = new List(); List list4 = new List(); for (int i = 0; i < list.Count; i++) { float num2 = GameUnits.ToMeters(NearestOtherDistance(list2, i)); if (!array[i]) { list4.Add(num2); continue; } GameObject val = SporeAreaScan.ResolveAreaRoot(list[i]); RemovedPositions.Add(((Component)list[i]).transform.position); val.SetActive(false); num++; list3.Add(num2); Diag.V($"[SporeAreaCull] removed \"{SporeAreaScan.DescribePath(val.transform)}\" @ {list2[i]} " + $"(nearest other spore area {num2:0.0}m)"); } Diag.Info($"[SporeAreaCull] {list.Count} spore area(s): removed {num}, kept {list.Count - num} " + $"(fraction={effectiveSporeAreaRemovalFraction:0.###}, seed={Plugin.Cfg.EffectiveSeed})"); if (num > 0 && num < list.Count) { Diag.Info($"[SporeAreaCull] nearest-neighbour spacing - removed: median {Median(list3):0.0}m " + $"(min {list3.Min():0.0}m), kept: median {Median(list4):0.0}m " + $"(min {list4.Min():0.0}m). Removed median should be the lower of the two."); } } catch (Exception ex) { Diag.Error("[SporeAreaCull] threw: " + ex.GetType().Name + ": " + ex.Message); } } private static float Median(List values) { List list = values.OrderBy((float v) => v).ToList(); int num = list.Count / 2; if (list.Count % 2 != 1) { return (list[num - 1] + list[num]) * 0.5f; } return list[num]; } private static float NearestOtherDistance(IReadOnlyList positions, int index) { long num = long.MaxValue; GridPos gridPos = positions[index]; for (int i = 0; i < positions.Count; i++) { if (i != index) { GridPos gridPos2 = positions[i]; long num2 = gridPos.X - gridPos2.X; long num3 = gridPos.Y - gridPos2.Y; long num4 = gridPos.Z - gridPos2.Z; long num5 = num2 * num2 + num3 * num3 + num4 * num4; if (num5 < num) { num = num5; } } } if (num != long.MaxValue) { return Mathf.Sqrt((float)num); } return 0f; } } internal static class SporeAreaDisablePatch { private static readonly Dictionary Deactivated = new Dictionary(); internal static void Run(Transform rootsSegment) { try { Apply(((Component)rootsSegment).GetComponentsInChildren(true), "level load"); } catch (Exception ex) { Diag.Error("[SporeAreas] Run threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ReapplyToAll() { if (Plugin.Cfg == null) { return; } try { Apply(Object.FindObjectsOfType(true), "config change"); } catch (Exception ex) { Diag.Error("[SporeAreas] ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ClearLevelState() { Deactivated.Clear(); } private static void Apply(IReadOnlyList emitters, string reason) { bool flag = RootsState.Active && Plugin.Cfg.EffectiveDisableSporeAreas; List list = SporeAreaScan.FilterSporeAreas(emitters); int num = 0; int num2 = 0; foreach (StatusEmitter item in list) { GameObject val = SporeAreaScan.ResolveAreaRoot(item); int instanceID = ((Object)val).GetInstanceID(); GameObject value; if (flag) { if (val.activeSelf) { val.SetActive(false); Deactivated[instanceID] = val; num++; Diag.V($"[SporeAreas] disabled \"{SporeAreaScan.DescribePath(val.transform)}\" (radius={item.radius}, amount={item.amount})"); } } else if (Deactivated.TryGetValue(instanceID, out value)) { Deactivated.Remove(instanceID); if ((Object)(object)value != (Object)null) { value.SetActive(true); num2++; } } } Diag.Info("[SporeAreas] " + reason + ": disable-spore-areas=" + (flag ? "ON" : "off") + ", " + $"{list.Count} spore area(s) found, {num} newly hidden, {num2} restored"); } } internal static class SporeAreaScan { private const int MaxParentWalk = 3; internal static bool IsSporeArea(StatusEmitter emitter) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((Object)(object)emitter != (Object)null && (int)emitter.statusType == 10) { return emitter.amount >= 0f; } return false; } internal static bool IsSporeBombSpawned(Transform t) { Transform val = t; while ((Object)(object)val != (Object)null) { if (SporeBombCullPatch.ClassifySporeBomb(((Object)val).name) || ((Object)val).name.IndexOf("Explosion", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (((Object)val).name == "Roots Segment") { break; } val = val.parent; } return false; } internal static GameObject ResolveAreaRoot(StatusEmitter emitter) { Transform val = ((Component)emitter).transform; for (int i = 0; i < 3; i++) { Transform parent = val.parent; if ((Object)(object)parent == (Object)null || ((Object)parent).name == "Roots Segment" || IsGroupingNode(parent) || ((Component)parent).GetComponentsInChildren(true).Length > 1) { break; } val = parent; } return ((Component)val).gameObject; } private static bool IsGroupingNode(Transform t) { if (!((Object)(object)((Component)t).GetComponent() != (Object)null) && !((Object)(object)((Component)t).GetComponent() != (Object)null)) { return (Object)(object)((Component)t).GetComponent() != (Object)null; } return true; } internal static List FilterSporeAreas(IReadOnlyList emitters) { List list = new List(); for (int i = 0; i < emitters.Count; i++) { StatusEmitter val = emitters[i]; if (IsSporeArea(val) && !IsSporeBombSpawned(((Component)val).transform)) { list.Add(val); } } return list; } internal static string DescribePath(Transform t) { List list = new List(); Transform val = t; while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); if (((Object)val).name == "Roots Segment") { break; } val = val.parent; } return string.Join(" < ", list); } } internal static class SporeAreaTuningPatch { private readonly struct EmitterBaseline { internal readonly float Radius; internal readonly float InnerFade; internal readonly float OuterFade; internal readonly float Amount; internal EmitterBaseline(float radius, float innerFade, float outerFade, float amount) { Radius = radius; InnerFade = innerFade; OuterFade = outerFade; Amount = amount; } } private static readonly Dictionary VanillaEmitters = new Dictionary(); private static readonly Dictionary VanillaVfxScales = new Dictionary(); private static bool _structureLogged; internal static void Run(Transform rootsSegment) { try { Apply(((Component)rootsSegment).GetComponentsInChildren(true), "level load"); } catch (Exception ex) { Diag.Error("[SporeAreaTuning] Run threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ReapplyToAll() { if (Plugin.Cfg == null) { return; } try { Apply(Object.FindObjectsOfType(true), "config change"); } catch (Exception ex) { Diag.Error("[SporeAreaTuning] ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void Apply(IReadOnlyList emitters, string reason) { double num = (RootsState.Active ? Plugin.Cfg.EffectiveSporeAreaRadiusMultiplier : 1.0); double num2 = (RootsState.Active ? Plugin.Cfg.EffectiveSporeAreaStatusRateMultiplier : 1.0); List list = SporeAreaScan.FilterSporeAreas(emitters); if (list.Count == 0) { return; } LogStructureOnce(list[0]); int num3 = 0; int num4 = 0; int num5 = 0; float num6 = 0f; float num7 = 0f; float num8 = 0f; float num9 = 0f; foreach (StatusEmitter item in list) { int instanceID = ((Object)item).GetInstanceID(); if (!VanillaEmitters.TryGetValue(instanceID, out var value)) { value = new EmitterBaseline(item.radius, item.innerFade, item.outerFade, item.amount); VanillaEmitters[instanceID] = value; } item.radius = SporeAreaTuning.ScaleRadius(value.Radius, num); item.innerFade = SporeAreaTuning.ScaleFade(value.InnerFade, num); item.outerFade = SporeAreaTuning.ScaleFade(value.OuterFade, num); item.amount = SporeAreaTuning.ScaleStatusRate(value.Amount, num2); num3++; num6 = value.Radius; num7 = item.radius; num8 = value.Amount; num9 = item.amount; int num10 = ScaleVfx(SporeAreaScan.ResolveAreaRoot(item), num); if (num10 > 0) { num4 += num10; } else { num5++; } } Diag.Info($"[SporeAreaTuning] {reason}: {num3} spore area(s) " + (RootsState.Active ? "tuned" : "restored to vanilla (not in Roots)") + ". " + $"radius x{num:0.###} (e.g. {num6:0.##} -> {num7:0.##} world units, " + $"{GameUnits.ToMeters(num6):0.#}m -> {GameUnits.ToMeters(num7):0.#}m), " + $"{num4} cloud VFX transform(s) scaled" + ((num5 > 0) ? $", {num5} area(s) had no VFX to scale" : string.Empty) + $". status rate x{num2:0.###} (e.g. amount {num8:0.####} -> {num9:0.####} per second)"); } private static int ScaleVfx(GameObject areaRoot, double multiplier) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) int num = 0; float num2 = SporeAreaTuning.ScaleVisual(multiplier); ParticleSystem[] componentsInChildren = areaRoot.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Transform transform = ((Component)componentsInChildren[i]).transform; int instanceID = ((Object)transform).GetInstanceID(); if (!VanillaVfxScales.TryGetValue(instanceID, out var value)) { value = transform.localScale; VanillaVfxScales[instanceID] = value; } transform.localScale = value * num2; num++; } return num; } private static void LogStructureOnce(StatusEmitter sample) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if (_structureLogged || !Diag.Enabled) { return; } _structureLogged = true; GameObject val = SporeAreaScan.ResolveAreaRoot(sample); Diag.V("[SporeAreaTuning] structure of \"" + SporeAreaScan.DescribePath(val.transform) + "\":"); Transform[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Transform val2 in componentsInChildren) { List list = new List(); Component[] components = ((Component)val2).GetComponents(); foreach (Component val3 in components) { list.Add(((Object)(object)val3 == (Object)null) ? "" : ((object)val3).GetType().Name); } Diag.V(string.Format("[SporeAreaTuning] \"{0}\" scale={1} : {2}", ((Object)val2).name, val2.localScale, string.Join(", ", list))); } } } internal static class SporeCloudOpacityPatch { internal static void Run(Transform rootsSegment) { try { Apply(SporeAreaScan.FilterSporeAreas(((Component)rootsSegment).GetComponentsInChildren(true)), "level load"); } catch (Exception ex) { Diag.Error("[SporeCloudOpacity] Run threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ReapplyToAll() { if (Plugin.Cfg == null) { return; } try { Apply(SporeAreaScan.FilterSporeAreas(Object.FindObjectsOfType(true)), "config change"); } catch (Exception ex) { Diag.Error("[SporeCloudOpacity] ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void Apply(IReadOnlyList areas, string reason) { if (areas.Count == 0) { return; } double num = (RootsState.Active ? Plugin.Cfg.SporeAreaCloudOpacity.Value : 1.0); int num2 = 0; int num3 = 0; for (int i = 0; i < areas.Count; i++) { int num4 = ParticleOpacity.Apply(SporeAreaScan.ResolveAreaRoot(areas[i]), num); num2 += num4; if (num4 == 0) { num3++; } } Diag.Info($"[SporeCloudOpacity] {reason}: {areas.Count} spore area(s), {num2} particle system(s) " + string.Format("at opacity x{0:0.###}{1}", num, SporeCloudOpacity.IsVanilla(num) ? " (vanilla - authored values restored)" : string.Empty) + ((num3 > 0) ? $", {num3} area(s) had no VFX to thin" : string.Empty)); } } } namespace Fairoots.Networking { [HarmonyPatch(typeof(BoardingPass), "StartGame")] internal static class BoardingPassStartGatePatch { private static bool _bypassNextCheck; private static bool Prefix(BoardingPass __instance) { try { if (_bypassNextCheck) { _bypassNextCheck = false; return true; } List missingPlayers = ModPresenceCheck.GetMissingPlayers(); if (missingPlayers.Count == 0) { return true; } Diag.Warn($"[BoardingPassStartGatePatch] Start blocked pending confirmation - {missingPlayers.Count} " + "player(s) missing Fairoots: " + string.Join(", ", missingPlayers.ConvertAll((Player p) => p.NickName))); ((MenuWindow)__instance).Hide(); ModPresenceDialog.ShowStartConfirm(delegate { _bypassNextCheck = true; ((MenuWindow)__instance).Show(); __instance.StartGame(); }, delegate { ((MenuWindow)__instance).Show(); Diag.Warn("[BoardingPassStartGatePatch] Start canceled by the player."); }); return false; } catch (Exception ex) { Diag.Error("[BoardingPassStartGatePatch] threw: " + ex.GetType().Name + ": " + ex.Message); return true; } } } internal static class HostAuthority { private const string KeyPrefix = "Fairoots."; private static readonly Dictionary Published = new Dictionary(); internal static bool IsHost => PhotonNetwork.IsMasterClient; internal static int Resolve(string key, int localValue) { if (IsHost) { return localValue; } if (TryGetRoomProperty(key, out var value) && value is int) { return (int)value; } LogFallback(key, value, localValue); return localValue; } internal static float Resolve(string key, float localValue) { if (IsHost) { return localValue; } if (TryGetRoomProperty(key, out var value) && value is float) { return (float)value; } LogFallback(key, value, localValue); return localValue; } internal static double Resolve(string key, double localValue) { if (IsHost) { return localValue; } if (TryGetRoomProperty(key, out var value) && value is double) { return (double)value; } LogFallback(key, value, localValue); return localValue; } internal static bool Resolve(string key, bool localValue) { if (IsHost) { return localValue; } if (TryGetRoomProperty(key, out var value) && value is bool) { return (bool)value; } LogFallback(key, value, localValue); return localValue; } private static void LogFallback(string key, object rawValue, object localValue) { if (Diag.Enabled) { string arg = ((rawValue == null) ? "no room property published yet" : $"unexpected type {rawValue.GetType().Name} (value={rawValue})"); Diag.V($"[HostAuthority] Resolve(\"{key}\") fell back to local value {localValue} - {arg}"); } } internal unsafe static void RefreshCache() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) Published.Clear(); Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null) { return; } DictionaryEntryEnumerator enumerator = ((RoomInfo)currentRoom).CustomProperties.GetEnumerator(); try { while (((DictionaryEntryEnumerator)(ref enumerator)).MoveNext()) { DictionaryEntry current = ((DictionaryEntryEnumerator)(ref enumerator)).Current; if (current.Key is string text && text.StartsWith("Fairoots.", StringComparison.Ordinal)) { Published[text.Substring("Fairoots.".Length)] = current.Value; } } } finally { ((IDisposable)(*(DictionaryEntryEnumerator*)(&enumerator))/*cast due to .constrained prefix*/).Dispose(); } Diag.V($"[HostAuthority] cached {Published.Count} published value(s) from the room."); } private static bool TryGetRoomProperty(string key, out object value) { return Published.TryGetValue(key, out value); } internal static void PublishAll() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Expected O, but got Unknown //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Expected O, but got Unknown //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected O, but got Unknown //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Expected O, but got Unknown //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Expected O, but got Unknown //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Expected O, but got Unknown //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Expected O, but got Unknown //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Expected O, but got Unknown //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Expected O, but got Unknown //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Expected O, but got Unknown //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Expected O, but got Unknown //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Expected O, but got Unknown //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Expected O, but got Unknown //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Expected O, but got Unknown //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Expected O, but got Unknown //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Expected O, but got Unknown //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Expected O, but got Unknown //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Expected O, but got Unknown //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Expected O, but got Unknown //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Expected O, but got Unknown //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_034e: Expected O, but got Unknown //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Expected O, but got Unknown //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Expected O, but got Unknown //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_0390: Expected O, but got Unknown //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_03a6: Expected O, but got Unknown //IL_03a6: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Expected O, but got Unknown //IL_03bc: Unknown result type (might be due to invalid IL or missing references) //IL_03d2: Expected O, but got Unknown //IL_03d2: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Expected O, but got Unknown //IL_03e8: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Expected O, but got Unknown //IL_03fe: Unknown result type (might be due to invalid IL or missing references) //IL_0414: Expected O, but got Unknown //IL_0414: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Expected O, but got Unknown //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_0440: Expected O, but got Unknown //IL_0440: Unknown result type (might be due to invalid IL or missing references) //IL_0456: Expected O, but got Unknown //IL_0456: Unknown result type (might be due to invalid IL or missing references) //IL_046c: Expected O, but got Unknown //IL_046c: Unknown result type (might be due to invalid IL or missing references) //IL_0482: Expected O, but got Unknown //IL_0482: Unknown result type (might be due to invalid IL or missing references) //IL_0498: Expected O, but got Unknown //IL_0499: Expected O, but got Unknown if (!IsHost) { Diag.V("[HostAuthority] PublishAll skipped - not the master client."); return; } Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null) { Diag.V("[HostAuthority] PublishAll skipped - not in a Photon room."); return; } PluginConfig cfg = Plugin.Cfg; Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"Fairoots.Seed", (object)cfg.Seed.Value); ((Dictionary)val).Add((object)"Fairoots.SporeBombCullFraction", (object)cfg.EffectiveSporeBombCullFraction); ((Dictionary)val).Add((object)"Fairoots.SporeBombTriggerRadiusMultiplier", (object)cfg.EffectiveSporeBombTriggerRadiusMultiplier); ((Dictionary)val).Add((object)"Fairoots.SporeBombKnockbackMultiplier", (object)cfg.EffectiveSporeBombKnockbackMultiplier); ((Dictionary)val).Add((object)"Fairoots.SporeBombScreenshakeRangeCapMeters", (object)cfg.EffectiveSporeBombScreenshakeRangeCapMeters); ((Dictionary)val).Add((object)"Fairoots.SporeBombVfxCountMultiplier", (object)cfg.EffectiveSporeBombVfxCountMultiplier); ((Dictionary)val).Add((object)"Fairoots.SporeBombTriggerHeightMultiplier", (object)cfg.EffectiveSporeBombTriggerHeightMultiplier); ((Dictionary)val).Add((object)"Fairoots.EnableFoliageRemoval", (object)cfg.EffectiveEnableFoliageRemoval); ((Dictionary)val).Add((object)"Fairoots.SporeBombSporeAreaRadiusMultiplier", (object)cfg.EffectiveSporeBombSporeAreaRadiusMultiplier); ((Dictionary)val).Add((object)"Fairoots.WindForceMultiplier", (object)cfg.EffectiveWindForceMultiplier); ((Dictionary)val).Add((object)"Fairoots.WindGustDurationMultiplier", (object)cfg.EffectiveWindGustDurationMultiplier); ((Dictionary)val).Add((object)"Fairoots.WindItemForceMultiplier", (object)cfg.EffectiveWindItemForceMultiplier); ((Dictionary)val).Add((object)"Fairoots.WindObstacleOcclusionRangeMultiplier", (object)cfg.EffectiveWindObstacleOcclusionRangeMultiplier); ((Dictionary)val).Add((object)"Fairoots.WindBackpackAlwaysImmune", (object)cfg.EffectiveWindBackpackAlwaysImmune); ((Dictionary)val).Add((object)"Fairoots.DisableWindEntirely", (object)cfg.EffectiveDisableWindEntirely); ((Dictionary)val).Add((object)"Fairoots.WindFallCameraDampenClamp", (object)cfg.EffectiveWindFallCameraDampenClamp); ((Dictionary)val).Add((object)"Fairoots.WindRecentForceWindowSeconds", (object)cfg.EffectiveWindRecentForceWindowSeconds); ((Dictionary)val).Add((object)"Fairoots.PreventWindRagdoll", (object)cfg.EffectivePreventWindRagdoll); ((Dictionary)val).Add((object)"Fairoots.DisableSporeAreas", (object)cfg.EffectiveDisableSporeAreas); ((Dictionary)val).Add((object)"Fairoots.DisableZombies", (object)cfg.EffectiveDisableZombies); ((Dictionary)val).Add((object)"Fairoots.ZombieSpeedMultiplier", (object)cfg.EffectiveZombieSpeedMultiplier); ((Dictionary)val).Add((object)"Fairoots.BeetleSpeedMultiplier", (object)cfg.EffectiveBeetleSpeedMultiplier); ((Dictionary)val).Add((object)"Fairoots.BeetleKnockbackMultiplier", (object)cfg.EffectiveBeetleKnockbackMultiplier); ((Dictionary)val).Add((object)"Fairoots.CreatureRagdollMultiplier", (object)cfg.EffectiveCreatureRagdollMultiplier); ((Dictionary)val).Add((object)"Fairoots.ZombieDeaggroEnabled", (object)cfg.EffectiveZombieDeaggroEnabled); ((Dictionary)val).Add((object)"Fairoots.ZombieDeaggroMultiplier", (object)cfg.EffectiveZombieDeaggroMultiplier); ((Dictionary)val).Add((object)"Fairoots.BeetleDeaggroMultiplier", (object)cfg.EffectiveBeetleDeaggroMultiplier); ((Dictionary)val).Add((object)"Fairoots.ZombieKnockoutSeconds", (object)cfg.EffectiveZombieKnockoutSeconds); ((Dictionary)val).Add((object)"Fairoots.BeetleKnockoutSeconds", (object)cfg.EffectiveBeetleKnockoutSeconds); ((Dictionary)val).Add((object)"Fairoots.CreatureKnockoutMinThrowSpeed", (object)cfg.EffectiveCreatureKnockoutMinThrowSpeed); ((Dictionary)val).Add((object)"Fairoots.CreatureKnockoutMaxThrowDistance", (object)cfg.EffectiveCreatureKnockoutMaxThrowDistance); ((Dictionary)val).Add((object)"Fairoots.BlowgunAffectsCreatures", (object)cfg.EffectiveBlowgunAffectsCreatures); ((Dictionary)val).Add((object)"Fairoots.BlowgunCreatureStunSeconds", (object)cfg.EffectiveBlowgunCreatureStunSeconds); ((Dictionary)val).Add((object)"Fairoots.ZombieWindMultiplier", (object)cfg.EffectiveZombieWindMultiplier); ((Dictionary)val).Add((object)"Fairoots.BeetleWindSusceptibility", (object)cfg.EffectiveBeetleWindSusceptibility); ((Dictionary)val).Add((object)"Fairoots.DisableBeetles", (object)cfg.EffectiveDisableBeetles); ((Dictionary)val).Add((object)"Fairoots.DisableSpiders", (object)cfg.EffectiveDisableSpiders); ((Dictionary)val).Add((object)"Fairoots.SporeAreaRemovalFraction", (object)cfg.EffectiveSporeAreaRemovalFraction); ((Dictionary)val).Add((object)"Fairoots.SporeAreaRadiusMultiplier", (object)cfg.EffectiveSporeAreaRadiusMultiplier); ((Dictionary)val).Add((object)"Fairoots.SporeAreaStatusRateMultiplier", (object)cfg.EffectiveSporeAreaStatusRateMultiplier); ((Dictionary)val).Add((object)"Fairoots.SporeClearTimeMultiplier", (object)cfg.EffectiveSporeClearTimeMultiplier); ((Dictionary)val).Add((object)"Fairoots.SporeBuildUpMultiplier", (object)cfg.EffectiveSporeBuildUpMultiplier); ((Dictionary)val).Add((object)"Fairoots.CoverMouthStaminaPerSecond", (object)cfg.EffectiveCoverMouthStaminaPerSecond); ((Dictionary)val).Add((object)"Fairoots.EnableCoverMouth", (object)cfg.EffectiveEnableCoverMouth); ((Dictionary)val).Add((object)"Fairoots.CoverMouthBlocksSporeBombs", (object)cfg.EffectiveCoverMouthBlocksSporeBombs); ((Dictionary)val).Add((object)"Fairoots.ClimbSheltersFromWind", (object)cfg.EffectiveClimbSheltersFromWind); ((Dictionary)val).Add((object)"Fairoots.ClimbWindSpeedMultiplier", (object)cfg.EffectiveClimbWindSpeedMultiplier); ((Dictionary)val).Add((object)"Fairoots.ClimbWindUpwardSpeedMultiplier", (object)cfg.EffectiveClimbWindUpwardSpeedMultiplier); ((Dictionary)val).Add((object)"Fairoots.ClimbWindIntoWindSpeedMultiplier", (object)cfg.EffectiveClimbWindIntoWindSpeedMultiplier); ((Dictionary)val).Add((object)"Fairoots.ClimbWindGraceForceMultiplier", (object)cfg.EffectiveClimbWindGraceForceMultiplier); ((Dictionary)val).Add((object)"Fairoots.ClimbShelterGraceSeconds", (object)cfg.EffectiveClimbShelterGraceSeconds); Hashtable val2 = val; currentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); RefreshCache(); Diag.V($"[HostAuthority] PublishAll wrote {((Dictionary)(object)val2).Count} propert(y/ies) to room " + $"\"{currentRoom.Name}\": windForce={cfg.EffectiveWindForceMultiplier:0.###}, " + $"shakeCap={cfg.EffectiveSporeBombScreenshakeRangeCapMeters:0.#}"); } } internal class HostAuthoritySync : MonoBehaviourPunCallbacks { public override void OnJoinedRoom() { HostAuthority.RefreshCache(); HostAuthority.PublishAll(); CoverMouthController.RepublishOnJoin(); } public override void OnMasterClientSwitched(Player newMasterClient) { HostAuthority.RefreshCache(); HostAuthority.PublishAll(); } public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { HostAuthority.RefreshCache(); if (!HostAuthority.IsHost) { Diag.V("[HostAuthoritySync] room properties updated - reapplying cached wind/trigger-radius/spore-area/creature/spore-decay state."); WindChillZoneTuningPatch.ReapplyAll(); SporeBombCullPatch.ReapplyTriggerRadiusToAll(); SporeAreaDisablePatch.ReapplyToAll(); SporeAreaTuningPatch.ReapplyToAll(); CreatureDisablePatch.ReapplyToAll(); CreatureSpeedPatch.ReapplyToAll(); CreatureKnockbackPatch.ReapplyToAll(); CreatureRagdollPatch.ReapplyToAll(); SporeDecayPatch.ReapplyToAll(); } } } internal static class LocalizationHelper { public static string Resolve(string[] arr) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected I4, but got Unknown int num = (int)LocalizedText.CURRENT_LANGUAGE; if (num >= 0 && num < arr.Length && !string.IsNullOrEmpty(arr[num])) { return arr[num]; } return arr[0]; } } internal class ModPresenceCheck : MonoBehaviourPunCallbacks { private const string InstalledPropertyKey = "Fairoots.Installed"; public override void OnJoinedRoom() { MarkSelfInstalled(); LogIfMissing(); } public override void OnPlayerEnteredRoom(Player newPlayer) { LogIfMissing(); } public override void OnPlayerLeftRoom(Player otherPlayer) { LogIfMissing(); } private static void MarkSelfInstalled() { //IL_000e: 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_0024: Expected O, but got Unknown //IL_002b: Expected O, but got Unknown try { if (PhotonNetwork.InRoom) { Player localPlayer = PhotonNetwork.LocalPlayer; Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"Fairoots.Installed", (object)true); localPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); } } catch (Exception ex) { Diag.Error("[ModPresenceCheck] MarkSelfInstalled threw: " + ex.GetType().Name + ": " + ex.Message); } } private static void LogIfMissing() { try { List missingPlayers = GetMissingPlayers(); if (missingPlayers.Count != 0) { Diag.Warn($"[ModPresenceCheck] {missingPlayers.Count} player(s) in this lobby do not have Fairoots " + "installed: " + string.Join(", ", missingPlayers.Select((Player p) => p.NickName)) + ". They'll be warned (with a chance to cancel) the next time anyone clicks Start on the Boarding Pass - see BoardingPassStartGatePatch."); } } catch (Exception ex) { Diag.Error("[ModPresenceCheck] LogIfMissing threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static List GetMissingPlayers() { if (!PhotonNetwork.InRoom) { return new List(); } return (from p in PhotonNetwork.PlayerList where !IsInstalled(p) orderby p.ActorNumber select p).ToList(); } private static bool IsInstalled(Player player) { bool flag = default(bool); int num; if (((Dictionary)(object)player.CustomProperties).TryGetValue((object)"Fairoots.Installed", out object value)) { if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } } internal static class ModPresenceDialog { private static FieldInfo _promptField; private static bool _isOpen; internal static bool IsOpen => _isOpen; internal static void ShowStartConfirm(Action onConfirm, Action onDecline) { try { if (!_isOpen) { _isOpen = true; ConfirmPage.Open("LEAVE_GAME_CONFIRM", (Action)delegate { _isOpen = false; onConfirm?.Invoke(); }, (Action)delegate { _isOpen = false; onDecline?.Invoke(); }); OverridePromptText(ModPresenceLocalization.Get(ModPresenceMsgKey.DialogBody)); } } catch (Exception ex) { Diag.Error("[ModPresenceDialog] ShowStartConfirm threw: " + ex.GetType().Name + ": " + ex.Message); _isOpen = false; onConfirm?.Invoke(); } } private static void OverridePromptText(string text) { ConfirmPage instance = RetrievableResourceSingleton.Instance; if (!((Object)(object)instance == (Object)null)) { if ((object)_promptField == null) { _promptField = AccessTools.Field(typeof(ConfirmPage), "prompt"); } object? obj = _promptField?.GetValue(instance); LocalizedText val = (LocalizedText)((obj is LocalizedText) ? obj : null); if (val != null) { val.SetText(text); } } } } internal enum ModPresenceMsgKey { DialogBody } internal static class ModPresenceLocalization { private static readonly Dictionary Table = new Dictionary { [ModPresenceMsgKey.DialogBody] = new string[15] { "Not everyone in this lobby has Fairoots installed. This will create issues in the Roots biome for those clients (check LogOutput.log for more details). Start anyway?", "Tous les membres de ce lobby n'ont pas Fairoots installé. Cela causera des problèmes dans le biome Roots pour ces clients (consultez LogOutput.log pour plus de détails). Démarrer quand même ?", "Non tutti i membri di questa lobby hanno Fairoots installato. Questo causerà problemi nel bioma Roots per quei client (controlla LogOutput.log per maggiori dettagli). Avviare comunque?", "Nicht alle Mitglieder dieser Lobby haben Fairoots installiert. Dies wird bei diesen Clients zu Problemen im Roots-Biom führen (siehe LogOutput.log für weitere Details). Trotzdem starten?", "No todos los miembros de este lobby tienen Fairoots instalado. Esto causará problemas en el bioma Roots para esos clientes (consulta LogOutput.log para más detalles). ¿Iniciar de todos modos?", "No todos los miembros de este lobby tienen Fairoots instalado. Esto causará problemas en el bioma Roots para esos clientes (consulta LogOutput.log para más detalles). ¿Iniciar de todos modos?", "Nem todos os membros deste lobby têm o Fairoots instalado. Isso causará problemas no bioma Roots para esses clientes (verifique o LogOutput.log para mais detalhes). Iniciar mesmo assim?", "Не у всех участников этого лобби установлен Fairoots. Это вызовет проблемы в биоме Roots у этих игроков (подробности смотрите в LogOutput.log). Всё равно начать?", "Не всі учасники цього лобі мають встановлений Fairoots. Це спричинить проблеми в біомі Roots для цих клієнтів (подробиці дивіться в LogOutput.log). Все одно почати?", "并非大厅中的所有玩家都安装了 Fairoots。这将导致这些客户端在 Roots 生态群系中出现问题(详情请查看 LogOutput.log)。仍要开始吗?", "", "このロビーの全員が Fairoots をインストールしているわけではありません。これにより、該当クライアントの Roots バイオームで問題が発生します(詳細は LogOutput.log を確認してください)。それでも開始しますか?", "이 로비의 모든 플레이어가 Fairoots를 설치한 것은 아닙니다. 이는 해당 클라이언트의 Roots 바이옴에서 문제를 일으킵니다 (자세한 내용은 LogOutput.log를 확인하세요). 그래도 시작하시겠습니까?", "Nie wszyscy w tym lobby mają zainstalowany Fairoots. Spowoduje to problemy w biomie Roots u tych klientów (sprawdź LogOutput.log, aby uzyskać więcej informacji). Rozpocząć mimo to?", "Bu lobideki herkeste Fairoots yüklü değil. Bu, o istemcilerde Roots biyomunda sorunlara yol açacaktır (daha fazla ayrıntı için LogOutput.log dosyasına bakın). Yine de başlansın mı?" } }; public static string Get(ModPresenceMsgKey key) { return LocalizationHelper.Resolve(Table[key]); } } } namespace Fairoots.Diagnostics { internal sealed class DebugOverlayHost : MonoBehaviour { private static DebugOverlayHost _instance; private static GameObject _host; internal static void Bind(GameObject host) { _host = host; Sync(); } internal static void Sync() { if (!((Object)(object)_host == (Object)null) && Plugin.Cfg != null) { bool flag = Plugin.Cfg.EnableDebugLogging.Value && Plugin.Cfg.ShowRemovedSporeBombMarkers.Value; if (flag && (Object)(object)_instance == (Object)null) { _instance = _host.AddComponent(); Diag.Info("[Debug] removed-spore-bomb marker overlay is ON - its OnGUI pass is now running."); } else if (!flag && (Object)(object)_instance != (Object)null) { Object.Destroy((Object)(object)_instance); _instance = null; Diag.Info("[Debug] removed-spore-bomb marker overlay is off - its OnGUI pass is gone."); } } } private void OnGUI() { RemovedMarkerOverlay.Draw(); } } internal static class Diag { internal static ManualLogSource Source; internal static bool Enabled { get { if (Plugin.Cfg != null) { return Plugin.Cfg.EnableDebugLogging.Value; } return false; } } internal static void Info(string msg) { ManualLogSource source = Source; if (source != null) { source.LogInfo((object)msg); } } internal static void Warn(string msg) { ManualLogSource source = Source; if (source != null) { source.LogWarning((object)msg); } } internal static void Error(string msg) { ManualLogSource source = Source; if (source != null) { source.LogError((object)msg); } } internal static void V(string msg) { if (Enabled) { ManualLogSource source = Source; if (source != null) { source.LogInfo((object)msg); } } } } internal static class MaterialProbe { private const float ProbeRange = 15f; private const int MaxRenderers = 6; internal static void DumpLookedAt() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) Diag.Info("===== Fairoots material probe ====="); try { Character player = Character.localCharacter; Camera main = Camera.main; if ((Object)(object)player == (Object)null || (Object)(object)main == (Object)null) { Diag.Info("[MaterialProbe] MISSING: no local Character or main Camera yet"); return; } Ray ray = new Ray(((Component)main).transform.position, ((Component)main).transform.forward); List<(Renderer, bool, float)> list = (from x in (from r in Object.FindObjectsOfType(false) where r is MeshRenderer || r is SkinnedMeshRenderer where !IsOwnCharacter(((Component)r).transform, player) select r).Select(delegate(Renderer r) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) Bounds bounds = r.bounds; float num = default(float); return (Renderer: r, Hit: ((Bounds)(ref bounds)).IntersectRay(ray, ref num), Distance: Distance(r, ray)); }) where x.Hit && x.Distance <= 15f orderby x.Distance select x).Take(6).ToList(); Diag.Info($"[MaterialProbe] {list.Count} renderer(s) under the crosshair within " + $"{GameUnits.ToMeters(15f):0.0}m (nearest first, capped at {6}; " + "the local player's own meshes are excluded)"); foreach (var (renderer, _, distance) in list) { DumpRenderer(renderer, distance); } } catch (Exception ex) { Diag.Error("[MaterialProbe] threw: " + ex.GetType().Name + ": " + ex.Message); } Diag.Info("===== end material probe ====="); } private static float Distance(Renderer r, Ray ray) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Bounds bounds = r.bounds; float result = default(float); ((Bounds)(ref bounds)).IntersectRay(ray, ref result); return result; } private static bool IsOwnCharacter(Transform t, Character player) { Character componentInParent = ((Component)t).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return (Object)(object)componentInParent == (Object)(object)player; } return false; } private static void DumpRenderer(Renderer renderer, float distance) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown bool flag = IsUnderSporeBomb(((Component)renderer).transform); bool flag2 = (Object)(object)((Component)renderer).GetComponentInParent() != (Object)null; Diag.Info($"[MaterialProbe] {Path(((Component)renderer).transform)} @ {GameUnits.ToMeters(distance):0.00}m " + $"[{((object)renderer).GetType().Name}] sporeBomb={flag} item={flag2}"); Material[] sharedMaterials = renderer.sharedMaterials; MaterialPropertyBlock val = new MaterialPropertyBlock(); for (int i = 0; i < sharedMaterials.Length; i++) { Material val2 = sharedMaterials[i]; if ((Object)(object)val2 == (Object)null) { Diag.Info($"[MaterialProbe] slot {i}: (null material)"); continue; } Shader shader = val2.shader; Diag.Info($"[MaterialProbe] slot {i}: material \"{((Object)val2).name}\" shader \"{((shader != null) ? ((Object)shader).name : null)}\""); if ((Object)(object)shader == (Object)null) { continue; } renderer.GetPropertyBlock(val, i); foreach (string item in DescribeColorProperties(shader, val2, val)) { Diag.Info("[MaterialProbe] " + item); } } } private static IEnumerable DescribeColorProperties(Shader shader, Material material, MaterialPropertyBlock block) { int count = shader.GetPropertyCount(); bool any = false; for (int i = 0; i < count; i++) { if ((int)shader.GetPropertyType(i) == 0) { any = true; string propertyName = shader.GetPropertyName(i); Color color = material.GetColor(propertyName); Color color2 = block.GetColor(propertyName); bool flag = color2 != Color.clear && color2 != color; yield return $"{propertyName}: material=({color.r:0.###}, {color.g:0.###}, {color.b:0.###}, {color.a:0.###})" + (flag ? $" OVERRIDDEN by property block -> ({color2.r:0.###}, {color2.g:0.###}, {color2.b:0.###})" : string.Empty); } } if (!any) { yield return "(shader declares no Color properties at all)"; } } private static bool IsUnderSporeBomb(Transform t) { Transform val = t; while ((Object)(object)val != (Object)null) { if (SporeBombCullPatch.ClassifySporeBomb(((Object)val).name)) { return true; } val = val.parent; } return false; } private static string Path(Transform t) { string text = ((Object)t).name; Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; if (((Object)parent).name == "Roots Segment") { break; } parent = parent.parent; } return text; } } [HarmonyPatch(typeof(PointPinger), "ReceivePoint_Rpc")] internal static class PingRadiusProbePatch { private const float ProbeRadius = 3f; private static void Postfix(Vector3 point) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) if (!Diag.Enabled) { return; } try { Diag.Info($"===== Fairoots ping-radius probe (radius={3f:0}m @ {Fmt(point)}) ====="); var (num, arg, num2, num3) = SporeBombCullPatch.NearestFoliageVertex(point); Diag.Info("[PingProbe] nearest foliage vertex (against the exact set the last cull run used, whole-level, " + $"not just this 3m radius): {num:0.00}m, countWithinRadius={num2}, " + $"maxHeightAbove={num3:0.00}m, owner=[{arg}]"); Collider[] array = Physics.OverlapSphere(point, 3f); Diag.Info($"[PingProbe] {array.Length} collider(s) within {3f:0}m:"); Collider[] array2 = array; foreach (Collider val in array2) { Diag.Info("[PingProbe] collider \"" + ((Object)((Component)val).gameObject).name + "\" tag=" + ((Component)val).tag + " layer=" + LayerMask.LayerToName(((Component)val).gameObject.layer)); } int num4 = 0; int num5 = 0; Renderer[] array3 = Object.FindObjectsOfType(true); foreach (Renderer val2 in array3) { if (Vector3.Distance(((Component)val2).transform.position, point) > 3f) { continue; } if ((Object)(object)((Component)val2).GetComponentInParent() != (Object)null) { num5++; continue; } if (num4++ >= 40) { Diag.Info("[PingProbe] ...(more renderers, truncated)"); break; } List list = new List(); Transform parent = ((Component)val2).transform.parent; int num6 = 0; while (num6 < 5 && (Object)(object)parent != (Object)null) { list.Add(((Object)parent).name); num6++; parent = parent.parent; } string text = ""; MeshFilter component = ((Component)val2).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.sharedMesh != (Object)null) { Mesh sharedMesh = component.sharedMesh; if (sharedMesh.isReadable) { float num7 = float.MaxValue; Vector3[] vertices = sharedMesh.vertices; Matrix4x4 localToWorldMatrix = ((Component)val2).transform.localToWorldMatrix; for (int j = 0; j < vertices.Length; j++) { float num8 = Vector3.Distance(((Matrix4x4)(ref localToWorldMatrix)).MultiplyPoint3x4(vertices[j]), point); if (num8 < num7) { num7 = num8; } } text = $" mesh(readable, verts={vertices.Length}, nearestVertexDist={num7:0.00})"; } else { text = $" mesh(NOT readable, verts={sharedMesh.vertexCount})"; } } string[] obj = new string[14] { "[PingProbe] renderer \"", ((Object)((Component)val2).gameObject).name, "\" @ ", Fmt(((Component)val2).transform.position), " tag=", ((Component)val2).tag, " layer=", LayerMask.LayerToName(((Component)val2).gameObject.layer), " boundsSize=", null, null, null, null, null }; Bounds bounds = val2.bounds; obj[9] = Fmt(((Bounds)(ref bounds)).size); obj[10] = text; obj[11] = " parents=["; obj[12] = string.Join(" < ", list); obj[13] = "]"; Diag.Info(string.Concat(obj)); } Diag.Info($"[PingProbe] (skipped {num5} character-attached renderer(s))"); Diag.Info("===== end ping-radius probe ====="); } catch (Exception ex) { Diag.Error("[PingProbe] threw: " + ex.GetType().Name + ": " + ex.Message); } } private static string Fmt(Vector3 p) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return $"({p.x:0.0}, {p.y:0.0}, {p.z:0.0})"; } } internal static class RemovedMarkerOverlay { private const float MaxDrawDistance = 250f; internal static void Draw() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Cfg == null || !Plugin.Cfg.EnableDebugLogging.Value || !Plugin.Cfg.ShowRemovedSporeBombMarkers.Value) { return; } Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return; } List<(Vector3, CullOutcome)> removedPositions = SporeBombCullPatch.RemovedPositions; Rect val2 = default(Rect); for (int i = 0; i < removedPositions.Count; i++) { Vector3 item = removedPositions[i].Item1; float num = Vector3.Distance(((Component)main).transform.position, item); if (!(num > 250f)) { Vector3 val = main.WorldToScreenPoint(item); if (!(val.z <= 0f)) { bool num2 = removedPositions[i].Item2 == CullOutcome.RemovedFoliage; string arg = (num2 ? "FOLIAGE" : "SEEDED"); ? color = (num2 ? Color.red : new Color(1f, 0.6f, 0f)); float x = val.x; float num3 = (float)Screen.height - val.y; string text = $"REMOVED\n({arg})\n{num:0}m"; ((Rect)(ref val2))..ctor(x - 45f, num3 - 27f, 90f, 48f); GUI.color = Color.black; GUI.Box(new Rect(((Rect)(ref val2)).x + 1f, ((Rect)(ref val2)).y + 1f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), string.Empty); GUI.color = (Color)color; GUI.Box(val2, string.Empty); GUI.color = Color.white; GUI.Label(val2, text); } } } GUI.color = Color.white; } } internal static class SceneDiagnostics { private static float _lastDump = -999f; internal static void AutoDump(string reason) { if (!(Time.time - _lastDump < 2f)) { _lastDump = Time.time; DumpReport(reason); } } internal static void ProbeFoliageNearestSporeBomb() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Unknown result type (might be due to invalid IL or missing references) //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_03b5: Unknown result type (might be due to invalid IL or missing references) Diag.Info("===== Fairoots foliage probe ====="); try { Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null) { Diag.Info("[FoliageProbe] MISSING: no local Character yet"); return; } Vector3 center = localCharacter.Center; Transform val = null; float num = float.MaxValue; Transform[] array = Object.FindObjectsOfType(true); foreach (Transform val2 in array) { if (ClassifySporeBomb(((Object)val2).name) != null) { float num2 = Vector3.Distance(val2.position, center); if (num2 < num) { num = num2; val = val2; } } } if ((Object)(object)val == (Object)null) { Diag.Info("[FoliageProbe] MISSING: no spore-bomb candidates found in scene"); return; } Vector3 position = val.position; Diag.Info($"[FoliageProbe] nearest candidate \"{((Object)val).name}\" @ {Fmt(position)}, {num:0.0}m from player"); GrassChunk[] array2 = Object.FindObjectsOfType(true); int num3 = 0; GrassChunk[] array3 = array2; foreach (GrassChunk val3 in array3) { if (val3.GrassPoints != null) { num3 += val3.GrassPoints.Count; } } Diag.Info($"[FoliageProbe] {array2.Length} GrassChunk(s) in scene, {num3} blade(s) total"); float[] array4 = new float[4] { 1f, 2f, 3f, 5f }; foreach (float num4 in array4) { int num5 = 0; array3 = array2; foreach (GrassChunk val4 in array3) { if (val4.GrassPoints == null) { continue; } foreach (GrassPoint grassPoint in val4.GrassPoints) { if (Vector3.Distance(float3.op_Implicit(grassPoint.worldPos), position) <= num4) { num5++; } } } Diag.Info($"[FoliageProbe] blades within {num4:0}m: {num5}"); } Collider[] array5 = Physics.OverlapSphere(position, 3f); Diag.Info($"[FoliageProbe] {array5.Length} collider(s) within 3m:"); Collider[] array6 = array5; foreach (Collider val5 in array6) { Diag.Info("[FoliageProbe] collider \"" + ((Object)((Component)val5).gameObject).name + "\" tag=" + ((Component)val5).tag + " layer=" + LayerMask.LayerToName(((Component)val5).gameObject.layer)); } int num6 = 0; int num7 = 0; Renderer[] array7 = Object.FindObjectsOfType(true); foreach (Renderer val6 in array7) { if (Vector3.Distance(((Component)val6).transform.position, position) > 3f) { continue; } if ((Object)(object)((Component)val6).GetComponentInParent() != (Object)null) { num7++; continue; } if (num6++ >= 40) { Diag.Info("[FoliageProbe] ...(more renderers within 3m, truncated)"); break; } List list = new List(); Transform parent = ((Component)val6).transform.parent; int num8 = 0; while (num8 < 5 && (Object)(object)parent != (Object)null) { list.Add(((Object)parent).name); num8++; parent = parent.parent; } string[] obj = new string[13] { "[FoliageProbe] renderer \"", ((Object)((Component)val6).gameObject).name, "\" @ ", Fmt(((Component)val6).transform.position), " tag=", ((Component)val6).tag, " layer=", LayerMask.LayerToName(((Component)val6).gameObject.layer), " boundsSize=", null, null, null, null }; Bounds bounds = val6.bounds; obj[9] = Fmt(((Bounds)(ref bounds)).size); obj[10] = " parents=["; obj[11] = string.Join(" < ", list); obj[12] = "]"; Diag.Info(string.Concat(obj)); } Diag.Info($"[FoliageProbe] (skipped {num7} character-attached renderer(s) within 3m)"); } catch (Exception ex) { Diag.Error("[FoliageProbe] threw: " + ex.GetType().Name + ": " + ex.Message); } finally { Diag.Info("===== end foliage probe ====="); } } internal static void DumpReport(string reason) { _lastDump = Time.time; Diag.Info("===== Fairoots scene diagnostics (" + reason + ") ====="); ReportBiome(); ReportWind(); ReportSporeBombs(); ReportSporeAreas(); ReportCreatures(); ReportFoliageGroups(); Diag.Info("===== end diagnostics ====="); } private static void ReportFoliageGroups() { Section("FoliageGroups", delegate { List list = (from t in Object.FindObjectsOfType(true) where ((Object)t).name == "PlateauProps" && IsUnderRootsSegment(t) select t).ToList(); Diag.Info($"[FoliageGroups] found {list.Count} \"PlateauProps\" container(s) under Roots Segment"); Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); foreach (Transform item in list) { for (int num = 0; num < item.childCount; num++) { Transform child = item.GetChild(num); dictionary[((Object)child).name] = ((!dictionary.TryGetValue(((Object)child).name, out var value)) ? 1 : (value + 1)); dictionary2[((Object)child).name] = (dictionary2.TryGetValue(((Object)child).name, out var value2) ? (value2 + child.childCount) : child.childCount); } } foreach (KeyValuePair item2 in dictionary.OrderByDescending((KeyValuePair kv) => kv.Value)) { Diag.Info($"[FoliageGroups] group \"{item2.Key}\" x{item2.Value} instance(s), {dictionary2[item2.Key]} total children"); } }); } private static bool IsUnderRootsSegment(Transform t) { Transform val = t; while ((Object)(object)val != (Object)null) { if (((Object)val).name == "Roots Segment") { return true; } val = val.parent; } return false; } private static void ReportBiome() { Section("Biome", delegate { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 MapHandler instance = Singleton.Instance; if ((Object)(object)instance == (Object)null) { Diag.Info("[Biome] MISSING: MapHandler singleton not available yet"); } else { BiomeType currentBiome = instance.GetCurrentBiome(); Diag.Info($"[Biome] OK current={currentBiome}, RootsPresent={instance.BiomeIsPresent((BiomeType)7)}, segment={instance.GetCurrentSegment()}"); if ((int)currentBiome != 7) { Diag.Info("[Biome] NOTE: not currently in Roots - Roots hazard tuning would be inactive here."); } } }); } private static void ReportWind() { Section("Wind", delegate { RootsWind instance = RootsWind.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.windZone == (Object)null) { Diag.Info("[Wind] MISSING: RootsWind.instance / windZone not present (expected outside Roots)"); } else { WindChillZone windZone = instance.windZone; Diag.Info($"[Wind] OK WindChillZone: windForce={windZone.windForce}, windMovesItems={windZone.windMovesItems}, " + $"windItemFactor={windZone.windItemFactor}, useRaycast={windZone.useRaycast} " + $"(min/maxRaycastDistance={windZone.minRaycastDistance}/{windZone.maxRaycastDistance}), windActive={windZone.windActive}"); Diag.Info("[Wind] -> useRaycast is the obstacle-occlusion lever (RESEARCH.md Q5/open-Q5); note whether it's True on the real Roots instance."); } }); } private static void ReportSporeBombs() { Section("SporeBombs", delegate { List<(Transform, string)> list = new List<(Transform, string)>(); Transform[] array = Object.FindObjectsOfType(true); foreach (Transform val in array) { string text = ClassifySporeBomb(((Object)val).name); if (text != null) { list.Add((val, text)); } } Diag.Info($"[SporeBombs] found {list.Count} candidate(s) by name substring"); if (list.Count == 0) { Diag.Info("[SporeBombs] MISSING: none found - confirm the Roots-biome prefab names, or scan while standing in Roots."); } else { foreach (IGrouping item in from m in list group m by ((Object)m.t).name into g orderby g.Count() descending select g) { Diag.Info($"[SporeBombs] name \"{item.Key}\" x{item.Count()}"); } foreach (IGrouping item2 in from m in list group m by ((Object)m.t).name) { int num = 0; foreach (var (t, kind) in item2) { if (num++ >= 2) { break; } ProbeSporeBombStructure(t, kind); } } Diag.Info("[SporeBombs] scene-wide component counts: " + $"AOE={Object.FindObjectsOfType(true).Length}, " + $"ExplosionEffect={Object.FindObjectsOfType(true).Length}, " + $"AddScreenshake={Object.FindObjectsOfType(true).Length}"); } }); } private static void LogTriggerEventListeners(GameObject go) { TriggerEvent component = go.GetComponent(); if ((Object)(object)component == (Object)null || component.triggerEvent == null) { return; } int persistentEventCount = ((UnityEventBase)component.triggerEvent).GetPersistentEventCount(); if (persistentEventCount == 0) { Diag.Info("[SporeBombs] TriggerEvent listeners: none persistent (runtime-added only)"); return; } for (int i = 0; i < persistentEventCount; i++) { Object persistentTarget = ((UnityEventBase)component.triggerEvent).GetPersistentTarget(i); Diag.Info($"[SporeBombs] TriggerEvent listener[{i}]: " + ((persistentTarget != (Object)null) ? (((object)persistentTarget).GetType().Name + " on \"" + persistentTarget.name + "\"") : "") + "." + ((UnityEventBase)component.triggerEvent).GetPersistentMethodName(i) + "()"); } } private static void ProbeSporeBombStructure(Transform t, string kind) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) GameObject gameObject = ((Component)t).gameObject; Diag.Info("[SporeBombs] probe " + kind + " @ " + Fmt(t.position) + " name=\"" + ((Object)t).name + "\""); Diag.Info("[SporeBombs] self components: " + ComponentNames(gameObject)); LogTriggerEventListeners(gameObject); int num = 0; Transform parent = t.parent; while ((Object)(object)parent != (Object)null && num < 4) { Diag.Info($"[SporeBombs] parent[{num}] \"{((Object)parent).name}\": {ComponentNames(((Component)parent).gameObject)}"); parent = parent.parent; num++; } AOE componentInParent = gameObject.GetComponentInParent(); AOE componentInChildren = ((Component)t.root).GetComponentInChildren(true); StatusEmitter componentInChildren2 = ((Component)t.root).GetComponentInChildren(true); Diag.Info("[SporeBombs] resolve: AOE-in-parent=" + (Object.op_Implicit((Object)(object)componentInParent) ? ((Object)((Component)componentInParent).gameObject).name : "none") + ", AOE-in-root=" + (Object.op_Implicit((Object)(object)componentInChildren) ? ((Object)((Component)componentInChildren).gameObject).name : "none") + ", StatusEmitter-in-root=" + (Object.op_Implicit((Object)(object)componentInChildren2) ? ((Object)((Component)componentInChildren2).gameObject).name : "none")); if ((Object)(object)componentInParent != (Object)null) { Diag.Info($"[SporeBombs] AOE values: range={componentInParent.range}, knockback={componentInParent.knockback}, " + $"minFactor={componentInParent.minFactor}, factorPow={componentInParent.factorPow}, " + $"statusType={componentInParent.statusType}, statusAmount={componentInParent.statusAmount}"); } } private static string ComponentNames(GameObject go) { Component[] components = go.GetComponents(); if (components.Length != 0) { return string.Join(", ", components.Select((Component c) => (!((Object)(object)c == (Object)null)) ? ((object)c).GetType().Name : "")); } return "(none)"; } private static void ReportSporeAreas() { Section("SporeAreas", delegate { //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) StatusEmitter[] array = Object.FindObjectsOfType(true); Diag.Info($"[SporeAreas] found {array.Length} StatusEmitter(s) total (not all are spores - grouped below)"); if (array.Length == 0) { return; } int num = array.Count((StatusEmitter e) => e is WindAffectedStatusEmitter); Diag.Info($"[SporeAreas] {num} of {array.Length} are WindAffectedStatusEmitter (wind already suppresses those)"); foreach (IGrouping<(STATUSTYPE, bool), StatusEmitter> item in from e in array group e by (type: e.statusType, wind: e is WindAffectedStatusEmitter) into g orderby g.Count() descending select g) { StatusEmitter val = item.First(); Diag.Info(string.Format("[SporeAreas] {0}{1} x{2} ", item.Key.Item1, item.Key.Item2 ? " (WindAffected)" : "", item.Count()) + $"e.g. amount={val.amount}, radius={val.radius}, outerFade={val.outerFade}, " + $"innerFade={val.innerFade}, minAmount={val.minAmount}"); } List list = array.Where((StatusEmitter e) => (int)e.statusType == 10).ToList(); Diag.Info($"[SporeAreas] Spores-type emitters: {list.Count} " + $"({list.Count((StatusEmitter e) => e is WindAffectedStatusEmitter)} WindAffected)"); int num2 = 0; foreach (StatusEmitter item2 in list) { if (num2++ >= 10) { break; } Diag.Info("[SporeAreas] " + ((item2 is WindAffectedStatusEmitter) ? "WindAffected " : "") + "Spores " + $"amount={item2.amount}, radius={item2.radius} @ {Fmt(((Component)item2).transform.position)}"); } }); } private static void ReportCreatures() { Section("Creatures", delegate { ZombieManager val = Object.FindObjectOfType(true); Diag.Info(((Object)(object)val == (Object)null) ? "[Creatures] ZombieManager: MISSING" : $"[Creatures] ZombieManager OK: maxActiveZombies={val.maxActiveZombies}"); int num = Object.FindObjectsOfType(true).Length; int num2 = Object.FindObjectsOfType(true).Length; int num3 = Object.FindObjectsOfType(true).Length; Diag.Info($"[Creatures] live counts: MushroomZombie={num}, Beetle={num2}, Spider={num3}"); }); } private static string ClassifySporeBomb(string name) { if (string.IsNullOrEmpty(name)) { return null; } if (name.IndexOf("SporeMushroomExplo", StringComparison.OrdinalIgnoreCase) >= 0) { return "Explosive Spore Bomb (SporeMushroomExplo)"; } if (name.IndexOf("SporeMushroom", StringComparison.OrdinalIgnoreCase) >= 0) { return "Poison Spore Bomb (SporeMushroom)"; } if (name.IndexOf("SporeFungus", StringComparison.OrdinalIgnoreCase) >= 0) { return "Spore Bomb (SporeFungus)"; } return null; } private static string Fmt(Vector3 p) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) return $"({p.x:0.0}, {p.y:0.0}, {p.z:0.0}) -> grid {GridPos.Round(p.x, p.y, p.z)}"; } private static void Section(string name, Action body) { try { body(); } catch (Exception ex) { Diag.Error("[" + name + "] diagnostics section threw: " + ex.GetType().Name + ": " + ex.Message); } } } [HarmonyPatch] internal static class ScreenshakeSourcePatch { private const int StackFramesToLog = 8; [HarmonyPatch(typeof(PerlinShake), "AddShake", new Type[] { typeof(float), typeof(float), typeof(float) })] [HarmonyPrefix] private static void LogShake(float amount, float duration, float scale) { try { if (Diag.Enabled && Plugin.Cfg != null && Plugin.Cfg.LogScreenshakeSources.Value) { Diag.Info($"[Screenshake] amount={amount:0.###} duration={duration:0.##} scale={scale:0.#}" + ((amount <= 0.0001f) ? " (silent - fully attenuated)" : string.Empty) + "\n" + Caller()); } } catch (Exception ex) { Diag.Error("[Screenshake] tracer threw: " + ex.GetType().Name + ": " + ex.Message); } } private static string Caller() { StackTrace stackTrace = new StackTrace(2, fNeedFileInfo: false); StringBuilder stringBuilder = new StringBuilder(); int num = 0; for (int i = 0; i < stackTrace.FrameCount; i++) { if (num >= 8) { break; } MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod(); if (!(methodBase == null)) { string text = methodBase.DeclaringType?.FullName ?? ""; if (!text.StartsWith("Fairoots.", StringComparison.Ordinal) && !text.StartsWith("HarmonyLib.", StringComparison.Ordinal) && !text.StartsWith("MonoMod.", StringComparison.Ordinal)) { stringBuilder.Append(" <- ").Append(text).Append('.') .Append(methodBase.Name) .Append('\n'); num++; } } } if (stringBuilder.Length != 0) { return stringBuilder.ToString().TrimEnd('\n'); } return " <- "; } } [HarmonyPatch(typeof(GamefeelHandler), "AddPerlinShakeProximity")] internal static class ProximityShakeSourcePatch { private static void Prefix(Vector3 position, float amount, float maxProximity) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) try { if (Diag.Enabled && Plugin.Cfg != null && Plugin.Cfg.LogScreenshakeSources.Value) { Character observedCharacter = Character.observedCharacter; string arg = (((Object)(object)observedCharacter == (Object)null) ? "observedCharacter=null (NO attenuation applied - full-strength shake)" : $"distance={GameUnits.ToMeters(Vector3.Distance(observedCharacter.Center, position)):0.#}m"); Diag.Info($"[Screenshake] proximity shake @ {position} amount={amount:0.##} " + $"maxProximity={GameUnits.ToMeters(maxProximity):0.#}m, {arg}"); } } catch (Exception ex) { Diag.Error("[Screenshake] proximity tracer threw: " + ex.GetType().Name + ": " + ex.Message); } } } [HarmonyPatch] internal static class SporeStatusSourcePatch { private const int StackFramesToLog = 10; [HarmonyPatch(typeof(CharacterAfflictions), "AddStatus")] [HarmonyPrefix] private static void LogSporeStatus(CharacterAfflictions __instance, STATUSTYPE statusType, float amount) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 try { if (Diag.Enabled && (int)statusType == 10 && !(amount <= 0f) && CoverMouthController.LocalCovering) { Character componentInParent = ((Component)__instance).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null) && !((Object)(object)componentInParent != (Object)(object)Character.localCharacter)) { Diag.Info($"[SporeSource] +{amount:0.####} Spores applied WHILE THE MOUTH IS COVERED\n{Caller()}"); } } } catch (Exception ex) { Diag.Error("[SporeSource] tracer threw: " + ex.GetType().Name + ": " + ex.Message); } } private static string Caller() { StackTrace stackTrace = new StackTrace(2, fNeedFileInfo: false); StringBuilder stringBuilder = new StringBuilder(); int num = 0; for (int i = 0; i < stackTrace.FrameCount; i++) { if (num >= 10) { break; } MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod(); if (!(methodBase == null)) { string text = methodBase.DeclaringType?.FullName ?? ""; if (!text.StartsWith("Fairoots.", StringComparison.Ordinal) && !text.StartsWith("HarmonyLib.", StringComparison.Ordinal) && !text.StartsWith("MonoMod.", StringComparison.Ordinal)) { stringBuilder.Append(" <- ").Append(text).Append('.') .Append(methodBase.Name) .Append('\n'); num++; } } } if (stringBuilder.Length != 0) { return stringBuilder.ToString().TrimEnd('\n'); } return " <- "; } } internal static class TriggerRadiusOverlay { private const float MaxDrawDistance = 10f; private const int CircleSegments = 32; private const float LineThickness = 0.05f; private static Material _lineMaterial; private static bool _initialized; private static bool _loggedFailure; private static readonly List<(Vector3 Center, float Radius)> PendingCapDiscs = new List<(Vector3, float)>(); internal static void Sync() { if (Plugin.Cfg == null) { return; } bool flag = Plugin.Cfg.EnableDebugLogging.Value && Plugin.Cfg.ShowSporeBombTriggerRadius.Value; if (flag != _initialized) { _initialized = flag; if (flag) { RenderPipelineManager.endCameraRendering += OnEndCameraRendering; Diag.Info("[Debug] spore-bomb trigger-radius wireframe is ON - its render callback is now running."); } else { RenderPipelineManager.endCameraRendering -= OnEndCameraRendering; Diag.Info("[Debug] spore-bomb trigger-radius wireframe is off - its render callback is gone."); } } } private static void OnEndCameraRendering(ScriptableRenderContext context, Camera cam) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Cfg == null || !Plugin.Cfg.EnableDebugLogging.Value || !Plugin.Cfg.ShowSporeBombTriggerRadius.Value || (Object)(object)cam == (Object)null || (Object)(object)cam != (Object)(object)Camera.main) { return; } List keptTriggerColliders = SporeBombCullPatch.KeptTriggerColliders; if (keptTriggerColliders.Count == 0) { return; } try { EnsureMaterial(); if ((Object)(object)_lineMaterial == (Object)null) { return; } _lineMaterial.SetPass(0); PendingCapDiscs.Clear(); GL.PushMatrix(); GL.LoadProjectionMatrix(cam.projectionMatrix); GL.modelview = cam.worldToCameraMatrix; GL.Begin(7); GL.Color(Color.red); Vector3 position = ((Component)cam).transform.position; for (int i = 0; i < keptTriggerColliders.Count; i++) { Collider val = keptTriggerColliders[i]; if (!((Object)(object)val == (Object)null) && !(Vector3.Distance(position, ((Component)val).transform.position) > 10f)) { DrawColliderWireframe(val, position); } } GL.End(); if (PendingCapDiscs.Count > 0) { GL.Begin(4); GL.Color(new Color(1f, 0f, 0f, 0.55f)); for (int j = 0; j < PendingCapDiscs.Count; j++) { DrawFilledDisc(PendingCapDiscs[j].Center, PendingCapDiscs[j].Radius); } GL.End(); } GL.PopMatrix(); } catch (Exception ex) { if (!_loggedFailure) { _loggedFailure = true; Diag.Error("[TriggerRadiusOverlay] threw: " + ex.GetType().Name + ": " + ex.Message); } } } private static void DrawColliderWireframe(Collider col, Vector3 camPos) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) SphereCollider val = (SphereCollider)(object)((col is SphereCollider) ? col : null); if (val == null) { BoxCollider val2 = (BoxCollider)(object)((col is BoxCollider) ? col : null); if (val2 != null) { DrawWireBox(((Component)val2).transform, val2.center, val2.size, camPos); } } else { Vector3 center = ((Component)val).transform.TransformPoint(val.center); float radius = val.radius * MaxAbsAxis(((Component)val).transform.lossyScale); DrawWireSphere(center, radius, camPos, CutoffY(val)); } } private static float? CutoffY(SphereCollider sphere) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Cfg.KeepVanillaTriggerRadius.Value) { return null; } float num = SporeBombExplosionTuning.ResolveTriggerHeightCutoffMeters(Plugin.Cfg.EffectiveSporeBombTriggerHeightMultiplier); if (num <= 0f) { return null; } string name = ((Object)((Component)sphere).gameObject).name; if (!SporeBombCullPatch.ClassifySporeBomb(name) || SporeBombCullPatch.IsExplosiveVariant(name)) { return null; } return ((Component)sphere).transform.position.y + GameUnits.MetersToUnits(num); } private static float MaxAbsAxis(Vector3 v) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) return Mathf.Max(new float[3] { Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z) }); } private static void DrawWireSphere(Vector3 center, float radius, Vector3 camPos, float? cutoffY) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) DrawCircle(center, radius, Vector3.right, Vector3.up, camPos, cutoffY); DrawCircle(center, radius, Vector3.forward, Vector3.up, camPos, cutoffY); DrawCircle(center, radius, Vector3.right, Vector3.forward, camPos, cutoffY); if (cutoffY.HasValue) { float num = cutoffY.Value - center.y; if (!(Mathf.Abs(num) >= radius)) { float num2 = Mathf.Sqrt(radius * radius - num * num); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(center.x, cutoffY.Value, center.z); DrawCircle(val, num2, Vector3.right, Vector3.forward, camPos, null); PendingCapDiscs.Add((val, num2)); } } } private static void DrawCircle(Vector3 center, float radius, Vector3 axisA, Vector3 axisB, Vector3 camPos, float? cutoffAtCenter) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) Vector3 a = center + axisA * radius; for (int i = 1; i <= 32; i++) { float num = (float)i / 32f * MathF.PI * 2f; Vector3 val = center + (axisA * Mathf.Cos(num) + axisB * Mathf.Sin(num)) * radius; DrawClippedSegment(a, val, camPos, cutoffAtCenter); a = val; } } private static void DrawClippedSegment(Vector3 a, Vector3 b, Vector3 camPos, float? cutoffY) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (!cutoffY.HasValue) { DrawThickLine(a, b, camPos); return; } float value = cutoffY.Value; bool flag = a.y <= value; bool flag2 = b.y <= value; if (flag && flag2) { DrawThickLine(a, b, camPos); } else if (flag || flag2) { float num = (value - a.y) / (b.y - a.y); Vector3 val = Vector3.Lerp(a, b, num); DrawThickLine(flag ? a : val, flag ? val : b, camPos); } } private static void DrawFilledDisc(Vector3 center, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) Vector3 val = center + Vector3.right * radius; for (int i = 1; i <= 32; i++) { float num = (float)i / 32f * MathF.PI * 2f; Vector3 val2 = center + (Vector3.right * Mathf.Cos(num) + Vector3.forward * Mathf.Sin(num)) * radius; GL.Vertex(center); GL.Vertex(val); GL.Vertex(val2); val = val2; } } private static void DrawWireBox(Transform transform, Vector3 center, Vector3 size, Vector3 camPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) Vector3 val = size * 0.5f; Vector3[] array = (Vector3[])(object)new Vector3[8] { center + new Vector3(0f - val.x, 0f - val.y, 0f - val.z), center + new Vector3(val.x, 0f - val.y, 0f - val.z), center + new Vector3(val.x, 0f - val.y, val.z), center + new Vector3(0f - val.x, 0f - val.y, val.z), center + new Vector3(0f - val.x, val.y, 0f - val.z), center + new Vector3(val.x, val.y, 0f - val.z), center + new Vector3(val.x, val.y, val.z), center + new Vector3(0f - val.x, val.y, val.z) }; Vector3[] array2 = (Vector3[])(object)new Vector3[8]; for (int i = 0; i < 8; i++) { array2[i] = transform.TransformPoint(array[i]); } DrawEdge(array2, 0, 1, camPos); DrawEdge(array2, 1, 2, camPos); DrawEdge(array2, 2, 3, camPos); DrawEdge(array2, 3, 0, camPos); DrawEdge(array2, 4, 5, camPos); DrawEdge(array2, 5, 6, camPos); DrawEdge(array2, 6, 7, camPos); DrawEdge(array2, 7, 4, camPos); DrawEdge(array2, 0, 4, camPos); DrawEdge(array2, 1, 5, camPos); DrawEdge(array2, 2, 6, camPos); DrawEdge(array2, 3, 7, camPos); } private static void DrawEdge(Vector3[] pts, int a, int b, Vector3 camPos) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) DrawThickLine(pts[a], pts[b], camPos); } private static void DrawThickLine(Vector3 a, Vector3 b, Vector3 camPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) Vector3 val = b - a; if (!(((Vector3)(ref val)).sqrMagnitude < 1E-08f)) { Vector3 val2 = camPos - (a + b) * 0.5f; val2 = Vector3.Cross(((Vector3)(ref val2)).normalized, val); Vector3 val3 = ((Vector3)(ref val2)).normalized * 0.025f; if (((Vector3)(ref val3)).sqrMagnitude < 1E-08f) { val2 = Vector3.Cross(Vector3.up, val); val3 = ((Vector3)(ref val2)).normalized * 0.025f; } GL.Vertex(a - val3); GL.Vertex(a + val3); GL.Vertex(b + val3); GL.Vertex(b - val3); } } private static void EnsureMaterial() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_lineMaterial != (Object)null) { return; } Shader val = Shader.Find("Hidden/Internal-Colored") ?? Shader.Find("Universal Render Pipeline/Unlit"); if ((Object)(object)val == (Object)null) { Diag.Error("[TriggerRadiusOverlay] no usable line shader found (both Hidden/Internal-Colored and Universal Render Pipeline/Unlit are missing) - wireframe cannot be drawn"); return; } _lineMaterial = new Material(val) { hideFlags = (HideFlags)61 }; _lineMaterial.SetInt("_SrcBlend", 5); _lineMaterial.SetInt("_DstBlend", 10); _lineMaterial.SetInt("_Cull", 0); _lineMaterial.SetInt("_ZWrite", 0); if (_lineMaterial.HasProperty("_BaseColor")) { _lineMaterial.SetColor("_BaseColor", Color.red); } if (_lineMaterial.HasProperty("_Color")) { _lineMaterial.SetColor("_Color", Color.red); } } } } namespace Fairoots.Creatures { [HarmonyPatch(typeof(Mob), "Targeting")] internal static class BeetleDeaggroPatch { internal sealed class TargetingState { internal float? RestoreAggroDistance; internal Character TargetBefore; internal bool Skipped; } private static readonly Dictionary SuppressedUntil = new Dictionary(); private static readonly FieldRef TargetChar = ResolveTargetChar(); internal static void ClearLevelState() { SuppressedUntil.Clear(); } private static bool Prefix(Mob __instance, out TargetingState __state) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) __state = null; if (!RootsState.Active || Plugin.Cfg == null || !(__instance is Beetle)) { return true; } int instanceID = ((Object)__instance).GetInstanceID(); Character target = GetTarget(__instance); __state = new TargetingState { TargetBefore = target }; double effectiveBeetleDeaggroMultiplier = Plugin.Cfg.EffectiveBeetleDeaggroMultiplier; if (CreatureTuning.IsVanilla(effectiveBeetleDeaggroMultiplier)) { return true; } if ((Object)(object)target != (Object)null && (Object)(object)target.data != (Object)null && target.data.fullyConscious) { float num = Vector3.Distance(((Component)__instance).transform.position, target.Center); if (BeetleDeaggro.ShouldKeepTarget(num, __instance.aggroDistance, effectiveBeetleDeaggroMultiplier)) { SuppressedUntil.Remove(instanceID); __state.Skipped = true; if (Diag.Enabled && CreatureAggroLog.DueForStatus(instanceID)) { CreatureAggroLog.Status("beetle", ((Component)__instance).gameObject, target, $"distance={GameUnits.ToMeters(num):0.#}m/" + $"{GameUnits.ToMeters(BeetleDeaggro.ResolveRetentionDistance(__instance.aggroDistance, effectiveBeetleDeaggroMultiplier)):0.#}m " + "(held past vanilla's line-of-sight test)"); } return false; } SuppressedUntil[instanceID] = Time.time + 5f; float aggroDistance = __instance.aggroDistance; float units = BeetleDeaggro.ResolveRetentionDistance(aggroDistance, effectiveBeetleDeaggroMultiplier); __state.RestoreAggroDistance = aggroDistance; __instance.aggroDistance = 0f; CreatureAggroLog.NoteForcedDeaggro(instanceID, $"Fairoots deaggro rule - {GameUnits.ToMeters(num):0.#}m away " + $"(limit {GameUnits.ToMeters(units):0.#}m), now ignoring everyone for " + $"{5f:0.#}s"); return true; } if (SuppressedUntil.TryGetValue(instanceID, out var value)) { if (Time.time < value) { __state.RestoreAggroDistance = __instance.aggroDistance; __instance.aggroDistance = 0f; return true; } SuppressedUntil.Remove(instanceID); Diag.V("[Aggro] beetle \"" + ((Object)((Component)__instance).gameObject).name + "\" suppression expired - may notice players again"); } return true; } private static void Postfix(Mob __instance, TargetingState __state) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (__state == null) { return; } if (__state.RestoreAggroDistance.HasValue) { __instance.aggroDistance = __state.RestoreAggroDistance.Value; } if (!Diag.Enabled || __state.Skipped) { return; } Character target = GetTarget(__instance); if (target != __state.TargetBefore) { if ((Object)(object)target != (Object)null) { float distanceUnits = Vector3.Distance(((Component)__instance).transform.position, target.Center); CreatureAggroLog.Aggro("beetle", ((Component)__instance).gameObject, target, distanceUnits, DescribeLimits(__instance)); } else if ((Object)(object)__state.TargetBefore != (Object)null) { CreatureAggroLog.Deaggro("beetle", ((Component)__instance).gameObject, __state.TargetBefore, CreatureAggroLog.ConsumeReason(((Object)__instance).GetInstanceID()), DescribeLimits(__instance)); } } } private static string DescribeLimits(Mob beetle) { double effectiveBeetleDeaggroMultiplier = Plugin.Cfg.EffectiveBeetleDeaggroMultiplier; return $"multiplier={effectiveBeetleDeaggroMultiplier:0.##} => keeps chasing to " + $"{GameUnits.ToMeters(BeetleDeaggro.ResolveRetentionDistance(beetle.aggroDistance, effectiveBeetleDeaggroMultiplier)):0.#}m " + $"(vanilla {GameUnits.ToMeters(beetle.aggroDistance):0.#}m)"; } private static Character GetTarget(Mob mob) { if (TargetChar != null) { return TargetChar.Invoke(mob); } return null; } private static FieldRef ResolveTargetChar() { try { return AccessTools.FieldRefAccess("_targetChar"); } catch (Exception ex) { Diag.Error("[Creatures] could not bind Mob._targetChar (" + ex.GetType().Name + ") - beetle deaggro tuning disabled (vanilla behavior)."); return null; } } } [HarmonyPatch(typeof(Action_RaycastDart), "RPC_DartImpact")] internal static class BlowgunCreaturePatch { private static void Postfix(Action_RaycastDart __instance, Vector3 origin, Vector3 endpoint) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) try { if (!RootsState.Active || Plugin.Cfg == null || !Plugin.Cfg.EffectiveBlowgunAffectsCreatures) { return; } Vector3 val = endpoint - origin; float magnitude = ((Vector3)(ref val)).magnitude; if (!(magnitude <= 0.001f)) { object obj = FindFirstCreatureAlongDart(origin, val / magnitude, magnitude, __instance.dartCollisionSize); if (obj != null) { Apply(obj); } } } catch (Exception ex) { Diag.Error("[Creatures] blowgun dart handling threw: " + ex.GetType().Name + ": " + ex.Message); } } private static object FindFirstCreatureAlongDart(Vector3 origin, Vector3 direction, float distance, float radius) { //IL_0000: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) RaycastHit[] array = Physics.SphereCastAll(origin, Mathf.Max(0.01f, radius), direction, distance, -1, (QueryTriggerInteraction)2); if (array.Length == 0) { return null; } Array.Sort(array, (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance)); RaycastHit[] array2 = array; for (int num = 0; num < array2.Length; num++) { RaycastHit val = array2[num]; if (!((Object)(object)((RaycastHit)(ref val)).collider == (Object)null)) { MushroomZombie componentInParent = ((Component)((RaycastHit)(ref val)).collider).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return componentInParent; } Beetle componentInParent2 = ((Component)((RaycastHit)(ref val)).collider).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { return componentInParent2; } Spider componentInParent3 = ((Component)((RaycastHit)(ref val)).collider).GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null) { return componentInParent3; } SpiderTrigger componentInParent4 = ((Component)((RaycastHit)(ref val)).collider).GetComponentInParent(); if ((Object)(object)componentInParent4 != (Object)null && (Object)(object)componentInParent4.spider != (Object)null) { return componentInParent4.spider; } } } return null; } private static void Apply(object creature) { float num = CreatureKnockout.ResolveBlowgunStunSeconds(Plugin.Cfg.EffectiveBlowgunCreatureStunSeconds); MushroomZombie val = (MushroomZombie)((creature is MushroomZombie) ? creature : null); if (val != null) { KillZombie(val); return; } Beetle val2 = (Beetle)((creature is Beetle) ? creature : null); if (val2 != null) { PhotonView component = ((Component)val2).GetComponent(); if ((!((Object)(object)component != (Object)null) || component.IsMine) && num > 0f && CreatureKnockoutPatch.KnockOutBeetle(val2, num, force: true)) { Diag.Info($"[Creatures] blowgun dart knocked out beetle \"{((Object)((Component)val2).gameObject).name}\" for {num:0.#}s"); } } else { Spider val3 = (Spider)((creature is Spider) ? creature : null); if (val3 != null) { StunSpider(val3, num); } } } private static void KillZombie(MushroomZombie zombie) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 if (((MonoBehaviourPun)zombie).photonView.IsMine && (int)zombie.currentState != 6) { zombie.currentState = (State)6; Diag.Info("[Creatures] blowgun dart killed zombie \"" + ((Object)((Component)zombie).gameObject).name + "\""); } } private static void StunSpider(Spider spider, float stunSeconds) { if (!(stunSeconds <= 0f) && (!((Object)(object)spider.photonView != (Object)null) || spider.photonView.IsMine)) { float bonkStunTime = spider.bonkStunTime; try { spider.bonkStunTime = stunSeconds; spider.Bonk(); } finally { spider.bonkStunTime = bonkStunTime; } Diag.Info($"[Creatures] blowgun dart stunned spider \"{((Object)((Component)spider).gameObject).name}\" for {stunSeconds:0.#}s"); } } } internal static class CreatureAggroLog { private const float StatusIntervalSeconds = 1f; private static readonly Dictionary NextStatusTime = new Dictionary(); private static readonly Dictionary PendingReason = new Dictionary(); internal static void ClearLevelState() { NextStatusTime.Clear(); PendingReason.Clear(); } internal static void NoteForcedDeaggro(int instanceId, string reason) { PendingReason[instanceId] = reason; } internal static string ConsumeReason(int instanceId) { if (PendingReason.TryGetValue(instanceId, out var value)) { PendingReason.Remove(instanceId); return value; } return "vanilla rule (lost sight, out of range, or target downed)"; } internal static bool DueForStatus(int instanceId) { if (NextStatusTime.TryGetValue(instanceId, out var value) && Time.time < value) { return false; } NextStatusTime[instanceId] = Time.time + 1f; return true; } internal static string Describe(Character character) { if ((Object)(object)character == (Object)null) { return ""; } PhotonView photonView = ((MonoBehaviourPun)character).photonView; object obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? owner.NickName : null); } string text = (string)obj; if (!string.IsNullOrEmpty(text)) { return text; } return ((Object)character).name; } internal static void Aggro(string kind, GameObject creature, Character target, float distanceUnits, string limits) { Diag.V("[Aggro] " + kind + " \"" + ((Object)creature).name + "\" AGGROED " + Describe(target) + " " + $"at {GameUnits.ToMeters(distanceUnits):0.#}m - {limits}"); } internal static void Status(string kind, GameObject creature, Character target, string detail) { Diag.V("[Aggro] " + kind + " \"" + ((Object)creature).name + "\" chasing " + Describe(target) + " - " + detail); } internal static void Deaggro(string kind, GameObject creature, Character target, string reason, string limits) { Diag.V("[Aggro] " + kind + " \"" + ((Object)creature).name + "\" DEAGGROED " + Describe(target) + " - cause: " + reason + "; " + limits); } } internal static class CreatureDisablePatch { private static readonly Dictionary Deactivated = new Dictionary(); internal static void Run(Transform rootsSegment) { try { Apply(((Component)rootsSegment).GetComponentsInChildren(true), ((Component)rootsSegment).GetComponentsInChildren(true), "level load"); } catch (Exception ex) { Diag.Error("[Creatures] Run threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ReapplyToAll() { if (Plugin.Cfg == null) { return; } try { Apply(Object.FindObjectsOfType(true), Object.FindObjectsOfType(true), "config change"); } catch (Exception ex) { Diag.Error("[Creatures] ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ClearLevelState() { Deactivated.Clear(); } internal static void ApplyToBeetle(Beetle beetle) { if (RootsState.Active && Plugin.Cfg != null && Plugin.Cfg.EffectiveDisableBeetles) { GameObject val = CreatureScan.ResolveBeetleRoot(beetle); if (val.activeSelf) { val.SetActive(false); Deactivated[((Object)val).GetInstanceID()] = val; Diag.V("[Creatures] disabled late beetle \"" + CreatureScan.DescribePath(val.transform) + "\""); } } } private static void Apply(IReadOnlyList beetles, IReadOnlyList spiders, string reason) { bool flag = RootsState.Active && Plugin.Cfg.EffectiveDisableBeetles; bool flag2 = RootsState.Active && Plugin.Cfg.EffectiveDisableSpiders; int num = 0; int num2 = 0; foreach (Beetle beetle in beetles) { if (!((Object)(object)beetle == (Object)null)) { if (SetDisabled(CreatureScan.ResolveBeetleRoot(beetle), flag, "beetle", out var restored)) { num++; } else if (restored) { num2++; } } } int num3 = 0; int num4 = 0; foreach (Spider spider in spiders) { if ((Object)(object)spider == (Object)null) { continue; } GameObject val = CreatureScan.ResolveSpiderVisual(spider); if (!((Object)(object)val == (Object)null)) { if (SetDisabled(val, flag2, "spider", out var restored2)) { num3++; } else if (restored2) { num4++; } if ((Object)(object)spider.line != (Object)null) { ((Renderer)spider.line).enabled = !flag2; } } } Diag.Info("[Creatures] " + reason + ": disable-zombies=" + OnOff(RootsState.Active && Plugin.Cfg.EffectiveDisableZombies) + ", " + $"disable-beetles={OnOff(flag)} ({beetles.Count} found, {num} newly hidden, {num2} restored), " + $"disable-spiders={OnOff(flag2)} ({spiders.Count} found, {num3} newly hidden, {num4} restored)" + (RootsState.Active ? string.Empty : " [not in Roots - everything restored]")); } private static bool SetDisabled(GameObject root, bool disable, string kind, out bool restored) { restored = false; int instanceID = ((Object)root).GetInstanceID(); if (disable) { if (!root.activeSelf) { return false; } root.SetActive(false); Deactivated[instanceID] = root; Diag.V("[Creatures] disabled " + kind + " \"" + CreatureScan.DescribePath(root.transform) + "\""); return true; } if (Deactivated.TryGetValue(instanceID, out var value)) { Deactivated.Remove(instanceID); if ((Object)(object)value != (Object)null) { value.SetActive(true); restored = true; } } return false; } private static string OnOff(bool value) { if (!value) { return "off"; } return "ON"; } } [HarmonyPatch(typeof(ZombieManager), "Update")] internal static class ZombieSpawnSuppressionPatch { private static bool Prefix(ZombieManager __instance) { if (!RootsState.Active || Plugin.Cfg == null || !Plugin.Cfg.EffectiveDisableZombies) { return true; } if (!PhotonNetwork.IsMasterClient) { return false; } for (int num = __instance.zombies.Count - 1; num >= 0; num--) { if (num < __instance.zombies.Count) { MushroomZombie val = __instance.zombies[num]; if (!((Object)(object)val == (Object)null) && val.isNPCZombie) { Diag.V("[Creatures] despawning NPC zombie \"" + ((Object)((Component)val).gameObject).name + "\" (disable-zombies is on)"); val.DestroyZombie(); } } } return false; } } [HarmonyPatch(typeof(Spider), "Scan")] internal static class SpiderScanSuppressionPatch { private static bool Prefix() { if (RootsState.Active && Plugin.Cfg != null) { return !Plugin.Cfg.EffectiveDisableSpiders; } return true; } } [HarmonyPatch(typeof(Spider), "GrabCharacter")] internal static class SpiderGrabSuppressionPatch { private static bool Prefix() { if (RootsState.Active && Plugin.Cfg != null) { return !Plugin.Cfg.EffectiveDisableSpiders; } return true; } } [HarmonyPatch(typeof(Spider), "LateUpdate")] internal static class SpiderRopeSuppressionPatch { private static bool Prefix() { if (RootsState.Active && Plugin.Cfg != null) { return !Plugin.Cfg.EffectiveDisableSpiders; } return true; } } [HarmonyPatch(typeof(Mob), "Start")] internal static class MobStartDisablePatch { private static void Postfix(Mob __instance) { Beetle val = (Beetle)(object)((__instance is Beetle) ? __instance : null); if (val != null) { CreatureDisablePatch.ApplyToBeetle(val); } } } internal static class CreatureKnockbackPatch { private static readonly Dictionary Baselines = new Dictionary(); internal static void ReapplyToAll() { if (Plugin.Cfg == null || (Object)(object)MobManager.instance == (Object)null) { return; } try { int num = 0; foreach (Mob mob in MobManager.instance.mobs) { Beetle val = (Beetle)(object)((mob is Beetle) ? mob : null); if (val != null && ApplyToBeetle(val)) { num++; } } Diag.Info(RootsState.Active ? $"[Creatures] knockback reapply: beetle x{Plugin.Cfg.EffectiveBeetleKnockbackMultiplier:0.###} ({num} live)" : $"[Creatures] not in Roots - vanilla knockback restored on {num} beetle(s)"); } catch (Exception ex) { Diag.Error("[Creatures] knockback ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ClearLevelState() { Baselines.Clear(); } internal static bool ApplyToBeetle(Beetle beetle) { if ((Object)(object)beetle == (Object)null || Plugin.Cfg == null) { return false; } int instanceID = ((Object)beetle).GetInstanceID(); if (!Baselines.TryGetValue(instanceID, out (float, float) value)) { value = (beetle.bonkForce, beetle.bonkForceUp); Baselines[instanceID] = value; } double effectiveBeetleKnockbackMultiplier = Plugin.Cfg.EffectiveBeetleKnockbackMultiplier; if (!RootsState.Active || CreatureTuning.IsVanilla(effectiveBeetleKnockbackMultiplier)) { (beetle.bonkForce, beetle.bonkForceUp) = value; } else { beetle.bonkForce = CreatureTuning.ScaleKnockback(value.Item1, effectiveBeetleKnockbackMultiplier); beetle.bonkForceUp = CreatureTuning.ScaleKnockback(value.Item2, effectiveBeetleKnockbackMultiplier); } Diag.V($"[Creatures] beetle \"{((Object)((Component)beetle).gameObject).name}\" bonkForce {value.Item1:0.#}/{value.Item2:0.#} " + $"-> {beetle.bonkForce:0.#}/{beetle.bonkForceUp:0.#}"); return true; } } [HarmonyPatch(typeof(Mob), "Start")] internal static class MobStartKnockbackPatch { private static void Postfix(Mob __instance) { Beetle val = (Beetle)(object)((__instance is Beetle) ? __instance : null); if (val != null) { CreatureKnockbackPatch.ApplyToBeetle(val); } } } internal static class CreatureKnockoutPatch { private static readonly Dictionary BeetleKnockedOutUntil = new Dictionary(); private static readonly Dictionary ZombieKnockedOutUntil = new Dictionary(); private static readonly FieldRef LastHolder = ResolveLastHolder(); internal static void ClearLevelState() { BeetleKnockedOutUntil.Clear(); ZombieKnockedOutUntil.Clear(); } internal static float RemainingZombieKnockout(MushroomZombie zombie) { if ((Object)(object)zombie == (Object)null || !ZombieKnockedOutUntil.TryGetValue(((Object)zombie).GetInstanceID(), out var value)) { return 0f; } return Mathf.Max(0f, value - Time.time); } internal static bool KnockOutZombie(MushroomZombie zombie, float seconds, bool force = false) { if ((Object)(object)zombie == (Object)null || seconds <= 0f || (!force && RemainingZombieKnockout(zombie) > 0f)) { return false; } Character component = ((Component)zombie).GetComponent(); if ((Object)(object)component == (Object)null) { return false; } ZombieKnockedOutUntil[((Object)zombie).GetInstanceID()] = Time.time + seconds; ((MonoBehaviourPun)component).photonView.RPC("RPCA_Fall", (RpcTarget)0, new object[1] { seconds }); Diag.V($"[Creatures] zombie \"{((Object)((Component)zombie).gameObject).name}\" knocked out for {seconds:0.##}s"); return true; } internal static bool IsHardThrow(Collision collision, string kind, GameObject creature, out Item item) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) item = null; if (collision == null || Plugin.Cfg == null) { return false; } Item componentInParent = collision.gameObject.GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null || (int)componentInParent.itemState != 0) { return false; } Vector3 relativeVelocity = collision.relativeVelocity; float magnitude = ((Vector3)(ref relativeVelocity)).magnitude; float num = CreatureKnockout.ResolveMinThrowSpeedMeters(Plugin.Cfg.EffectiveCreatureKnockoutMinThrowSpeed); float thresholdUnits = GameUnits.MetersToUnits(num); float num2 = CreatureKnockout.ResolveMaxThrowDistanceMeters(Plugin.Cfg.EffectiveCreatureKnockoutMaxThrowDistance); float thresholdUnits2 = GameUnits.MetersToUnits(num2); float num3 = ThrowerDistanceUnits(componentInParent, creature); bool flag = CreatureKnockout.IsHardEnough(magnitude, thresholdUnits); bool flag2 = num3 < 0f || CreatureKnockout.IsCloseEnough(num3, thresholdUnits2); if (flag && flag2) { item = componentInParent; return true; } if (Diag.Enabled) { string text = (flag ? "thrown from too far away" : (flag2 ? "too soft" : "too soft and too far")); string arg = ((num3 < 0f) ? "thrower unknown" : ($"{GameUnits.ToMeters(num3):0.#}m from thrower" + ((num2 > 0f) ? $"/{num2:0.#}m" : " (no limit)"))); Diag.V("[Creatures] " + kind + " \"" + ((Object)creature).name + "\" ignored \"" + ((Object)((Component)componentInParent).gameObject).name + "\" (" + text + "): " + $"{GameUnits.ToMeters(magnitude):0.#}/{num:0.#} m/s, {arg}"); } return false; } private static float ThrowerDistanceUnits(Item item, GameObject creature) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (LastHolder == null) { return -1f; } Character val = LastHolder.Invoke(item); if ((Object)(object)val == (Object)null) { return -1f; } return Vector3.Distance(val.Center, creature.transform.position); } private static FieldRef ResolveLastHolder() { try { return AccessTools.FieldRefAccess("lastHolderCharacter"); } catch (Exception ex) { Diag.Error("[Creatures] could not bind Item.lastHolderCharacter (" + ex.GetType().Name + ") - knockout throws will not be distance-limited."); return null; } } internal static bool IsBeetleKnockedOut(Mob beetle) { if ((Object)(object)beetle == (Object)null) { return false; } if (BeetleKnockedOutUntil.TryGetValue(((Object)beetle).GetInstanceID(), out var value)) { return Time.time < value; } return false; } internal static bool KnockOutBeetle(Beetle beetle, float seconds, bool force = false) { if ((Object)(object)beetle == (Object)null || seconds <= 0f || (!force && IsBeetleKnockedOut((Mob)(object)beetle))) { return false; } if (!MobStateAccess.Available) { return false; } BeetleKnockedOutUntil[((Object)beetle).GetInstanceID()] = Time.time + seconds; MobStateAccess.SetRigidbodyControlled((Mob)(object)beetle); ((Component)beetle).GetComponent()?.Broadcast(seconds); Diag.V($"[Creatures] beetle \"{((Object)((Component)beetle).gameObject).name}\" knocked out for {seconds:0.##}s"); return true; } } internal static class MobStateAccess { private static readonly MethodInfo Setter = AccessTools.PropertySetter(typeof(Mob), "mobState"); private static readonly Type StateType = AccessTools.Inner(typeof(Mob), "MobState"); private static readonly object RigidbodyControlled = ((StateType != null) ? Enum.ToObject(StateType, 0) : null); private static bool _loggedFailure; private static readonly MethodInfo Getter = AccessTools.PropertyGetter(typeof(Mob), "mobState"); private static readonly object Walking = ((StateType != null) ? Enum.ToObject(StateType, 1) : null); internal static bool Available { get { if (Setter != null) { return RigidbodyControlled != null; } return false; } } internal static bool IsWalking(Mob mob) { if (Getter == null || Walking == null) { return false; } try { return object.Equals(Getter.Invoke(mob, null), Walking); } catch { return false; } } internal static void SetRigidbodyControlled(Mob mob) { try { Setter.Invoke(mob, new object[1] { RigidbodyControlled }); } catch (Exception ex) { if (!_loggedFailure) { _loggedFailure = true; Diag.Error("[Creatures] could not set Mob.mobState (" + ex.GetType().Name + ") - beetle knockouts disabled."); } } } } [HarmonyPatch(typeof(Mob), "TestStartFlippingMyself")] internal static class BeetleFlipSuppressionPatch { private static bool Prefix(Mob __instance) { return !CreatureKnockoutPatch.IsBeetleKnockedOut(__instance); } } internal sealed class ZombieKnockoutReceiver : MonoBehaviour { internal MushroomZombie Zombie; private void OnCollisionEnter(Collision collision) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if (!RootsState.Active || (Object)(object)Zombie == (Object)null || Plugin.Cfg == null || collision == null) { return; } PhotonView component = ((Component)Zombie).GetComponent(); if (!((Object)(object)component != (Object)null) || component.IsMine) { double effectiveZombieKnockoutSeconds = Plugin.Cfg.EffectiveZombieKnockoutSeconds; if (!CreatureKnockout.IsDisabled(effectiveZombieKnockoutSeconds) && CreatureKnockoutPatch.IsHardThrow(collision, "zombie", ((Component)Zombie).gameObject, out var item) && CreatureKnockoutPatch.KnockOutZombie(Zombie, CreatureKnockout.ResolveSeconds(effectiveZombieKnockoutSeconds))) { string name = ((Object)((Component)item).gameObject).name; Vector3 relativeVelocity = collision.relativeVelocity; Diag.V("[Creatures] ...by \"" + name + "\" at " + $"{GameUnits.ToMeters(((Vector3)(ref relativeVelocity)).magnitude):0.#} m/s"); } } } } [HarmonyPatch(typeof(MushroomZombie), "Update")] internal static class ZombieKnockoutEnforcePatch { private static void Postfix(MushroomZombie __instance) { float num = CreatureKnockoutPatch.RemainingZombieKnockout(__instance); if (!(num <= 0f)) { Character component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.data == (Object)null)) { component.data.fallSeconds = num; __instance.currentState = (State)5; } } } } internal sealed class BeetleKnockoutReceiver : MonoBehaviour { private Beetle _beetle; private void Awake() { _beetle = ((Component)this).GetComponent(); } private void OnCollisionEnter(Collision collision) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if (!RootsState.Active || (Object)(object)_beetle == (Object)null || Plugin.Cfg == null || collision == null) { return; } PhotonView component = ((Component)_beetle).GetComponent(); if (!((Object)(object)component != (Object)null) || component.IsMine) { double effectiveBeetleKnockoutSeconds = Plugin.Cfg.EffectiveBeetleKnockoutSeconds; if (!CreatureKnockout.IsDisabled(effectiveBeetleKnockoutSeconds) && CreatureKnockoutPatch.IsHardThrow(collision, "beetle", ((Component)_beetle).gameObject, out var item) && CreatureKnockoutPatch.KnockOutBeetle(_beetle, CreatureKnockout.ResolveSeconds(effectiveBeetleKnockoutSeconds))) { string name = ((Object)((Component)item).gameObject).name; Vector3 relativeVelocity = collision.relativeVelocity; Diag.V("[Creatures] ...by \"" + name + "\" at " + $"{GameUnits.ToMeters(((Vector3)(ref relativeVelocity)).magnitude):0.#} m/s"); } } } } [HarmonyPatch(typeof(MushroomZombie), "Start")] internal static class MushroomZombieStartKnockoutPatch { private static void Postfix(MushroomZombie __instance) { try { Character component = ((Component)__instance).GetComponent(); CharacterRagdoll val = (((Object)(object)component != (Object)null && component.refs != null) ? component.refs.ragdoll : null); if ((Object)(object)val == (Object)null) { return; } int num = 0; foreach (Bodypart part in val.partList) { if (!((Object)(object)part == (Object)null) && !((Object)(object)part.Rig == (Object)null)) { GameObject gameObject = ((Component)part.Rig).gameObject; if ((Object)(object)gameObject.GetComponent() == (Object)null) { gameObject.AddComponent().Zombie = __instance; num++; } } } Diag.V($"[Creatures] zombie \"{((Object)((Component)__instance).gameObject).name}\": knockout receivers on {num} bodypart(s)"); } catch (Exception ex) { Diag.Error("[Creatures] attaching zombie knockout receivers threw: " + ex.GetType().Name + ": " + ex.Message); } } } [HarmonyPatch(typeof(Mob), "Start")] internal static class MobStartKnockoutPatch { private static void Postfix(Mob __instance) { if (__instance is Beetle && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } internal static class CreatureRagdollPatch { private static readonly Dictionary BeetleBaselines = new Dictionary(); private static readonly Dictionary ZombieBaselines = new Dictionary(); internal static void ReapplyToAll() { if (Plugin.Cfg == null) { return; } try { int num = 0; int num2 = 0; if ((Object)(object)MobManager.instance != (Object)null) { foreach (Mob mob in MobManager.instance.mobs) { Beetle val = (Beetle)(object)((mob is Beetle) ? mob : null); if (val != null && ApplyToBeetle(val)) { num++; } } } ZombieManager instance = ZombieManager.Instance; if ((Object)(object)instance != (Object)null) { foreach (MushroomZombie zombie in instance.zombies) { if (ApplyToZombie(zombie)) { num2++; } } } Diag.Info(RootsState.Active ? ($"[Creatures] ragdoll reapply: x{Plugin.Cfg.EffectiveCreatureRagdollMultiplier:0.###} " + $"({num} beetle(s), {num2} zombie(s) live)") : $"[Creatures] not in Roots - vanilla ragdoll restored on {num} beetle(s), {num2} zombie(s)"); } catch (Exception ex) { Diag.Error("[Creatures] ragdoll ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ClearLevelState() { BeetleBaselines.Clear(); ZombieBaselines.Clear(); } internal static bool ApplyToBeetle(Beetle beetle) { if ((Object)(object)beetle == (Object)null || Plugin.Cfg == null) { return false; } int instanceID = ((Object)beetle).GetInstanceID(); if (!BeetleBaselines.TryGetValue(instanceID, out var value)) { value = beetle.ragdollTime; BeetleBaselines[instanceID] = value; } double effectiveCreatureRagdollMultiplier = Plugin.Cfg.EffectiveCreatureRagdollMultiplier; beetle.ragdollTime = ((!RootsState.Active || CreatureTuning.IsVanilla(effectiveCreatureRagdollMultiplier)) ? value : CreatureTuning.ScaleRagdollTime(value, effectiveCreatureRagdollMultiplier)); Diag.V($"[Creatures] beetle \"{((Object)((Component)beetle).gameObject).name}\" ragdollTime {value:0.##}s -> {beetle.ragdollTime:0.##}s"); return true; } internal static bool ApplyToZombie(MushroomZombie zombie) { if ((Object)(object)zombie == (Object)null || Plugin.Cfg == null) { return false; } int instanceID = ((Object)zombie).GetInstanceID(); if (!ZombieBaselines.TryGetValue(instanceID, out var value)) { value = zombie.biteStunTime; ZombieBaselines[instanceID] = value; } double effectiveCreatureRagdollMultiplier = Plugin.Cfg.EffectiveCreatureRagdollMultiplier; zombie.biteStunTime = ((!RootsState.Active || CreatureTuning.IsVanilla(effectiveCreatureRagdollMultiplier)) ? value : CreatureTuning.ScaleRagdollTime(value, effectiveCreatureRagdollMultiplier)); Diag.V($"[Creatures] zombie \"{((Object)((Component)zombie).gameObject).name}\" biteStunTime {value:0.##}s -> {zombie.biteStunTime:0.##}s"); return true; } } [HarmonyPatch(typeof(Mob), "Start")] internal static class MobStartRagdollPatch { private static void Postfix(Mob __instance) { Beetle val = (Beetle)(object)((__instance is Beetle) ? __instance : null); if (val != null) { CreatureRagdollPatch.ApplyToBeetle(val); } } } [HarmonyPatch(typeof(MushroomZombie), "Start")] internal static class MushroomZombieStartRagdollPatch { private static void Postfix(MushroomZombie __instance) { CreatureRagdollPatch.ApplyToZombie(__instance); } } internal static class CreatureScan { internal static GameObject ResolveBeetleRoot(Beetle beetle) { return ((Component)beetle).gameObject; } internal static GameObject ResolveSpiderVisual(Spider spider) { if (!((Object)(object)spider.spider != (Object)null)) { return null; } return spider.spider; } internal static string DescribePath(Transform t) { List list = new List(); Transform val = t; while ((Object)(object)val != (Object)null && list.Count < 6) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } } internal static class CreatureSpeedPatch { private static readonly Dictionary BeetleBaselines = new Dictionary(); private static readonly Dictionary ZombieBaselines = new Dictionary(); internal static void ReapplyToAll() { if (Plugin.Cfg == null) { return; } try { int num = 0; int num2 = 0; if ((Object)(object)MobManager.instance != (Object)null) { foreach (Mob mob in MobManager.instance.mobs) { Beetle val = (Beetle)(object)((mob is Beetle) ? mob : null); if (val != null && ApplyToBeetle(val)) { num++; } } } ZombieManager instance = ZombieManager.Instance; if ((Object)(object)instance != (Object)null) { foreach (MushroomZombie zombie in instance.zombies) { if ((Object)(object)zombie != (Object)null && ApplyToZombie(zombie)) { num2++; } } } Diag.Info(RootsState.Active ? ($"[Creatures] speed reapply: zombie x{Plugin.Cfg.EffectiveZombieSpeedMultiplier:0.###} " + $"({num2} live), beetle x{Plugin.Cfg.EffectiveBeetleSpeedMultiplier:0.###} ({num} live)") : $"[Creatures] not in Roots - vanilla speed restored on {num2} zombie(s), {num} beetle(s)"); } catch (Exception ex) { Diag.Error("[Creatures] speed ReapplyToAll threw: " + ex.GetType().Name + ": " + ex.Message); } } internal static void ClearLevelState() { BeetleBaselines.Clear(); ZombieBaselines.Clear(); } internal static bool ApplyToBeetle(Beetle beetle) { if ((Object)(object)beetle == (Object)null || Plugin.Cfg == null) { return false; } int instanceID = ((Object)beetle).GetInstanceID(); if (!BeetleBaselines.TryGetValue(instanceID, out var value)) { value = ((Mob)beetle).movementSpeed; BeetleBaselines[instanceID] = value; } double effectiveBeetleSpeedMultiplier = Plugin.Cfg.EffectiveBeetleSpeedMultiplier; ((Mob)beetle).movementSpeed = ((!RootsState.Active || CreatureTuning.IsVanilla(effectiveBeetleSpeedMultiplier)) ? value : CreatureTuning.ScaleMovementSpeed(value, effectiveBeetleSpeedMultiplier)); Diag.V($"[Creatures] beetle \"{((Object)((Component)beetle).gameObject).name}\" movementSpeed {value:0.##} -> {((Mob)beetle).movementSpeed:0.##}"); return true; } internal static bool ApplyToZombie(MushroomZombie zombie) { if ((Object)(object)zombie == (Object)null || Plugin.Cfg == null) { return false; } CharacterMovement componentInChildren = ((Component)zombie).GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { return false; } int instanceID = ((Object)componentInChildren).GetInstanceID(); if (!ZombieBaselines.TryGetValue(instanceID, out var value)) { value = componentInChildren.movementForce; ZombieBaselines[instanceID] = value; } double effectiveZombieSpeedMultiplier = Plugin.Cfg.EffectiveZombieSpeedMultiplier; componentInChildren.movementForce = ((!RootsState.Active || CreatureTuning.IsVanilla(effectiveZombieSpeedMultiplier)) ? value : CreatureTuning.ScaleMovementSpeed(value, effectiveZombieSpeedMultiplier)); Diag.V($"[Creatures] zombie \"{((Object)((Component)zombie).gameObject).name}\" movementForce {value:0.##} -> {componentInChildren.movementForce:0.##}"); return true; } } [HarmonyPatch(typeof(Mob), "Start")] internal static class MobStartSpeedPatch { private static void Postfix(Mob __instance) { Beetle val = (Beetle)(object)((__instance is Beetle) ? __instance : null); if (val != null) { CreatureSpeedPatch.ApplyToBeetle(val); } } } [HarmonyPatch(typeof(MushroomZombie), "Start")] internal static class MushroomZombieStartSpeedPatch { private static void Postfix(MushroomZombie __instance) { CreatureSpeedPatch.ApplyToZombie(__instance); } } internal static class CreatureStunIndicator { private static bool _loggedMissingTemplate; private static ParticleSystem FindTemplate() { SpiderManager instance = SpiderManager.instance; if ((Object)(object)instance == (Object)null) { return null; } foreach (Spider spider in instance.spiders) { if ((Object)(object)spider != (Object)null && (Object)(object)spider.stunnedParticle != (Object)null) { return spider.stunnedParticle; } } return null; } internal static ParticleSystem TryClone(Transform host, float heightOffset) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) try { ParticleSystem val = FindTemplate(); if ((Object)(object)val == (Object)null) { if (!_loggedMissingTemplate) { _loggedMissingTemplate = true; Diag.V("[Creatures] no spider stun particle available to clone - beetles will have no stun marker this level."); } return null; } ParticleSystem obj = Object.Instantiate(val, host, false); ((Object)((Component)obj).gameObject).name = "FairootsStunIndicator"; ((Component)obj).transform.localPosition = new Vector3(0f, heightOffset, 0f); obj.Stop(true, (ParticleSystemStopBehavior)0); return obj; } catch (Exception ex) { Diag.Error("[Creatures] cloning the stun indicator threw: " + ex.GetType().Name + ": " + ex.Message); return null; } } internal static void Drive(ParticleSystem particles, bool stunned, ref bool authoredLoopCaptured, ref bool authoredLoop) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)particles == (Object)null) { return; } MainModule main = particles.main; if (!authoredLoopCaptured) { authoredLoop = ((MainModule)(ref main)).loop; authoredLoopCaptured = true; } if (stunned) { if (!((MainModule)(ref main)).loop) { ((MainModule)(ref main)).loop = true; } if (!particles.isPlaying) { particles.Play(); } } else if (particles.isPlaying) { ((MainModule)(ref main)).loop = authoredLoop; particles.Stop(true, (ParticleSystemStopBehavior)0); particles.Clear(true); } } } [HarmonyPatch(typeof(Spider), "LateUpdate")] internal static class SpiderStunIndicatorPatch { private static readonly FieldRef StunnedTime = ResolveStunnedTime(); private static bool _loggedMissingField; private static readonly Dictionary LoopState = new Dictionary(); internal static void ClearLevelState() { LoopState.Clear(); } private static FieldRef ResolveStunnedTime() { try { return AccessTools.FieldRefAccess("_stunnedTime"); } catch (Exception ex) { Diag.Error("[Creatures] could not bind Spider._stunnedTime (" + ex.GetType().Name + ") - spider stun markers will keep vanilla's fixed length."); return null; } } private static void Postfix(Spider __instance) { if (!RootsState.Active) { return; } if (StunnedTime == null) { if (!_loggedMissingField) { _loggedMissingField = true; Diag.Warn("[Creatures] spider stun marker running vanilla-length (_stunnedTime unavailable)."); } } else if (!((Object)(object)__instance.stunnedParticle == (Object)null)) { int instanceID = ((Object)__instance).GetInstanceID(); LoopState.TryGetValue(instanceID, out (bool, bool) value); var (authoredLoopCaptured, authoredLoop) = value; CreatureStunIndicator.Drive(__instance.stunnedParticle, StunnedTime.Invoke(__instance) > 0f, ref authoredLoopCaptured, ref authoredLoop); LoopState[instanceID] = (authoredLoopCaptured, authoredLoop); } } } internal sealed class BeetleStunIndicator : MonoBehaviour { private const float HeightOffset = 0.9f; private const float HeadOffsetFraction = 0.75f; private Beetle _beetle; private ParticleSystem _particles; private bool _authoredLoopCaptured; private bool _authoredLoop; private float _showUntil; private void Awake() { _beetle = ((Component)this).GetComponent(); } internal void Broadcast(float seconds) { if (seconds <= 0f) { return; } PhotonView component = ((Component)this).GetComponent(); if ((Object)(object)component == (Object)null) { Show(seconds); return; } try { component.RPC("FairootsBeetleStun", (RpcTarget)0, new object[1] { seconds }); } catch (Exception ex) { Diag.V("[Creatures] stun-marker RPC failed (" + ex.GetType().Name + ") - showing locally only."); Show(seconds); } } [PunRPC] public void FairootsBeetleStun(float seconds) { Show(seconds); } private void Show(float seconds) { _showUntil = Time.time + seconds; if ((Object)(object)_particles == (Object)null) { _particles = CreatureStunIndicator.TryClone(((Component)this).transform, 0.9f); if ((Object)(object)_particles == (Object)null) { return; } } PlaceOverHead(); CreatureStunIndicator.Drive(_particles, stunned: true, ref _authoredLoopCaptured, ref _authoredLoop); } private void PlaceOverHead() { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_particles == (Object)null)) { Vector3 localPosition = default(Vector3); ((Vector3)(ref localPosition))..ctor(0f, 0.9f, 0f); Transform val = (((Object)(object)_beetle != (Object)null) ? _beetle.bonkPoint : null); if ((Object)(object)val != (Object)null) { Vector3 val2 = ((Component)this).transform.InverseTransformPoint(val.position); ((Vector3)(ref localPosition))..ctor(val2.x * 0.75f, 0.9f, val2.z * 0.75f); } ((Component)_particles).transform.localPosition = localPosition; } } private void LateUpdate() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_particles == (Object)null)) { bool flag = Time.time < _showUntil; if (flag) { PlaceOverHead(); ((Component)_particles).transform.rotation = Quaternion.identity; } CreatureStunIndicator.Drive(_particles, flag, ref _authoredLoopCaptured, ref _authoredLoop); } } } [HarmonyPatch(typeof(Mob), "Start")] internal static class MobStartStunIndicatorPatch { private static void Postfix(Mob __instance) { if (!(__instance is Beetle) || (Object)(object)((Component)__instance).GetComponent() != (Object)null) { return; } ((Component)__instance).gameObject.AddComponent(); try { PhotonView component = ((Component)__instance).GetComponent(); if (component != null) { component.RefreshRpcMonoBehaviourCache(); } } catch (Exception ex) { Diag.V("[Creatures] could not refresh the beetle RPC cache (" + ex.GetType().Name + ") - stun markers may not replicate."); } } } internal static class CreatureWindPatch { private static readonly FieldRef WindDirection = ResolveWindDirection(); private static FieldRef ResolveWindDirection() { try { return AccessTools.FieldRefAccess("currentWindDirection"); } catch (Exception ex) { Diag.Error("[Creatures] could not bind WindChillZone.currentWindDirection (" + ex.GetType().Name + ") - beetles will not be affected by wind."); return null; } } private static Vector3? ActiveWindAt(Vector3 position) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) if (WindDirection == null || Plugin.Cfg == null) { return null; } if (Plugin.Cfg.EffectiveDisableWindEntirely) { return null; } WindChillZone instance = WindChillZone.instance; if ((Object)(object)instance == (Object)null || !instance.windActive || !((Bounds)(ref instance.windZoneBounds)).Contains(position)) { return null; } Vector3 val = WindDirection.Invoke(instance); if (!(((Vector3)(ref val)).sqrMagnitude < 1E-06f)) { return ((Vector3)(ref val)).normalized; } return null; } internal static Vector3? WindPushAt(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return ActiveWindAt(position); } } [HarmonyPatch(typeof(WindChillZone), "AddWindForceToCharacter")] internal static class ZombieWindForcePatch { private static float _nextLogTime; private static void Prefix(WindChillZone __instance, Character character, out float? __state) { __state = null; if (!RootsState.Active || Plugin.Cfg == null || (Object)(object)character == (Object)null || !character.isZombie) { return; } double effectiveZombieWindMultiplier = Plugin.Cfg.EffectiveZombieWindMultiplier; if (!CreatureWind.IsVanillaZombieWind(effectiveZombieWindMultiplier)) { float windForce = __instance.windForce; __state = windForce; __instance.windForce = (float)((double)windForce * CreatureWind.ResolveZombieMultiplier(effectiveZombieWindMultiplier)); if (Diag.Enabled && Time.time >= _nextLogTime) { _nextLogTime = Time.time + 2f; Diag.V($"[Creatures] wind on zombie \"{((Object)character).name}\": windForce {windForce:0.#} -> " + $"{__instance.windForce:0.#} (x{effectiveZombieWindMultiplier:0.##}; the game applies " + $"{0.6f:0.##} of a player's on top)"); } } } private static void Postfix(WindChillZone __instance, float? __state) { if (__state.HasValue) { __instance.windForce = __state.Value; } } } [HarmonyPatch(typeof(Mob), "FixedUpdate")] internal static class BeetleWindDriftPatch { private static void Postfix(Mob __instance) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) try { if (!RootsState.Active || Plugin.Cfg == null) { return; } Beetle val = (Beetle)(object)((__instance is Beetle) ? __instance : null); if (val == null || ((Mob)val).sleeping) { return; } double effectiveBeetleWindSusceptibility = Plugin.Cfg.EffectiveBeetleWindSusceptibility; float num = CreatureWind.BeetleDriftSpeed(((Mob)val).movementSpeed, effectiveBeetleWindSusceptibility); if (!(num <= 0f) && MobStateAccess.IsWalking((Mob)(object)val)) { Vector3? val2 = CreatureWindPatch.WindPushAt(((Component)val).transform.position); if (val2.HasValue) { Transform transform = ((Component)val).transform; transform.position += val2.Value * num * Time.fixedDeltaTime; } } } catch (Exception ex) { Diag.Error("[Creatures] beetle wind drift threw: " + ex.GetType().Name + ": " + ex.Message); } } } internal static class SpiderStrikeWarning { private readonly struct Strike { internal Spider Spider { get; } internal float ExpiresAt { get; } internal Strike(Spider spider, float expiresAt) { Spider = spider; ExpiresAt = expiresAt; } } private const float MissLingerSeconds = 1f; private static readonly Dictionary Incoming = new Dictionary(); internal static void ClearLevelState() { Incoming.Clear(); } internal static void NoteDrop(Spider spider, float distance) { if (!((Object)(object)spider == (Object)null)) { float num = spider.spiderMoveSpeed * Mathf.Sqrt(Mathf.Max(0f, distance)); float num2 = num + 1f; float expiresAt = Time.time + num2; int instanceID = ((Object)spider).GetInstanceID(); bool num3 = !Incoming.ContainsKey(instanceID); Incoming[instanceID] = new Strike(spider, expiresAt); if (num3) { Diag.V("[Creatures] spider \"" + ((Object)((Component)spider).gameObject).name + "\" is dropping on the local player - " + $"warning for {num2:0.##}s (descent {num:0.##}s + {1f:0.#}s linger; " + $"its post-landing hang of {spider.spiderWaitTime:0.##}s is excluded)"); } } } internal static bool StrikeIncoming() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Invalid comparison between Unknown and I4 if (Incoming.Count == 0) { return false; } List list = null; bool result = false; foreach (KeyValuePair item in Incoming) { Spider spider = item.Value.Spider; if ((Object)(object)spider == (Object)null || (int)spider.spiderState != 1) { (list ?? (list = new List())).Add(item.Key); } else if (Time.time >= item.Value.ExpiresAt) { (list ?? (list = new List())).Add(item.Key); Diag.V("[Creatures] spider \"" + ((Object)((Component)spider).gameObject).name + "\" missed the local player - warning cleared"); } else { result = true; } } if (list != null) { foreach (int item2 in list) { Incoming.Remove(item2); } } return result; } } [HarmonyPatch(typeof(Spider), "RPCA_DropSpider")] internal static class SpiderDropWarningPatch { private static void Postfix(Spider __instance, PhotonView characterView, float distance) { if (RootsState.Active && Plugin.Cfg != null && !((Object)(object)characterView == (Object)null) && !Plugin.Cfg.EffectiveDisableSpiders) { Character component = ((Component)characterView).GetComponent(); if (!((Object)(object)component == (Object)null) && component.IsLocal) { SpiderStrikeWarning.NoteDrop(__instance, distance); } } } } [HarmonyPatch(typeof(MushroomZombie), "Update")] internal static class ZombieAggroLogPatch { private static readonly Dictionary LastTarget = new Dictionary(); internal static void ClearLevelState() { LastTarget.Clear(); } private static void Postfix(MushroomZombie __instance) { if (RootsState.Active && Diag.Enabled && Plugin.Cfg != null) { int instanceID = ((Object)__instance).GetInstanceID(); Character currentTarget = __instance.currentTarget; LastTarget.TryGetValue(instanceID, out var value); if (currentTarget != value) { LastTarget[instanceID] = currentTarget; LogTransition(__instance, value, currentTarget); } else if ((Object)(object)currentTarget != (Object)null && CreatureAggroLog.DueForStatus(instanceID)) { CreatureAggroLog.Status("zombie", ((Component)__instance).gameObject, currentTarget, DescribeChaseState(__instance, currentTarget)); } } } private static void LogTransition(MushroomZombie zombie, Character previous, Character current) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)current != (Object)null) { Character component = ((Component)zombie).GetComponent(); float distanceUnits = (((Object)(object)component == (Object)null) ? 0f : Vector3.Distance(component.Center, current.Center)); CreatureAggroLog.Aggro("zombie", ((Component)zombie).gameObject, current, distanceUnits, DescribeLimits()); } else if ((Object)(object)previous != (Object)null) { CreatureAggroLog.Deaggro("zombie", ((Component)zombie).gameObject, previous, CreatureAggroLog.ConsumeReason(((Object)zombie).GetInstanceID()), DescribeLimits()); } } private static string DescribeChaseState(MushroomZombie zombie, Character target) { //IL_001c: 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) double effectiveZombieDeaggroMultiplier = Plugin.Cfg.EffectiveZombieDeaggroMultiplier; Character component = ((Component)zombie).GetComponent(); float units = (((Object)(object)component == (Object)null) ? 0f : Vector3.Distance(component.Center, target.Center)); float num = ((ZombieDeaggroPatch.SinceSeenTarget == null) ? (-1f) : ZombieDeaggroPatch.SinceSeenTarget.Invoke(zombie)); string arg = ((num < 0f) ? "unseen-for=" : $"unseen-for={num:0.#}s/{ZombieDeaggro.ResolveSightLossSeconds(effectiveZombieDeaggroMultiplier):0.#}s"); return $"distance={GameUnits.ToMeters(units):0.#}m/" + $"{GameUnits.ToMeters(ZombieDeaggro.ResolveDistanceWorldUnits(effectiveZombieDeaggroMultiplier)):0.#}m, {arg}"; } private static string DescribeLimits() { double effectiveZombieDeaggroMultiplier = Plugin.Cfg.EffectiveZombieDeaggroMultiplier; if (!Plugin.Cfg.EffectiveZombieDeaggroEnabled) { return "deaggro DISABLED (vanilla: zombies never give up)"; } return $"multiplier={effectiveZombieDeaggroMultiplier:0.##} => gives up after " + $"{ZombieDeaggro.ResolveSightLossSeconds(effectiveZombieDeaggroMultiplier):0.#}s unseen or " + $"{GameUnits.ToMeters(ZombieDeaggro.ResolveDistanceWorldUnits(effectiveZombieDeaggroMultiplier)):0.#}m away"; } } [HarmonyPatch(typeof(MushroomZombie), "TargetIsValid")] internal static class ZombieDeaggroPatch { internal static readonly FieldRef SinceSeenTarget = ResolveSinceSeenTarget(); private static bool _loggedMissingField; private static FieldRef ResolveSinceSeenTarget() { try { return AccessTools.FieldRefAccess("sinceSeenTarget"); } catch (Exception ex) { Diag.Error("[Creatures] could not bind MushroomZombie.sinceSeenTarget (" + ex.GetType().Name + ") - zombie deaggro will fall back to distance only."); return null; } } private static void Postfix(MushroomZombie __instance, Character target, ref bool __result) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (!RootsState.Active || !__result || Plugin.Cfg == null || (Object)(object)target == (Object)null || !Plugin.Cfg.EffectiveZombieDeaggroEnabled || (int)__instance.currentState == 0) { return; } double effectiveZombieDeaggroMultiplier = Plugin.Cfg.EffectiveZombieDeaggroMultiplier; Character component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null) { return; } float num = Vector3.Distance(component.Center, target.Center); if (ZombieDeaggro.ShouldDeaggroForDistance(num, effectiveZombieDeaggroMultiplier)) { __result = false; LogDeaggro(__instance, target, "distance", num, effectiveZombieDeaggroMultiplier); } else { if (target != __instance.currentTarget) { return; } if (SinceSeenTarget == null) { if (!_loggedMissingField) { _loggedMissingField = true; Diag.Warn("[Creatures] zombie deaggro running distance-only (sinceSeenTarget unavailable)."); } return; } float num2 = SinceSeenTarget.Invoke(__instance); if (ZombieDeaggro.ShouldDeaggroForSightLoss(num2, effectiveZombieDeaggroMultiplier)) { __result = false; LogDeaggro(__instance, target, "lost sight", num2, effectiveZombieDeaggroMultiplier); } } } private static void LogDeaggro(MushroomZombie zombie, Character target, string reason, float value, double multiplier) { if (Diag.Enabled) { string text = ((reason == "distance") ? $"ran {GameUnits.ToMeters(value):0.#}m away (limit {GameUnits.ToMeters(ZombieDeaggro.ResolveDistanceWorldUnits(multiplier)):0.#}m)" : $"stayed unseen {value:0.#}s (limit {ZombieDeaggro.ResolveSightLossSeconds(multiplier):0.#}s)"); CreatureAggroLog.NoteForcedDeaggro(((Object)zombie).GetInstanceID(), "Fairoots deaggro rule - " + text); } } } } namespace Fairoots.Core { public static class BeetleDeaggro { public const double MinMultiplier = 0.1; public const double MaxMultiplier = 3.0; public const float SuppressionSeconds = 5f; public static double ClampMultiplier(double multiplier) { if (double.IsNaN(multiplier)) { return 1.0; } return Math.Min(3.0, Math.Max(0.1, multiplier)); } public static float ResolveRetentionDistance(float vanillaAggroDistance, double multiplier) { return CreatureTuning.ScaleDeaggroDistance(vanillaAggroDistance, ClampMultiplier(multiplier)); } public static bool ShouldKeepTarget(float distanceToTarget, float vanillaAggroDistance, double multiplier) { return distanceToTarget <= ResolveRetentionDistance(vanillaAggroDistance, multiplier); } } public readonly struct ClimbMove { public float Lateral { get; } public float Up { get; } public ClimbMove(float lateral, float up) { Lateral = lateral; Up = up; } } public static class ClimbWindResistance { public const float PressureFreshnessSeconds = 0.35f; public const float GraceRampFraction = 0.4f; public static bool IsPressureCurrent(float recordedAtTime, float currentTime) { if (recordedAtTime < 0f) { return false; } return currentTime - recordedAtTime <= 0.35f; } public static float GraceForceMultiplier(float lastHeldOnTime, float currentTime, float graceSeconds, double reducedMultiplier) { if (lastHeldOnTime < 0f || graceSeconds <= 0f) { return 1f; } float num = currentTime - lastHeldOnTime; if (num >= graceSeconds) { return 1f; } if (num < 0f) { num = 0f; } float num2 = (float)reducedMultiplier; if (num2 < 0f || float.IsNaN(num2)) { num2 = 0f; } else if (num2 > 1f) { num2 = 1f; } float num3 = graceSeconds * 0.6f; if (num <= num3) { return num2; } float num4 = (num - num3) / (graceSeconds - num3); return num2 + (1f - num2) * num4; } public static float ClampPressure(float pressure) { if (pressure < 0f || float.IsNaN(pressure)) { return 0f; } if (!(pressure > 1f)) { return pressure; } return 1f; } public static ClimbMove Resist(ClimbMove move, float windLateral, float windUp, float pressure, double baseMultiplier, double upwardMultiplier, double intoWindMultiplier) { pressure = ClampPressure(pressure); if (pressure <= 0f) { return move; } float num = Fade(baseMultiplier, pressure); float num2 = move.Up * num; if (move.Up > 0f) { num2 *= Fade(upwardMultiplier, pressure); } num2 *= OpposingFactor(move.Up, windUp, pressure, intoWindMultiplier); return new ClimbMove(move.Lateral * num * OpposingFactor(move.Lateral, windLateral, pressure, intoWindMultiplier), num2); } private static float OpposingFactor(float moveComponent, float windComponent, float pressure, double multiplier) { if (moveComponent * windComponent >= 0f) { return 1f; } return Fade(multiplier, pressure * Math.Abs(windComponent)); } private static float Fade(double multiplier, float weight) { if (multiplier < 0.0) { multiplier = 0.0; } float num = (float)(1.0 + (multiplier - 1.0) * (double)weight); if (!(num < 0f)) { return num; } return 0f; } } public static class ClusteredRemovalSelection { public static bool[] Select(IReadOnlyList positions, int budget, int userSeed, string mechanicTag) { if (positions == null) { throw new ArgumentNullException("positions"); } int count = positions.Count; bool[] array = new bool[count]; if (budget <= 0 || count == 0) { return array; } if (budget > count) { budget = count; } long[] nearestDistSq = new long[count]; int[] array2 = new int[count]; for (int i = 0; i < count; i++) { GridPos gridPos = positions[i]; long num = long.MaxValue; int num2 = -1; uint num3 = 0u; for (int j = 0; j < count; j++) { if (i == j) { continue; } GridPos gridPos2 = positions[j]; long num4 = gridPos.X - gridPos2.X; long num5 = gridPos.Y - gridPos2.Y; long num6 = gridPos.Z - gridPos2.Z; long num7 = num4 * num4 + num5 * num5 + num6 * num6; if (num7 > num) { continue; } if (num7 < num) { num = num7; num2 = j; num3 = DeterministicHash.RankKey(userSeed, mechanicTag, gridPos2); continue; } uint num8 = DeterministicHash.RankKey(userSeed, mechanicTag, gridPos2); if (num8 < num3 || (num8 == num3 && ComparePos(gridPos2, positions[num2]) < 0)) { num2 = j; num3 = num8; } } nearestDistSq[i] = num; array2[i] = num2; } int[] array3 = new int[count]; for (int k = 0; k < count; k++) { array3[k] = k; } Array.Sort(array3, delegate(int a, int b) { int num15 = nearestDistSq[a].CompareTo(nearestDistSq[b]); if (num15 != 0) { return num15; } GridPos gridPos3 = positions[a]; GridPos gridPos4 = positions[b]; uint num16 = DeterministicHash.RankKey(userSeed, mechanicTag, gridPos3); uint num17 = DeterministicHash.RankKey(userSeed, mechanicTag, gridPos4); return (num16 != num17) ? ((num16 >= num17) ? 1 : (-1)) : ComparePos(gridPos3, gridPos4); }); bool[] array4 = new bool[count]; int num9 = 0; for (int num10 = 0; num10 < count; num10++) { if (num9 >= budget) { break; } int num11 = array3[num10]; if (!array4[num11]) { array[num11] = true; num9++; int num12 = array2[num11]; if (num12 >= 0 && !array[num12]) { array4[num12] = true; } } } if (num9 < budget) { for (int num13 = 0; num13 < count; num13++) { if (num9 >= budget) { break; } int num14 = array3[num13]; if (!array[num14]) { array[num14] = true; num9++; } } } return array; } public static int RemovalBudget(int total, double fraction) { if (total <= 0) { return 0; } double num = ((fraction < 0.0) ? 0.0 : ((fraction > 1.0) ? 1.0 : fraction)); int num2 = (int)Math.Floor((double)total * (1.0 - num) + 1E-09); return total - num2; } internal static int ComparePos(GridPos a, GridPos b) { if (a.X != b.X) { if (a.X >= b.X) { return 1; } return -1; } if (a.Y != b.Y) { if (a.Y >= b.Y) { return 1; } return -1; } if (a.Z != b.Z) { if (a.Z >= b.Z) { return 1; } return -1; } return 0; } } public static class ConfigDefaults { public const double SporeBombCullFraction = 0.0; public const bool EnableFoliageRemoval = false; public const double SporeBombTriggerRadiusMultiplier = 1.0; public const double SporeBombKnockbackMultiplier = 1.0; public const double SporeBombScreenshakeRangeCapMeters = 0.0; public const double SporeBombVfxCountMultiplier = 1.0; public const double SporeBombTriggerHeightMultiplier = 1.0; public const double SporeBombSporeAreaRadiusMultiplier = 1.0; public const bool CoverMouthBlocksSporeBombs = false; public const bool DisableSporeAreas = false; public const double SporeAreaRemovalFraction = 0.0; public const double SporeAreaRadiusMultiplier = 1.0; public const double SporeAreaStatusRateMultiplier = 1.0; public const bool EnableCoverMouth = false; public const float CoverMouthStaminaPerSecond = 0.03f; public const double SporeClearTimeMultiplier = 1.0; public const double SporeBuildUpMultiplier = 1.0; public const bool DisableZombies = false; public const bool DisableBeetles = false; public const bool DisableSpiders = false; public const double ZombieSpeedMultiplier = 1.0; public const double BeetleSpeedMultiplier = 1.0; public const double BeetleKnockbackMultiplier = 1.0; public const double CreatureRagdollMultiplier = 1.0; public const bool ZombieDeaggroEnabled = false; public const double ZombieDeaggroMultiplier = 1.0; public const double BeetleDeaggroMultiplier = 1.0; public const double ZombieKnockoutSeconds = 0.0; public const double BeetleKnockoutSeconds = 0.0; public const double CreatureKnockoutMinThrowSpeed = 36.0; public const double CreatureKnockoutMaxThrowDistance = 12.0; public const bool BlowgunAffectsCreatures = false; public const double BlowgunCreatureStunSeconds = 45.0; public const double ZombieWindMultiplier = 1.0; public const double BeetleWindSusceptibility = 0.0; public const bool DisableWindEntirely = false; public const bool WindBackpackAlwaysImmune = false; public const double WindForceMultiplier = 1.0; public const double WindGustDurationMultiplier = 1.0; public const double WindItemForceMultiplier = 1.0; public const double WindObstacleOcclusionRangeMultiplier = 1.0; public const double WindFallCameraDampenClamp = 0.0; public const float WindRecentForceWindowSeconds = 1.5f; public const bool PreventWindRagdoll = false; public const bool ClimbSheltersFromWind = false; public const double ClimbWindSpeedMultiplier = 1.0; public const double ClimbWindUpwardSpeedMultiplier = 1.0; public const double ClimbWindIntoWindSpeedMultiplier = 1.0; public const float ClimbShelterGraceSeconds = 0.5f; public const double ClimbWindGraceForceMultiplier = 1.0; } public static class CoverMouth { public static bool NextState(bool covering, bool keyPressedThisFrame, bool keyHeld, bool holdMode, bool allowed) { if (!allowed) { return false; } if (holdMode) { return keyHeld; } if (!keyPressedThisFrame) { return covering; } return !covering; } public static float StaminaCost(double ratePerSecond, float deltaSeconds) { if (ratePerSecond <= 0.0 || deltaSeconds <= 0f) { return 0f; } return (float)(ratePerSecond * (double)deltaSeconds); } } public static class CreatureKnockout { public const float SpiderStunSeconds = 5f; public const float MaxSeconds = 60f; public const float VanillaBonkableThresholdUnits = 5f; public const double MaxMinThrowSpeedMeters = 50.0; public const double MaxThrowDistanceMeters = 200.0; public const double MaxBlowgunStunSeconds = 600.0; public const double CalibratedMinThrowSpeedMeters = 36.0; public static float ResolveBlowgunStunSeconds(double configuredSeconds) { if (double.IsNaN(configuredSeconds)) { return 0f; } return (float)Math.Min(600.0, Math.Max(0.0, configuredSeconds)); } public static float ResolveMinThrowSpeedMeters(double configuredMetersPerSecond) { if (double.IsNaN(configuredMetersPerSecond)) { return 0f; } return (float)Math.Min(50.0, Math.Max(0.0, configuredMetersPerSecond)); } public static float ResolveSeconds(double configuredSeconds) { if (double.IsNaN(configuredSeconds)) { return 0f; } return (float)Math.Min(60.0, Math.Max(0.0, configuredSeconds)); } public static bool IsDisabled(double configuredSeconds) { return ResolveSeconds(configuredSeconds) <= 0f; } public static bool IsHardEnough(float impactSpeedUnits, float thresholdUnits) { return impactSpeedUnits >= thresholdUnits; } public static float ResolveMaxThrowDistanceMeters(double configuredMeters) { if (double.IsNaN(configuredMeters)) { return 0f; } return (float)Math.Min(200.0, Math.Max(0.0, configuredMeters)); } public static bool IsCloseEnough(float distanceUnits, float thresholdUnits) { if (!(thresholdUnits <= 0f)) { return distanceUnits <= thresholdUnits; } return true; } } public static class CreatureTuning { public static float ScaleMovementSpeed(float vanillaSpeed, double multiplier) { return (float)((double)vanillaSpeed * Math.Max(0.0, multiplier)); } public static float ScaleKnockback(float vanillaForce, double multiplier) { return (float)((double)vanillaForce * Math.Max(0.0, multiplier)); } public static float ScaleRagdollTime(float vanillaSeconds, double multiplier) { return (float)((double)vanillaSeconds * Math.Max(0.0, multiplier)); } public static float ScaleDeaggroDistance(float vanillaAggroDistance, double multiplier) { return (float)((double)vanillaAggroDistance * Math.Max(0.0, multiplier)); } public static bool IsVanilla(double multiplier) { return Math.Abs(multiplier - 1.0) < 1E-06; } } public static class CreatureWind { public const float VanillaBotWindShare = 0.6f; public const double MaxMultiplier = 5.0; public static double ResolveZombieMultiplier(double configured) { if (double.IsNaN(configured)) { return 1.0; } return Math.Min(5.0, Math.Max(0.0, configured)); } public static double ResolveBeetleSusceptibility(double configured) { if (double.IsNaN(configured)) { return 0.0; } return Math.Min(5.0, Math.Max(0.0, configured)); } public static bool IsVanillaZombieWind(double configured) { return Math.Abs(ResolveZombieMultiplier(configured) - 1.0) < 1E-06; } public static float BeetleDriftSpeed(float movementSpeed, double susceptibility) { return (float)((double)Math.Max(0f, movementSpeed) * ResolveBeetleSusceptibility(susceptibility)); } } public static class DeterministicHash { private const uint FnvOffsetBasis = 2166136261u; private const uint FnvPrime = 16777619u; public static uint Hash(int userSeed, string mechanicTag, GridPos pos) { return Finalize(MixInt(MixInt(MixInt(MixString(MixInt(2166136261u, userSeed), mechanicTag), pos.X), pos.Y), pos.Z)); } public static double UnitValue(int userSeed, string mechanicTag, GridPos pos) { return (double)(Hash(userSeed, mechanicTag, pos) >> 8) * 5.960464477539063E-08; } public static uint RankKey(int userSeed, string mechanicTag, GridPos pos) { return Hash(userSeed, mechanicTag, pos); } private static uint MixInt(uint h, int value) { h = (h ^ (uint)(value & 0xFF)) * 16777619; h = (h ^ (uint)((value >>> 8) & 0xFF)) * 16777619; h = (h ^ (uint)((value >>> 16) & 0xFF)) * 16777619; h = (h ^ (uint)((value >>> 24) & 0xFF)) * 16777619; return h; } private static uint MixString(uint h, string s) { h = MixInt(h, s?.Length ?? (-1)); if (s == null) { return h; } foreach (ushort num in s) { h = (h ^ (uint)(num & 0xFF)) * 16777619; h = (h ^ (uint)((num >> 8) & 0xFF)) * 16777619; } return h; } private static uint Finalize(uint h) { h ^= h >> 16; h *= 2246822507u; h ^= h >> 13; h *= 3266489909u; h ^= h >> 16; return h; } } public readonly struct GridPos { public readonly int X; public readonly int Y; public readonly int Z; public GridPos(int x, int y, int z) { X = x; Y = y; Z = z; } public static GridPos Round(float x, float y, float z) { return new GridPos(RoundToInt(x), RoundToInt(y), RoundToInt(z)); } private static int RoundToInt(float value) { return (int)Math.Round(value, MidpointRounding.AwayFromZero); } public override string ToString() { return $"({X}, {Y}, {Z})"; } } public static class LabelColors { public const double DefaultOutlineDarkening = 0.7; public static Rgb Outline(Rgb foreground, double amount = 0.7) { (double, double, double) tuple = SporeBombRecolor.ToHsv(foreground); double num = 1.0 - amount; if (num < 0.0) { num = 0.0; } else if (num > 1.0) { num = 1.0; } return SporeBombRecolor.FromHsv(tuple.Item1, tuple.Item2, tuple.Item3 * num); } } public static class SporeAreaCull { public const string MechanicTag = "spore-area-cull"; public static bool[] Decide(IReadOnlyList positions, double removalFraction, int userSeed, string mechanicTag = "spore-area-cull") { if (positions == null) { throw new ArgumentNullException("positions"); } int budget = ClusteredRemovalSelection.RemovalBudget(positions.Count, removalFraction); return ClusteredRemovalSelection.Select(positions, budget, userSeed, mechanicTag); } public static int CountRemoved(IReadOnlyList flags) { if (flags == null) { throw new ArgumentNullException("flags"); } int num = 0; for (int i = 0; i < flags.Count; i++) { if (flags[i]) { num++; } } return num; } } public static class SporeAreaTuning { public const float MinVisualScale = 0.01f; public static float ScaleRadius(float vanillaRadius, double multiplier) { float num = (float)((double)vanillaRadius * multiplier); if (!(num < 0f)) { return num; } return 0f; } public static float ScaleFade(float vanillaFade, double multiplier) { float num = (float)((double)vanillaFade * multiplier); if (!(num < 0f)) { return num; } return 0f; } public static float ScaleVisual(double multiplier) { float num = (float)multiplier; if (!(num < 0.01f)) { return num; } return 0.01f; } public static float ScaleStatusRate(float vanillaAmount, double multiplier) { float num = (float)((double)vanillaAmount * multiplier); if (!(num < 0f)) { return num; } return 0f; } } public static class SporeBombCloudPresence { public static bool IsInsideStatusRange(double distance, double range, double minFactor, double factorPow) { if (range <= 0.0 || distance < 0.0 || distance > range) { return false; } return Factor(distance, range, factorPow) >= minFactor; } public static double Factor(double distance, double range, double factorPow) { if (range <= 0.0) { return 0.0; } double num = 1.0 - distance / range; if (num <= 0.0) { return 0.0; } if (!(factorPow <= 0.0)) { return Math.Pow(num, factorPow); } return num; } } public enum CullOutcome { Kept, RemovedFoliage, RemovedSeededCull } public readonly struct CullSummary { public readonly int Total; public readonly int FoliageRemoved; public readonly int SeededRemoved; public readonly int Kept; public int TotalRemoved => FoliageRemoved + SeededRemoved; public CullSummary(int total, int foliageRemoved, int seededRemoved, int kept) { Total = total; FoliageRemoved = foliageRemoved; SeededRemoved = seededRemoved; Kept = kept; } } public static class SporeBombCull { public const string MechanicTag = "spore-bomb-cull"; public static CullOutcome[] Decide(IReadOnlyList positions, IReadOnlyList inFoliage, double cullFraction, int userSeed, bool foliageRemovalEnabled = true, string mechanicTag = "spore-bomb-cull") { if (positions == null) { throw new ArgumentNullException("positions"); } if (inFoliage == null) { throw new ArgumentNullException("inFoliage"); } if (positions.Count != inFoliage.Count) { throw new ArgumentException("positions and inFoliage must be the same length (parallel arrays)."); } int count = positions.Count; CullOutcome[] array = new CullOutcome[count]; int num = 0; List list = new List(count); for (int i = 0; i < count; i++) { if (foliageRemovalEnabled && inFoliage[i]) { array[i] = CullOutcome.RemovedFoliage; num++; } else { array[i] = CullOutcome.Kept; list.Add(i); } } int num2 = ClusteredRemovalSelection.RemovalBudget(count, cullFraction) - num; if (num2 < 0) { num2 = 0; } if (num2 > list.Count) { num2 = list.Count; } if (num2 > 0) { GridPos[] array2 = new GridPos[list.Count]; for (int j = 0; j < list.Count; j++) { array2[j] = positions[list[j]]; } bool[] array3 = ClusteredRemovalSelection.Select(array2, num2, userSeed, mechanicTag); for (int k = 0; k < array3.Length; k++) { if (array3[k]) { array[list[k]] = CullOutcome.RemovedSeededCull; } } } return array; } public static CullSummary Summarize(IReadOnlyList outcomes) { if (outcomes == null) { throw new ArgumentNullException("outcomes"); } int num = 0; int num2 = 0; int num3 = 0; for (int i = 0; i < outcomes.Count; i++) { switch (outcomes[i]) { case CullOutcome.RemovedFoliage: num++; break; case CullOutcome.RemovedSeededCull: num2++; break; default: num3++; break; } } return new CullSummary(outcomes.Count, num, num2, num3); } } public static class SporeBombExplosionTuning { public const float NoScreenshakeCap = 0f; public const float DetonationScreenshakeWindowSeconds = 4f; public const float DetonationScreenshakeRadiusMeters = 20f; public const float TriggerHeightBaselineMeters = 2.8f; public static float ScaleTriggerRadius(float vanillaRadius, double multiplier) { return (float)((double)vanillaRadius * multiplier); } public static float ScaleKnockback(float vanillaKnockback, double multiplier) { return (float)((double)vanillaKnockback * multiplier); } public static int ScaleVfxCount(int vanillaCount, double multiplier) { int num = (int)Math.Round((double)vanillaCount * multiplier, MidpointRounding.AwayFromZero); if (num >= 0) { return num; } return 0; } public static float CapScreenshakeRange(float vanillaRange, float capMeters) { if (capMeters <= 0f) { return vanillaRange; } if (!(vanillaRange < capMeters)) { return capMeters; } return vanillaRange; } public static bool ShouldForcePositionalScreenshake(float capMeters) { return capMeters > 0f; } public static float ResolveScreenshakeRange(float vanillaRange, bool vanillaPositional, float capUnits) { if (capUnits <= 0f) { return vanillaRange; } if (!vanillaPositional) { return capUnits; } return CapScreenshakeRange(vanillaRange, capUnits); } public static bool IsDetonationScreenshake(float ageSeconds, float distanceMeters) { if (ageSeconds >= 0f && ageSeconds <= 4f) { return distanceMeters <= 20f; } return false; } public static float ScaleSporeAreaRadius(float vanillaValue, double multiplier) { float num = (float)((double)vanillaValue * multiplier); if (!(num < 0f)) { return num; } return 0f; } public static float ResolveTriggerHeightCutoffMeters(double multiplier) { if (!(multiplier >= 1.0)) { return (float)(multiplier * 2.799999952316284); } return 0f; } public static bool ShouldSuppressTriggerForHeight(float heightAboveBase, float maxTriggerHeightMeters) { if (maxTriggerHeightMeters <= 0f) { return false; } return heightAboveBase > maxTriggerHeightMeters; } } public readonly struct Rgb { public static readonly Rgb White = new Rgb(1.0, 1.0, 1.0); public double R { get; } public double G { get; } public double B { get; } public Rgb(double r, double g, double b) { R = r; G = g; B = b; } public override string ToString() { return $"({R:0.###}, {G:0.###}, {B:0.###})"; } } public static class SporeBombRecolor { public const double SaturationBlend = 0.8; public static readonly Rgb FallbackSporeColor = new Rgb(0.93, 0.24, 0.62); public static double Luminance(Rgb c) { return 0.2126 * c.R + 0.7152 * c.G + 0.0722 * c.B; } public static double Saturation(Rgb c) { return ToHsv(c).S; } public static Rgb Recolor(Rgb original, Rgb sporeColor, double saturationBlend) { (double, double, double) tuple = ToHsv(sporeColor); (double, double, double) tuple2 = ToHsv(original); if (tuple.Item3 <= 0.0 || tuple2.Item3 <= 0.0) { return original; } double num = ((saturationBlend < 0.0) ? 0.0 : ((saturationBlend > 1.0) ? 1.0 : saturationBlend)); double s = tuple2.Item2 + (tuple.Item2 - tuple2.Item2) * num; return MatchLuminance(FromHsv(tuple.Item1, s, tuple2.Item3), original); } public static Rgb MatchLuminance(Rgb color, Rgb reference) { double num = Luminance(reference); double num2 = Luminance(color); if (num2 <= 0.0 || num <= 0.0) { return color; } double num3 = num / num2; double num4 = Math.Max(color.R, Math.Max(color.G, color.B)); double num5 = Math.Max(1.0, Math.Max(reference.R, Math.Max(reference.G, reference.B))); if (num4 > 0.0 && num4 * num3 > num5) { num3 = num5 / num4; } return new Rgb(color.R * num3, color.G * num3, color.B * num3); } public static (double H, double S, double V) ToHsv(Rgb c) { double num = Math.Max(c.R, Math.Max(c.G, c.B)); double num2 = Math.Min(c.R, Math.Min(c.G, c.B)); double num3 = num - num2; if (num <= 0.0) { return (H: 0.0, S: 0.0, V: num); } double item = num3 / num; if (num3 <= 0.0) { return (H: 0.0, S: 0.0, V: num); } double num4 = ((num == c.R) ? (60.0 * ((c.G - c.B) / num3 % 6.0)) : ((num != c.G) ? (60.0 * ((c.R - c.G) / num3 + 4.0)) : (60.0 * ((c.B - c.R) / num3 + 2.0)))); if (num4 < 0.0) { num4 += 360.0; } return (H: num4, S: item, V: num); } public static Rgb FromHsv(double h, double s, double v) { s = ((s < 0.0) ? 0.0 : ((s > 1.0) ? 1.0 : s)); if (s <= 0.0) { return new Rgb(v, v, v); } h %= 360.0; if (h < 0.0) { h += 360.0; } double num = v * s; double num2 = h / 60.0; double num3 = num * (1.0 - Math.Abs(num2 % 2.0 - 1.0)); double num4 = v - num; double num5; double num6; double num7; if (num2 < 1.0) { num5 = num; num6 = num3; num7 = 0.0; } else if (num2 < 2.0) { num5 = num3; num6 = num; num7 = 0.0; } else if (num2 < 3.0) { num5 = 0.0; num6 = num; num7 = num3; } else if (num2 < 4.0) { num5 = 0.0; num6 = num3; num7 = num; } else if (num2 < 5.0) { num5 = num3; num6 = 0.0; num7 = num; } else { num5 = num; num6 = 0.0; num7 = num3; } return new Rgb(num5 + num4, num6 + num4, num7 + num4); } } public static class SporeCloudOpacity { public const double Vanilla = 1.0; public static bool IsVanilla(double multiplier) { return multiplier >= 1.0; } public static float ScaleAlpha(float vanillaAlpha, double multiplier) { double num = (double)vanillaAlpha * multiplier; if (num <= 0.0) { return 0f; } if (!(num >= 1.0)) { return (float)num; } return 1f; } } public static class SporeStatusTuning { public const double MinClearTimeMultiplier = 0.05; public static bool IsVanilla(double multiplier) { return Math.Abs(multiplier - 1.0) < 1E-06; } public static float ScaleDecayRate(float vanillaPerSecond, double clearTimeMultiplier) { if (vanillaPerSecond <= 0f) { return vanillaPerSecond; } double num = ClampClearTime(clearTimeMultiplier); return (float)((double)vanillaPerSecond / num); } public static float ScaleDecayCooldown(float vanillaCooldown, double clearTimeMultiplier) { if (vanillaCooldown <= 0f) { return vanillaCooldown; } float num = (float)((double)vanillaCooldown * ClampClearTime(clearTimeMultiplier)); if (!(num < 0f)) { return num; } return 0f; } public static double SecondsToClear(float vanillaCooldown, float vanillaPerSecond, float status, double clearTimeMultiplier) { float num = ScaleDecayRate(vanillaPerSecond, clearTimeMultiplier); if (num <= 0f) { return double.PositiveInfinity; } return (double)ScaleDecayCooldown(vanillaCooldown, clearTimeMultiplier) + (double)status / (double)num; } public static float ScaleBuildUp(float amount, double multiplier) { if (amount <= 0f) { return amount; } float num = (float)((double)amount * multiplier); if (!(num < 0f)) { return num; } return 0f; } private static double ClampClearTime(double multiplier) { if (!(multiplier < 0.05)) { return multiplier; } return 0.05; } } public static class WindTuning { public const float MinWindActiveDurationSeconds = 1f; public const float FullRagdollControl = 1f; public static float ScaleWindForce(float vanillaForce, double multiplier) { return (float)((double)vanillaForce * multiplier); } public static float ScaleWindActiveDuration(float vanillaSeconds, double multiplier) { float num = (float)((double)vanillaSeconds * multiplier); if (!(num < 1f)) { return num; } return 1f; } public static float ScaleWindRestDuration(float vanillaSeconds, double multiplier) { if (multiplier <= 0.0) { return vanillaSeconds; } return (float)((double)vanillaSeconds / multiplier); } public static float ScaleItemForceFactor(float vanillaFactor, double multiplier) { float num = (float)((double)vanillaFactor * multiplier); if (!(num < 0f)) { return num; } return 0f; } public static float ScaleRaycastDistance(float vanillaDistance, double multiplier) { float num = (float)((double)vanillaDistance * multiplier); if (!(num < 0f)) { return num; } return 0f; } public static bool IsWindForceStillRecent(float lastWindForceTime, float currentTime, float windowSeconds) { if (lastWindForceTime < 0f) { return false; } return currentTime - lastWindForceTime <= windowSeconds; } public static float ApplyWindRagdollImmunity(float vanillaTargetRagdollControl, bool fallIsWindPreceded, bool immunityEnabled) { if (!immunityEnabled || !fallIsWindPreceded) { return vanillaTargetRagdollControl; } return Math.Max(vanillaTargetRagdollControl, 1f); } public static float ApplyFallCameraDampening(float vanillaTargetRagdollControl, bool fallIsWindPreceded, float dampenClampValue) { if (!fallIsWindPreceded || dampenClampValue <= 0f) { return vanillaTargetRagdollControl; } return Math.Max(vanillaTargetRagdollControl, dampenClampValue); } } public static class WorldUnits { public const float DefaultUnitsToMeters = 1.6f; public static float SafeFactor(float unitsToMeters) { if (!(unitsToMeters > 0f)) { return 1.6f; } return unitsToMeters; } public static float MetersToUnits(float meters, float unitsToMeters) { return meters / SafeFactor(unitsToMeters); } public static float UnitsToMeters(float units, float unitsToMeters) { return units * SafeFactor(unitsToMeters); } } public static class ZombieDeaggro { public const double MinMultiplier = 0.1; public const double MaxMultiplier = 1.0; public const float BaseSightLossSeconds = 30f; public const float BaseDistanceWorldUnits = 75f; public static double ClampMultiplier(double multiplier) { if (double.IsNaN(multiplier)) { return 1.0; } return Math.Min(1.0, Math.Max(0.1, multiplier)); } public static float ResolveSightLossSeconds(double multiplier) { return (float)(30.0 * ClampMultiplier(multiplier)); } public static float ResolveDistanceWorldUnits(double multiplier) { return (float)(75.0 * ClampMultiplier(multiplier)); } public static bool ShouldDeaggroForSightLoss(float secondsOutOfSight, double multiplier) { return secondsOutOfSight > ResolveSightLossSeconds(multiplier); } public static bool ShouldDeaggroForDistance(float distanceWorldUnits, double multiplier) { return distanceWorldUnits > ResolveDistanceWorldUnits(multiplier); } } } namespace Fairoots.Core.Presets { public static class OverrideResolution { public static T Resolve(T presetValue, T configuredValue, T defaultValue, PresetId preset, bool applyPurePreset) { if (preset == PresetId.Custom) { return configuredValue; } if (applyPurePreset) { return presetValue; } if (!EqualityComparer.Default.Equals(configuredValue, defaultValue)) { return configuredValue; } return presetValue; } } public static class PresetCatalog { private static PresetId CatalogKey(PresetId preset) { if (preset != PresetId.Custom) { return preset; } return PresetId.Balanced; } public static double SporeBombCullFraction(PresetId preset) { return PresetValues.SporeBombCullFraction(CatalogKey(preset)); } public static double SporeBombTriggerRadiusMultiplier(PresetId preset) { return PresetValues.SporeBombTriggerRadiusMultiplier(CatalogKey(preset)); } public static double SporeBombKnockbackMultiplier(PresetId preset) { return PresetValues.SporeBombKnockbackMultiplier(CatalogKey(preset)); } public static double SporeBombScreenshakeRangeCapMeters(PresetId preset) { return PresetValues.SporeBombScreenshakeRangeCapMeters(CatalogKey(preset)); } public static double SporeBombVfxCountMultiplier(PresetId preset) { return PresetValues.SporeBombVfxCountMultiplier(CatalogKey(preset)); } public static double SporeBombTriggerHeightMultiplier(PresetId preset) { return PresetValues.SporeBombTriggerHeightMultiplier(CatalogKey(preset)); } public static double SporeBombSporeAreaRadiusMultiplier(PresetId preset) { return PresetValues.SporeBombSporeAreaRadiusMultiplier(CatalogKey(preset)); } public static bool EnableFoliageRemoval(PresetId preset) { return PresetValues.EnableFoliageRemoval(CatalogKey(preset)); } public static bool CoverMouthBlocksSporeBombs(PresetId preset) { return PresetValues.CoverMouthBlocksSporeBombs(CatalogKey(preset)); } public static double SporeAreaRemovalFraction(PresetId preset) { return PresetValues.SporeAreaRemovalFraction(CatalogKey(preset)); } public static double SporeAreaRadiusMultiplier(PresetId preset) { return PresetValues.SporeAreaRadiusMultiplier(CatalogKey(preset)); } public static double SporeAreaStatusRateMultiplier(PresetId preset) { return PresetValues.SporeAreaStatusRateMultiplier(CatalogKey(preset)); } public static bool EnableCoverMouth(PresetId preset) { return PresetValues.EnableCoverMouth(CatalogKey(preset)); } public static float CoverMouthStaminaPerSecond(PresetId preset) { return PresetValues.CoverMouthStaminaPerSecond(CatalogKey(preset)); } public static double SporeClearTimeMultiplier(PresetId preset) { return PresetValues.SporeClearTimeMultiplier(CatalogKey(preset)); } public static double SporeBuildUpMultiplier(PresetId preset) { return PresetValues.SporeBuildUpMultiplier(CatalogKey(preset)); } public static double ZombieSpeedMultiplier(PresetId preset) { return PresetValues.ZombieSpeedMultiplier(CatalogKey(preset)); } public static double BeetleSpeedMultiplier(PresetId preset) { return PresetValues.BeetleSpeedMultiplier(CatalogKey(preset)); } public static double BeetleKnockbackMultiplier(PresetId preset) { return PresetValues.BeetleKnockbackMultiplier(CatalogKey(preset)); } public static double CreatureRagdollMultiplier(PresetId preset) { return PresetValues.CreatureRagdollMultiplier(CatalogKey(preset)); } public static bool ZombieDeaggroEnabled(PresetId preset) { return PresetValues.ZombieDeaggroEnabled(CatalogKey(preset)); } public static double ZombieDeaggroMultiplier(PresetId preset) { return PresetValues.ZombieDeaggroMultiplier(CatalogKey(preset)); } public static double BeetleDeaggroMultiplier(PresetId preset) { return PresetValues.BeetleDeaggroMultiplier(CatalogKey(preset)); } public static double ZombieKnockoutSeconds(PresetId preset) { return PresetValues.ZombieKnockoutSeconds(CatalogKey(preset)); } public static double BeetleKnockoutSeconds(PresetId preset) { return PresetValues.BeetleKnockoutSeconds(CatalogKey(preset)); } public static double CreatureKnockoutMinThrowSpeed(PresetId preset) { return PresetValues.CreatureKnockoutMinThrowSpeed(CatalogKey(preset)); } public static double CreatureKnockoutMaxThrowDistance(PresetId preset) { return PresetValues.CreatureKnockoutMaxThrowDistance(CatalogKey(preset)); } public static bool BlowgunAffectsCreatures(PresetId preset) { return PresetValues.BlowgunAffectsCreatures(CatalogKey(preset)); } public static double BlowgunCreatureStunSeconds(PresetId preset) { return PresetValues.BlowgunCreatureStunSeconds(CatalogKey(preset)); } public static double ZombieWindMultiplier(PresetId preset) { return PresetValues.ZombieWindMultiplier(CatalogKey(preset)); } public static double BeetleWindSusceptibility(PresetId preset) { return PresetValues.BeetleWindSusceptibility(CatalogKey(preset)); } public static double WindForceMultiplier(PresetId preset) { return PresetValues.WindForceMultiplier(CatalogKey(preset)); } public static double WindGustDurationMultiplier(PresetId preset) { return PresetValues.WindGustDurationMultiplier(CatalogKey(preset)); } public static double WindItemForceMultiplier(PresetId preset) { return PresetValues.WindItemForceMultiplier(CatalogKey(preset)); } public static bool WindBackpackAlwaysImmune(PresetId preset) { return PresetValues.WindBackpackAlwaysImmune(CatalogKey(preset)); } public static double WindObstacleOcclusionRangeMultiplier(PresetId preset) { return PresetValues.WindObstacleOcclusionRangeMultiplier(CatalogKey(preset)); } public static double WindFallCameraDampenClamp(PresetId preset) { return PresetValues.WindFallCameraDampenClamp(CatalogKey(preset)); } public static float WindRecentForceWindowSeconds(PresetId preset) { return PresetValues.WindRecentForceWindowSeconds(CatalogKey(preset)); } public static bool PreventWindRagdoll(PresetId preset) { return PresetValues.PreventWindRagdoll(CatalogKey(preset)); } public static bool ClimbSheltersFromWind(PresetId preset) { return PresetValues.ClimbSheltersFromWind(CatalogKey(preset)); } public static double ClimbWindSpeedMultiplier(PresetId preset) { return PresetValues.ClimbWindSpeedMultiplier(CatalogKey(preset)); } public static double ClimbWindUpwardSpeedMultiplier(PresetId preset) { return PresetValues.ClimbWindUpwardSpeedMultiplier(CatalogKey(preset)); } public static double ClimbWindIntoWindSpeedMultiplier(PresetId preset) { return PresetValues.ClimbWindIntoWindSpeedMultiplier(CatalogKey(preset)); } public static float ClimbShelterGraceSeconds(PresetId preset) { return PresetValues.ClimbShelterGraceSeconds(CatalogKey(preset)); } public static double ClimbWindGraceForceMultiplier(PresetId preset) { return PresetValues.ClimbWindGraceForceMultiplier(CatalogKey(preset)); } } public enum PresetId { Subtle = 1, Balanced, Generous, Tame, Custom } public static class PresetValues { private static T Pick(PresetId preset, T subtle, T balanced, T generous, T tame) { return preset switch { PresetId.Subtle => subtle, PresetId.Balanced => balanced, PresetId.Generous => generous, PresetId.Tame => tame, _ => throw new ArgumentOutOfRangeException("preset", preset, "PresetValues is indexed by presets 1-4 only - map Custom first (PresetCatalog does)."), }; } public static double SporeBombCullFraction(PresetId preset) { return Pick(preset, 0.0, 0.05, 0.15, 0.25); } public static bool EnableFoliageRemoval(PresetId preset) { return Pick(preset, subtle: true, balanced: true, generous: true, tame: true); } public static double SporeBombTriggerRadiusMultiplier(PresetId preset) { return Pick(preset, 0.9, 0.75, 0.7, 0.6); } public static double SporeBombKnockbackMultiplier(PresetId preset) { return Pick(preset, 1.0, 0.9, 0.7, 0.5); } public static double SporeBombScreenshakeRangeCapMeters(PresetId preset) { return Pick(preset, 75.0, 75.0, 50.0, 50.0); } public static double SporeBombVfxCountMultiplier(PresetId preset) { return Pick(preset, 1.0, 0.75, 0.5, 0.35); } public static double SporeBombTriggerHeightMultiplier(PresetId preset) { return Pick(preset, 0.9, 0.804, 0.804, 0.804); } public static double SporeBombSporeAreaRadiusMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.0, 1.0, 1.0); } public static bool CoverMouthBlocksSporeBombs(PresetId preset) { return Pick(preset, subtle: false, balanced: false, generous: false, tame: false); } public static double SporeAreaRemovalFraction(PresetId preset) { return Pick(preset, 0.0, 0.0, 0.2, 0.35); } public static double SporeAreaRadiusMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.0, 0.85, 0.7); } public static double SporeAreaStatusRateMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.0, 0.9, 0.8); } public static bool EnableCoverMouth(PresetId preset) { return Pick(preset, subtle: true, balanced: true, generous: true, tame: true); } public static float CoverMouthStaminaPerSecond(PresetId preset) { return Pick(preset, 0.04f, 0.03f, 0.02f, 0.01f); } public static double SporeClearTimeMultiplier(PresetId preset) { return Pick(preset, 1.0, 0.8, 0.75, 0.65); } public static double SporeBuildUpMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.0, 1.0, 1.0); } public static double ZombieSpeedMultiplier(PresetId preset) { return Pick(preset, 1.0, 0.9, 0.8, 0.65); } public static double BeetleSpeedMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.0, 0.9, 0.8); } public static double BeetleKnockbackMultiplier(PresetId preset) { return Pick(preset, 1.0, 0.8, 0.65, 0.5); } public static double CreatureRagdollMultiplier(PresetId preset) { return Pick(preset, 1.0, 0.85, 0.65, 0.4); } public static bool ZombieDeaggroEnabled(PresetId preset) { return Pick(preset, subtle: false, balanced: true, generous: true, tame: true); } public static double ZombieDeaggroMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.0, 0.85, 0.75); } public static double BeetleDeaggroMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.0, 0.9, 0.8); } public static double ZombieKnockoutSeconds(PresetId preset) { return Pick(preset, 1.0, 2.0, 3.0, 4.0); } public static double BeetleKnockoutSeconds(PresetId preset) { return Pick(preset, 2.0, 3.0, 4.0, 4.0); } public static double CreatureKnockoutMinThrowSpeed(PresetId preset) { return Pick(preset, 38.0, 36.0, 34.0, 32.0); } public static double CreatureKnockoutMaxThrowDistance(PresetId preset) { return Pick(preset, 10.0, 12.0, 13.0, 15.0); } public static bool BlowgunAffectsCreatures(PresetId preset) { return Pick(preset, subtle: true, balanced: true, generous: true, tame: true); } public static double BlowgunCreatureStunSeconds(PresetId preset) { return Pick(preset, 30.0, 45.0, 60.0, 60.0); } public static double ZombieWindMultiplier(PresetId preset) { return Pick(preset, 1.2, 1.4, 1.5, 1.6); } public static double BeetleWindSusceptibility(PresetId preset) { return Pick(preset, 0.25, 0.4, 0.6, 0.8); } public static bool WindBackpackAlwaysImmune(PresetId preset) { return Pick(preset, subtle: true, balanced: true, generous: true, tame: true); } public static double WindForceMultiplier(PresetId preset) { return Pick(preset, 0.95, 0.95, 0.9, 0.85); } public static double WindGustDurationMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.0, 0.9, 0.9); } public static double WindItemForceMultiplier(PresetId preset) { return Pick(preset, 0.7, 0.6, 0.4, 0.2); } public static double WindObstacleOcclusionRangeMultiplier(PresetId preset) { return Pick(preset, 1.0, 1.2, 1.3, 1.5); } public static double WindFallCameraDampenClamp(PresetId preset) { return Pick(preset, 0.2, 0.35, 0.55, 0.75); } public static float WindRecentForceWindowSeconds(PresetId preset) { return Pick(preset, 1f, 1.5f, 2f, 2.5f); } public static bool PreventWindRagdoll(PresetId preset) { return Pick(preset, subtle: true, balanced: true, generous: true, tame: true); } public static bool ClimbSheltersFromWind(PresetId preset) { return Pick(preset, subtle: false, balanced: true, generous: true, tame: true); } public static double ClimbWindSpeedMultiplier(PresetId preset) { return Pick(preset, 0.85, 0.9, 0.93, 0.96); } public static double ClimbWindUpwardSpeedMultiplier(PresetId preset) { return Pick(preset, 0.8, 0.85, 0.89, 0.94); } public static double ClimbWindIntoWindSpeedMultiplier(PresetId preset) { return Pick(preset, 0.8, 0.85, 0.89, 0.94); } public static float ClimbShelterGraceSeconds(PresetId preset) { return Pick(preset, 0.4f, 0.5f, 0.5f, 0.6f); } public static double ClimbWindGraceForceMultiplier(PresetId preset) { return Pick(preset, 0.25, 0.15, 0.12, 0.1); } } }