Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Treadwell v1.0.1
plugins/Treadwell/Treadwell.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using Treadwell.Core; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("JStack424")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Treadwell Valheim plugin.")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+1948087b100c762de2d66d7a53312765ce279bc1")] [assembly: AssemblyProduct("Treadwell")] [assembly: AssemblyTitle("Treadwell")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/JStack424/Treadwell")] [assembly: AssemblyVersion("1.0.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Treadwell { internal sealed class CompatibilityResult { internal bool IsCompatible { get; } internal string Reason { get; } internal CompatibilityResult(bool isCompatible, string reason) { IsCompatible = isCompatible; Reason = reason; } } internal static class CompatibilityGate { internal static CompatibilityResult Evaluate(FeatureHost features) { List<string> list = new List<string>(); try { features.ValidateCompatibility(list); } catch (Exception ex) { list.Add("feature compatibility validation threw: " + ex.GetType().Name); } if (list.Count != 0) { return new CompatibilityResult(isCompatible: false, string.Join("; ", list) + " (" + RuntimeDiagnostics() + ")"); } return new CompatibilityResult(isCompatible: true, "runtime contract verified (" + RuntimeDiagnostics() + ")"); } internal static void RequireMethod(ICollection<string> failures, Type declaringType, string name, Type returnType, BindingFlags flags, Type[] parameterTypes, Func<MethodInfo, bool> additionalCheck = null) { MethodInfo[] array; try { array = ExactRuntimeContract.FindMethods(declaringType, name, flags, returnType, parameterTypes, additionalCheck); } catch (Exception ex) { failures.Add(declaringType.Name + "." + name + " contract inspection threw: " + ex.GetType().Name); return; } if (array.Length != 1) { failures.Add(declaringType.Name + "." + name + " requires one exact runtime signature; found " + array.Length); } } internal static void RequireConstructor(ICollection<string> failures, Type declaringType, BindingFlags flags, Type[] parameterTypes) { ConstructorInfo[] array; try { array = ExactRuntimeContract.FindConstructors(declaringType, flags, parameterTypes); } catch (Exception ex) { failures.Add(declaringType.Name + " constructor inspection threw: " + ex.GetType().Name); return; } if (array.Length != 1) { failures.Add(declaringType.Name + " requires one exact runtime constructor; found " + array.Length); } } internal static void RequireMethodNamedReturn(ICollection<string> failures, Type declaringType, string name, string returnTypeFullName, BindingFlags flags, Type[] parameterTypes) { MethodInfo[] array; try { array = (from method in declaringType.GetMethods(flags) where string.Equals(method.Name, name, StringComparison.Ordinal) where string.Equals(method.ReturnType.FullName, returnTypeFullName, StringComparison.Ordinal) where ParametersMatch(method.GetParameters(), parameterTypes) select method).ToArray(); } catch (Exception ex) { failures.Add(declaringType.Name + "." + name + " contract inspection threw: " + ex.GetType().Name); return; } if (array.Length != 1) { failures.Add(declaringType.Name + "." + name + " requires one exact runtime signature; found " + array.Length); } } internal static void RequirePatchMethod(ICollection<string> failures, Type declaringType, string name, Type[] parameterTypes) { RequirePatchMethod(failures, declaringType, name, typeof(void), parameterTypes); } internal static void RequirePatchMethod(ICollection<string> failures, Type declaringType, string name, Type returnType, Type[] parameterTypes) { RequireMethod(failures, declaringType, name, returnType, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.NonPublic, parameterTypes, (MethodInfo method) => method.IsStatic); } internal static void RequireField(ICollection<string> failures, Type declaringType, string name, Type fieldType, BindingFlags flags) { FieldInfo[] array; try { array = ExactRuntimeContract.FindFields(declaringType, name, flags | BindingFlags.DeclaredOnly, fieldType); } catch (Exception ex) { failures.Add(declaringType.Name + "." + name + " field inspection threw: " + ex.GetType().Name); return; } if (array.Length != 1) { failures.Add(declaringType.Name + "." + name + " requires one exact runtime field; found " + array.Length); } } internal static void RequireProperty(ICollection<string> failures, Type declaringType, string name, Type propertyType, BindingFlags flags, bool requireGetter, bool requireSetter) { PropertyInfo[] array; try { array = (from property in ExactRuntimeContract.FindProperties(declaringType, name, flags, propertyType) where property.GetIndexParameters().Length == 0 where !requireGetter || property.GetGetMethod(nonPublic: true) != null where !requireSetter || property.GetSetMethod(nonPublic: true) != null select property).ToArray(); } catch (Exception ex) { failures.Add(declaringType.Name + "." + name + " property inspection threw: " + ex.GetType().Name); return; } if (array.Length != 1) { failures.Add(declaringType.Name + "." + name + " requires one exact runtime property; found " + array.Length); } } internal static void RequireGenericMethod(ICollection<string> failures, Type declaringType, string name, BindingFlags flags, Type[] parameterTypes, bool returnsArray) { MethodInfo[] array; try { array = (from method in declaringType.GetMethods(flags) where string.Equals(method.Name, name, StringComparison.Ordinal) where method.IsGenericMethodDefinition && method.GetGenericArguments().Length == 1 where ParametersMatch(method.GetParameters(), parameterTypes) where GenericReturnMatches(method.ReturnType, returnsArray) select method).ToArray(); } catch (Exception ex) { failures.Add(declaringType.Name + "." + name + " generic contract inspection threw: " + ex.GetType().Name); return; } if (array.Length != 1) { failures.Add(declaringType.Name + "." + name + " requires one exact generic runtime signature; found " + array.Length); } } internal static void RequireEnumValue(ICollection<string> failures, Type enumType, string name, int expectedValue) { try { if (!enumType.IsEnum || !Enum.IsDefined(enumType, name) || Convert.ToInt32(Enum.Parse(enumType, name)) != expectedValue) { failures.Add(enumType.Name + "." + name + " enum value is incompatible"); } } catch (Exception ex) { failures.Add(enumType.Name + "." + name + " enum inspection threw: " + ex.GetType().Name); } } internal static void RequireColor(ICollection<string> failures, string label, Color observed, float red, float green, float blue, float alpha) { //IL_0000: 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_0029: 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) if (Math.Abs(observed.r - red) > 0.0001f || Math.Abs(observed.g - green) > 0.0001f || Math.Abs(observed.b - blue) > 0.0001f || Math.Abs(observed.a - alpha) > 0.0001f) { failures.Add(label + " encoding is incompatible"); } } private static bool ParametersMatch(ParameterInfo[] observed, Type[] expected) { if (observed.Length != expected.Length) { return false; } for (int i = 0; i < observed.Length; i++) { if (observed[i].ParameterType != expected[i]) { return false; } } return true; } private static bool GenericReturnMatches(Type returnType, bool returnsArray) { if (!returnsArray) { if (returnType.IsGenericParameter) { return returnType.GenericParameterPosition == 0; } return false; } Type elementType = returnType.GetElementType(); if (returnType.IsArray && elementType != null && elementType.IsGenericParameter) { return elementType.GenericParameterPosition == 0; } return false; } private static string RuntimeDiagnostics() { string text = ReadStaticDiagnosticProperty(typeof(Player).Assembly.GetType("Version", throwOnError: false), "CurrentVersion"); string text2 = ReadStaticDiagnosticProperty(typeof(Application), "unityVersion"); string text3 = SafeDiagnostic(() => typeof(BaseUnityPlugin).Assembly.GetName().Version?.ToString()); string text4 = SafeDiagnostic(() => typeof(Harmony).Assembly.GetName().Version?.ToString()); string text5 = SafeDiagnostic(() => typeof(Player).Assembly.ManifestModule.ModuleVersionId.ToString()); return "game " + text + ", Unity " + text2 + ", BepInEx " + text3 + ", Harmony " + text4 + ", assembly MVID " + text5; } private static string ReadStaticDiagnosticProperty(Type declaringType, string name) { if (declaringType == null) { return "unknown"; } return SafeDiagnostic(delegate { PropertyInfo[] array = (from property in declaringType.GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where string.Equals(property.Name, name, StringComparison.Ordinal) where property.GetIndexParameters().Length == 0 && property.GetGetMethod(nonPublic: true) != null select property).ToArray(); return (array.Length != 1) ? "unknown" : array[0].GetValue(null, null)?.ToString(); }); } private static string SafeDiagnostic(Func<string> read) { try { return read() ?? "unknown"; } catch { return "unknown"; } } } internal sealed class FeatureHost : IDisposable { private readonly IReadOnlyList<IFeatureModule> _modules; private readonly ManualLogSource _log; private bool _started; internal FeatureHost(IEnumerable<IFeatureModule> modules, ManualLogSource log) { _modules = (modules ?? throw new ArgumentNullException("modules")).ToArray(); _log = log ?? throw new ArgumentNullException("log"); if (_modules.Select((IFeatureModule module) => module.Id).Distinct<string>(StringComparer.Ordinal).Count() != _modules.Count) { throw new InvalidOperationException("Feature module ids must be unique."); } } internal void ValidateCompatibility(ICollection<string> failures) { foreach (IFeatureModule module in _modules) { module.ValidateCompatibility(failures); } } internal void Start() { if (_started) { return; } try { foreach (IFeatureModule module in _modules) { module.Enabled.SettingChanged += OnSettingChanged; if (module.Enabled.Value) { module.Enable(); } } _started = true; } catch { Stop(); throw; } } private void OnSettingChanged(object sender, EventArgs eventArgs) { try { foreach (IFeatureModule module in _modules) { if (module.Enabled.Value) { module.Enable(); } else { module.Disable(); } } } catch (Exception ex) { _log.LogError((object)("A feature toggle failed; all feature modules were disabled: " + ex)); Stop(); } } internal void Stop() { foreach (IFeatureModule item in _modules.Reverse()) { item.Enabled.SettingChanged -= OnSettingChanged; try { item.Disable(); } catch (Exception ex) { _log.LogError((object)("Feature cleanup failed for " + item.Id + ": " + ex)); } } _started = false; } public void Dispose() { Stop(); } } internal interface IFeatureModule { string Id { get; } ConfigEntry<bool> Enabled { get; } void ValidateCompatibility(ICollection<string> failures); void Enable(); void Disable(); } internal abstract class FeatureModuleBase : IFeatureModule { private readonly string _harmonyId; private Harmony _harmony; private bool _active; private bool _cleanupPending; public string Id { get; } public ConfigEntry<bool> Enabled { get; } protected ManualLogSource Log { get; } protected Harmony Harmony => _harmony ?? throw new InvalidOperationException("Harmony was not initialized after compatibility validation."); protected FeatureModuleBase(string id, ConfigEntry<bool> enabled, ManualLogSource log) { if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentException("Feature id is required.", "id"); } Id = id; Enabled = enabled ?? throw new ArgumentNullException("enabled"); Log = log ?? throw new ArgumentNullException("log"); _harmonyId = "com.jstack424.treadwell.feature." + id; } public abstract void ValidateCompatibility(ICollection<string> failures); public void Enable() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown if (_active) { return; } if (_cleanupPending) { Disable(); } _cleanupPending = true; try { _harmony = new Harmony(_harmonyId); TransactionalInstall.Run(InstallPatches, delegate { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } }, OnDisabled); _active = true; _cleanupPending = false; Log.LogInfo((object)("Enabled feature module: " + Id)); } catch { _active = false; throw; } } public void Disable() { if (!_active && !_cleanupPending) { return; } List<Exception> list = new List<Exception>(); try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception item) { list.Add(item); } try { OnDisabled(); } catch (Exception item2) { list.Add(item2); } _active = false; if (list.Count > 0) { _cleanupPending = true; throw new AggregateException("Feature cleanup failed for " + Id + ".", list); } _harmony = null; _cleanupPending = false; Log.LogInfo((object)("Disabled feature module: " + Id)); } protected abstract void InstallPatches(); protected virtual void OnDisabled() { } } internal sealed class RoadFeatureModule : FeatureModuleBase { private sealed class ScaledIndicatorTransform { private readonly Transform _transform; private readonly Vector3 _baseScale; private Vector3 _appliedScale; internal ScaledIndicatorTransform(Transform transform) { //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_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) _transform = transform ?? throw new ArgumentNullException("transform"); _baseScale = transform.localScale; _appliedScale = _baseScale; } internal void Apply(float factor) { //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_0057: Unknown result type (might be due to invalid IL or missing references) TerrainIndicatorScale terrainIndicatorScale = new TerrainIndicatorScale(_baseScale.x, _baseScale.y, _baseScale.z).ScaleUniformly(factor); _appliedScale = new Vector3(terrainIndicatorScale.X, terrainIndicatorScale.Y, terrainIndicatorScale.Z); _transform.localScale = _appliedScale; } internal bool IsApplied() { //IL_0014: 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) if ((Object)(object)_transform != (Object)null) { return Approximately(_transform.localScale, _appliedScale); } return false; } internal void Restore() { //IL_0014: 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_002d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_transform != (Object)null && Approximately(_transform.localScale, _appliedScale)) { _transform.localScale = _baseScale; } } } private sealed class TerrainBrushBinding { internal TerrainBrushKind Kind { get; } internal Piece Piece { get; } internal TerrainOp TerrainOp { get; } internal float VanillaRadius { get; } internal bool IndicatorSynchronized { get; set; } internal TerrainBrushBinding(TerrainBrushKind kind, Piece piece, TerrainOp terrainOp, float vanillaRadius) { Kind = kind; Piece = piece; TerrainOp = terrainOp; VanillaRadius = vanillaRadius; } } private sealed class RadiusPlacement { private const float PositionEpsilon = 0.01f; private readonly Vector3 _position; private readonly RadiusMutation _prefabMutation; private bool _active; private bool _restored; internal TerrainBrushKind Kind { get; } internal float TargetRadius { get; } internal RadiusPlacement(TerrainBrushKind kind, float targetRadius, Vector3 position, TerrainOp prefabTerrainOp) { //IL_0015: 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) Kind = kind; TargetRadius = targetRadius; _position = position; _prefabMutation = new RadiusMutation(prefabTerrainOp); } internal void ApplyAndActivate() { _prefabMutation.Apply(TargetRadius); try { if (_activeRadiusPlacements == null) { _activeRadiusPlacements = new Stack<RadiusPlacement>(); } _activeRadiusPlacements.Push(this); _active = true; } catch { _prefabMutation.Restore(_activeModule?.Log); throw; } } internal bool MatchesPosition(Vector3 position) { //IL_0000: 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_003c: Unknown result type (might be due to invalid IL or missing references) if (Math.Abs(position.x - _position.x) <= 0.01f && Math.Abs(position.y - _position.y) <= 0.01f) { return Math.Abs(position.z - _position.z) <= 0.01f; } return false; } internal void Restore(ManualLogSource log) { if (_restored) { return; } _restored = true; if (_active) { if (_activeRadiusPlacements != null && _activeRadiusPlacements.Count != 0 && _activeRadiusPlacements.Peek() == this) { _activeRadiusPlacements.Pop(); } else { _activeRadiusPlacements?.Clear(); if (log != null) { log.LogWarning((object)"Terrain-radius placement scopes were not nested as expected; active scopes were cleared."); } } _active = false; } _prefabMutation.Restore(log); } } private sealed class RadiusMutation { private readonly TerrainRadiusMutationSession _session; internal RadiusMutation(params TerrainOp[] terrainOps) { if (terrainOps == null || terrainOps.Length == 0) { throw new ArgumentNullException("terrainOps"); } List<ITerrainRadiusMutationTarget> list = new List<ITerrainRadiusMutationTarget>(); foreach (TerrainOp val in terrainOps) { if ((Object)(object)val == (Object)null || val.m_settings == null) { throw new ArgumentNullException("terrainOps"); } list.Add(new TerrainOpSettingsTarget(val.m_settings)); } _session = new TerrainRadiusMutationSession(list); } internal RadiusMutation(ITerrainRadiusMutationTarget target) { _session = new TerrainRadiusMutationSession(new ITerrainRadiusMutationTarget[1] { target ?? throw new ArgumentNullException("target") }); } internal void Apply(float targetRadius) { _session.Apply(targetRadius); } internal void Restore(ManualLogSource log) { if (_session.Restore() && log != null) { log.LogWarning((object)"A terrain-radius field changed during placement; Treadwell left that conflicting field untouched."); } } } private sealed class TerrainOpSettingsTarget : ITerrainRadiusMutationTarget { private readonly Settings _settings; public object Identity => _settings; public bool LevelActive => _settings.m_level; public bool RaiseActive => _settings.m_raise; public bool SmoothActive => _settings.m_smooth; public bool PaintActive => _settings.m_paintCleared; public int PaintType => (int)_settings.m_paintType; public float LevelRadius { get { return _settings.m_levelRadius; } set { _settings.m_levelRadius = value; } } public float RaiseRadius { get { return _settings.m_raiseRadius; } set { _settings.m_raiseRadius = value; } } public float SmoothRadius { get { return _settings.m_smoothRadius; } set { _settings.m_smoothRadius = value; } } public float PaintRadius { get { return _settings.m_paintRadius; } set { _settings.m_paintRadius = value; } } internal TerrainOpSettingsTarget(Settings settings) { _settings = settings ?? throw new ArgumentNullException("settings"); } } private sealed class PieceTableEntryInspection { internal Piece Piece { get; } internal TerrainOp[] TerrainOps { get; } internal TerrainBrushShape BrushShape { get; } internal PieceTableEntryInspection(Piece piece, TerrainOp[] terrainOps, TerrainBrushShape brushShape) { Piece = piece; TerrainOps = terrainOps; BrushShape = brushShape; } } internal const double NaturalGapHoldSeconds = 0.18; private static RoadFeatureModule _activeModule; [ThreadStatic] private static Stack<RadiusPlacement> _activeRadiusPlacements; private static MethodInfo GetLastGroundColliderMethod; private readonly ConfigEntry<float> _dirtSpeed; private readonly ConfigEntry<float> _dirtStamina; private readonly ConfigEntry<float> _pavedSpeed; private readonly ConfigEntry<float> _pavedStamina; private readonly RoadSurfaceTracker _surfaceTracker = new RoadSurfaceTracker(0.18); private readonly TerrainRadiusControlHint _radiusControlHint = new TerrainRadiusControlHint(); private Harmony _radiusControlHintHarmony; private readonly Dictionary<Piece, TerrainBrushBinding> _terrainBrushes = new Dictionary<Piece, TerrainBrushBinding>(); private readonly Dictionary<TerrainBrushKind, TerrainRadiusSelection> _terrainRadii = new Dictionary<TerrainBrushKind, TerrainRadiusSelection> { [TerrainBrushKind.LevelGround] = new TerrainRadiusSelection(TerrainBrushKind.LevelGround, 3f), [TerrainBrushKind.Pathen] = new TerrainRadiusSelection(TerrainBrushKind.Pathen, 2f), [TerrainBrushKind.PavedRoad] = new TerrainRadiusSelection(TerrainBrushKind.PavedRoad, 3f) }; private PieceTable _radiusPieceTable; private int _radiusValidationFrame = -1; private bool _radiusValidationResult; private GameObject _scaledPlacementGhost; private readonly List<ScaledIndicatorTransform> _scaledIndicatorTransforms = new List<ScaledIndicatorTransform>(); private static int _suppressCameraMouseWheelDepth; private bool _loggedRadiusDiscoveryFailure; private static RadiusPlacement CurrentRadiusPlacement { get { if (_activeRadiusPlacements == null || _activeRadiusPlacements.Count == 0) { return null; } return _activeRadiusPlacements.Peek(); } } internal RoadFeatureModule(ConfigEntry<bool> enabled, ConfigEntry<float> dirtSpeed, ConfigEntry<float> dirtStamina, ConfigEntry<float> pavedSpeed, ConfigEntry<float> pavedStamina, ManualLogSource log) : base("roads", enabled, log) { _dirtSpeed = dirtSpeed ?? throw new ArgumentNullException("dirtSpeed"); _dirtStamina = dirtStamina ?? throw new ArgumentNullException("dirtStamina"); _pavedSpeed = pavedSpeed ?? throw new ArgumentNullException("pavedSpeed"); _pavedStamina = pavedStamina ?? throw new ArgumentNullException("pavedStamina"); } public override void ValidateCompatibility(ICollection<string> failures) { //IL_124c: Unknown result type (might be due to invalid IL or missing references) //IL_1270: Unknown result type (might be due to invalid IL or missing references) //IL_1294: Unknown result type (might be due to invalid IL or missing references) CompatibilityGate.RequireMethod(failures, typeof(PieceTable), "UpdateAvailable", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[4] { typeof(HashSet<string>), typeof(Player), typeof(bool), typeof(bool) }, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireField(failures, typeof(PieceTable), "m_pieces", typeof(List<GameObject>), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireMethod(failures, typeof(ZNetScene), "OnDestroy", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Player), "SetPlaceMode", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, new Type[1] { typeof(PieceTable) }, (MethodInfo method) => !method.IsStatic && method.IsFamily && method.IsVirtual); CompatibilityGate.RequireMethod(failures, typeof(Player), "GetBuildTool", typeof(PieceTable), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Player), "UpdatePlacement", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, new Type[2] { typeof(bool), typeof(float) }, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(GameCamera), "UpdateCamera", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, new Type[1] { typeof(float) }, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Player), "PlacePiece", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[5] { typeof(Piece), typeof(Vector3), typeof(Quaternion), typeof(bool), typeof(bool) }, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(TerrainComp), "DoOperation", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, new Type[3] { typeof(Vector3), typeof(Vector3), typeof(Settings) }, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Player), "InPlaceMode", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Player), "IsDead", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(PieceTable), "GetSelectedPiece", typeof(Piece), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireField(failures, typeof(Player), "m_placementGhost", typeof(GameObject), BindingFlags.Instance | BindingFlags.NonPublic); CompatibilityGate.RequireField(failures, typeof(Piece), "m_name", typeof(string), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Piece), "m_craftingStation", typeof(CraftingStation), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Piece), "m_resources", typeof(Requirement[]), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Requirement), "m_resItem", typeof(ItemDrop), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Requirement), "m_amount", typeof(int), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(TerrainModifier), "m_paintType", typeof(PaintType), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Piece), "m_canRotate", typeof(bool), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(TerrainOp), "m_settings", typeof(Settings), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_level", typeof(bool), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_levelRadius", typeof(float), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_raise", typeof(bool), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_raiseRadius", typeof(float), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_smooth", typeof(bool), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_smoothRadius", typeof(float), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_paintCleared", typeof(bool), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_paintType", typeof(PaintType), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Settings), "m_paintRadius", typeof(float), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireMethod(failures, typeof(TerrainOp), "GetRadius", typeof(float), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Settings), "GetRadius", typeof(float), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Player), "GetRunSpeedFactor", typeof(float), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes, (MethodInfo method) => method.IsFamily && method.IsVirtual); CompatibilityGate.RequireMethod(failures, typeof(SEMan), "ModifyRunStaminaDrain", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[4] { typeof(float), typeof(float).MakeByRefType(), typeof(Vector3), typeof(bool) }, (MethodInfo method) => !method.IsStatic && string.Equals(method.GetParameters()[1].Name, "drain", StringComparison.Ordinal)); CompatibilityGate.RequireMethod(failures, typeof(Character), "IsOnGround", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes); CompatibilityGate.RequireMethod(failures, typeof(Character), "IsRunning", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes); CompatibilityGate.RequireMethodNamedReturn(failures, typeof(Character), "GetLastGroundCollider", "UnityEngine.Collider", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes); CompatibilityGate.RequireMethod(failures, typeof(Heightmap), "GetPaintMask", typeof(Color), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(Vector3) }); CompatibilityGate.RequireField(failures, typeof(Player), "m_localPlayer", typeof(Player), BindingFlags.Static | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(SEMan), "m_character", typeof(Character), BindingFlags.Instance | BindingFlags.NonPublic); CompatibilityGate.RequireField(failures, typeof(Heightmap), "m_paintMaskDirt", typeof(Color), BindingFlags.Static | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Heightmap), "m_paintMaskCultivated", typeof(Color), BindingFlags.Static | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Heightmap), "m_paintMaskPaved", typeof(Color), BindingFlags.Static | BindingFlags.Public); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "PlayerSetPlaceModePrefix", new Type[1] { typeof(PieceTable) }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "PieceTableUpdateAvailablePrefix", new Type[1] { typeof(PieceTable) }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "PlayerUpdatePlacementPrefix", new Type[3] { typeof(Player), typeof(bool), typeof(GameObject) }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "PlayerPlacePiecePrefix", new Type[4] { typeof(Player), typeof(Piece), typeof(Vector3), typeof(RadiusPlacement).MakeByRefType() }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "PlayerPlacePieceFinalizer", typeof(Exception), new Type[2] { typeof(Exception), typeof(RadiusPlacement) }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "TerrainCompDoOperationPrefix", new Type[3] { typeof(Vector3), typeof(Settings), typeof(RadiusMutation).MakeByRefType() }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "TerrainCompDoOperationFinalizer", typeof(Exception), new Type[2] { typeof(Exception), typeof(RadiusMutation) }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "GameCameraUpdateCameraPrefix", new Type[1] { typeof(bool).MakeByRefType() }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "GameCameraUpdateCameraFinalizer", typeof(Exception), new Type[2] { typeof(Exception), typeof(bool) }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "ZInputGetMouseScrollWheelPrefix", typeof(bool), new Type[1] { typeof(float).MakeByRefType() }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "ZNetSceneOnDestroyPrefix", Type.EmptyTypes); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "GetRunSpeedFactorPostfix", new Type[2] { typeof(Player), typeof(float).MakeByRefType() }); CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "ModifyRunStaminaDrainPostfix", new Type[2] { typeof(Character), typeof(float).MakeByRefType() }); CompatibilityGate.RequireGenericMethod(failures, typeof(GameObject), "GetComponent", BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, returnsArray: false); CompatibilityGate.RequireGenericMethod(failures, typeof(GameObject), "GetComponentsInChildren", BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(bool) }, returnsArray: true); CompatibilityGate.RequireGenericMethod(failures, typeof(Component), "GetComponent", BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, returnsArray: false); CompatibilityGate.RequireGenericMethod(failures, typeof(Component), "GetComponentInParent", BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, returnsArray: false); CompatibilityGate.RequireProperty(failures, typeof(Component), "gameObject", typeof(GameObject), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireProperty(failures, typeof(GameObject), "activeInHierarchy", typeof(bool), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireProperty(failures, typeof(GameObject), "transform", typeof(Transform), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireProperty(failures, typeof(Component), "transform", typeof(Transform), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireProperty(failures, typeof(Transform), "position", typeof(Vector3), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireProperty(failures, typeof(Transform), "localScale", typeof(Vector3), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: true); CompatibilityGate.RequireProperty(failures, typeof(ParticleSystem), "main", typeof(MainModule), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireProperty(failures, typeof(MainModule), "scalingMode", typeof(ParticleSystemScalingMode), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: true); CompatibilityGate.RequireMethod(failures, typeof(Transform), "Find", typeof(Transform), BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(string) }, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireProperty(failures, typeof(Time), "frameCount", typeof(int), BindingFlags.Static | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireMethod(failures, typeof(ZInput), "GetKey", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[2] { typeof(KeyCode), typeof(bool) }, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(ZInput), "GetMouseScrollWheel", typeof(float), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Hud), "IsPieceSelectionVisible", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireProperty(failures, typeof(Time), "unscaledTime", typeof(float), BindingFlags.Static | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireProperty(failures, typeof(Object), "name", typeof(string), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireField(failures, typeof(Color), "r", typeof(float), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Color), "g", typeof(float), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Color), "b", typeof(float), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(Color), "a", typeof(float), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireMethod(failures, typeof(Object), "op_Equality", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[2] { typeof(Object), typeof(Object) }, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Object), "op_Inequality", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[2] { typeof(Object), typeof(Object) }, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireConstructor(failures, typeof(Harmony), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(string) }); CompatibilityGate.RequireConstructor(failures, typeof(HarmonyMethod), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[3] { typeof(Type), typeof(string), typeof(Type[]) }); CompatibilityGate.RequireMethod(failures, typeof(Harmony), "Patch", typeof(MethodInfo), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[6] { typeof(MethodBase), typeof(HarmonyMethod), typeof(HarmonyMethod), typeof(HarmonyMethod), typeof(HarmonyMethod), typeof(HarmonyMethod) }, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Harmony), "UnpatchSelf", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(AccessTools), "DeclaredMethod", typeof(MethodInfo), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[4] { typeof(Type), typeof(string), typeof(Type[]), typeof(Type[]) }, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireEnumValue(failures, typeof(PaintType), "Dirt", 0); CompatibilityGate.RequireEnumValue(failures, typeof(PaintType), "Cultivate", 1); CompatibilityGate.RequireEnumValue(failures, typeof(PaintType), "Paved", 2); CompatibilityGate.RequireEnumValue(failures, typeof(ParticleSystemScalingMode), "Local", 1); CompatibilityGate.RequireColor(failures, "dirt paint", Heightmap.m_paintMaskDirt, 1f, 0f, 0f, 1f); CompatibilityGate.RequireColor(failures, "cultivated paint", Heightmap.m_paintMaskCultivated, 0f, 1f, 0f, 1f); CompatibilityGate.RequireColor(failures, "paved paint", Heightmap.m_paintMaskPaved, 0f, 0f, 1f, 1f); } protected override void InstallPatches() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Expected O, but got Unknown //IL_020c: Expected O, but got Unknown //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Expected O, but got Unknown //IL_0287: Expected O, but got Unknown //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Expected O, but got Unknown //IL_02e8: Expected O, but got Unknown //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown //IL_0358: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Expected O, but got Unknown //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Expected O, but got Unknown //IL_0411: Unknown result type (might be due to invalid IL or missing references) //IL_041e: Expected O, but got Unknown if (_activeModule != null && _activeModule != this) { throw new InvalidOperationException("Another road feature module is already active."); } GetLastGroundColliderMethod = AccessTools.DeclaredMethod(typeof(Character), "GetLastGroundCollider", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException(typeof(Character).FullName, "GetLastGroundCollider"); _activeModule = this; base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Player), "SetPlaceMode", new Type[1] { typeof(PieceTable) }, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "PlayerSetPlaceModePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(PieceTable), "UpdateAvailable", new Type[4] { typeof(HashSet<string>), typeof(Player), typeof(bool), typeof(bool) }, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "PieceTableUpdateAvailablePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Player), "UpdatePlacement", new Type[2] { typeof(bool), typeof(float) }, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "PlayerUpdatePlacementPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Player), "PlacePiece", new Type[5] { typeof(Piece), typeof(Vector3), typeof(Quaternion), typeof(bool), typeof(bool) }, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "PlayerPlacePiecePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(RoadFeatureModule), "PlayerPlacePieceFinalizer", (Type[])null), (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(TerrainComp), "DoOperation", new Type[3] { typeof(Vector3), typeof(Vector3), typeof(Settings) }, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "TerrainCompDoOperationPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(RoadFeatureModule), "TerrainCompDoOperationFinalizer", (Type[])null), (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(GameCamera), "UpdateCamera", new Type[1] { typeof(float) }, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "GameCameraUpdateCameraPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(RoadFeatureModule), "GameCameraUpdateCameraFinalizer", (Type[])null), (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZInput), "GetMouseScrollWheel", Type.EmptyTypes, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "ZInputGetMouseScrollWheelPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZNetScene), "OnDestroy", Type.EmptyTypes, (Type[])null), new HarmonyMethod(typeof(RoadFeatureModule), "ZNetSceneOnDestroyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Player), "GetRunSpeedFactor", Type.EmptyTypes, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(RoadFeatureModule), "GetRunSpeedFactorPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); base.Harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(SEMan), "ModifyRunStaminaDrain", new Type[4] { typeof(float), typeof(float).MakeByRefType(), typeof(Vector3), typeof(bool) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(RoadFeatureModule), "ModifyRunStaminaDrainPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); try { TryInstallTerrainRadiusControlHint(); } catch (Exception ex) { try { base.Log.LogWarning((object)("Terrain-radius control hint was skipped; gameplay remains active: " + ex)); } catch { } } } protected override void OnDisabled() { List<Exception> list = new List<Exception>(); try { ResetTerrainRadiusRuntime(); } catch (Exception item) { list.Add(item); } if (_radiusControlHintHarmony != null) { try { _radiusControlHintHarmony.UnpatchSelf(); _radiusControlHintHarmony = null; } catch (Exception item2) { list.Add(item2); } } try { _radiusControlHint.Detach(); } catch (Exception item3) { list.Add(item3); } if (_activeModule == this) { _activeModule = null; } _surfaceTracker.Reset(); GetLastGroundColliderMethod = null; if (list.Count != 0) { throw new AggregateException("Road feature cleanup was incomplete.", list); } } private void TryInstallTerrainRadiusControlHint() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown List<string> list = new List<string>(); ValidateTerrainRadiusControlHintCompatibility(list); if (list.Count != 0) { base.Log.LogWarning((object)("Terrain-radius control hint was skipped; gameplay remains active. " + string.Join("; ", list))); return; } Harmony val = null; try { val = new Harmony("com.jstack424.treadwell.feature.road.hint"); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(KeyHints), "UpdateHints", Type.EmptyTypes, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(RoadFeatureModule), "KeyHintsUpdateHintsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _radiusControlHintHarmony = val; } catch (Exception ex) { Exception ex2 = null; try { if (val != null) { val.UnpatchSelf(); } } catch (Exception ex3) { ex2 = ex3; _radiusControlHintHarmony = val; } base.Log.LogWarning((object)("Terrain-radius control hint could not be installed; gameplay remains active: " + ex?.ToString() + ((ex2 == null) ? string.Empty : (" Cleanup also failed and will be retried on disable: " + ex2)))); } } private static void ValidateTerrainRadiusControlHintCompatibility(ICollection<string> failures) { CompatibilityGate.RequirePatchMethod(failures, typeof(RoadFeatureModule), "KeyHintsUpdateHintsPostfix", new Type[1] { typeof(KeyHints) }); CompatibilityGate.RequireMethod(failures, typeof(KeyHints), "UpdateHints", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireField(failures, typeof(KeyHints), "m_buildHints", typeof(GameObject), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(KeyHints), "m_buildAlternativePlacingKey", typeof(TextMeshProUGUI), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(UIInputHint), "m_gamepadHint", typeof(GameObject), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireField(failures, typeof(UIInputHint), "m_mouseKeyboardHint", typeof(GameObject), BindingFlags.Instance | BindingFlags.Public); CompatibilityGate.RequireMethod(failures, typeof(ZInput), "IsGamepadActive", typeof(bool), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireProperty(failures, typeof(Transform), "parent", typeof(Transform), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireMethod(failures, typeof(Transform), "SetAsLastSibling", typeof(void), BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(GameObject), "SetActive", typeof(void), BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(bool) }, (MethodInfo method) => !method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Object), "Instantiate", typeof(Object), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[3] { typeof(Object), typeof(Transform), typeof(bool) }, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireMethod(failures, typeof(Object), "Destroy", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public, new Type[1] { typeof(Object) }, (MethodInfo method) => method.IsStatic); CompatibilityGate.RequireProperty(failures, typeof(Object), "name", typeof(string), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: true); CompatibilityGate.RequireProperty(failures, typeof(TMP_Text), "text", typeof(string), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: true); CompatibilityGate.RequireProperty(failures, typeof(LayoutElement), "ignoreLayout", typeof(bool), BindingFlags.Instance | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireProperty(failures, typeof(Localization), "instance", typeof(Localization), BindingFlags.Static | BindingFlags.Public, requireGetter: true, requireSetter: false); CompatibilityGate.RequireMethod(failures, typeof(Localization), "RemoveTextFromCache", typeof(void), BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(TMP_Text) }, (MethodInfo method) => !method.IsStatic); } private static void PlayerSetPlaceModePrefix(PieceTable __0) { _activeModule?.RefreshTerrainRadiusBrushes(__0); } private static void PieceTableUpdateAvailablePrefix(PieceTable __instance) { RoadFeatureModule activeModule = _activeModule; if (activeModule != null) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && localPlayer.GetBuildTool() == __instance) { activeModule.RefreshTerrainRadiusBrushes(__instance); } } } private static void PlayerUpdatePlacementPrefix(Player __instance, bool __0, GameObject ___m_placementGhost) { RoadFeatureModule activeModule = _activeModule; if (activeModule == null) { return; } try { activeModule.UpdateTerrainRadiusInput(__instance, __0, ___m_placementGhost); } catch (Exception ex) { activeModule.ResetTerrainRadiusRuntime(); activeModule.Log.LogError((object)("Terrain-radius input failed and its runtime state was reset: " + ex)); } } private static void PlayerPlacePiecePrefix(Player __instance, Piece __0, Vector3 __1, ref RadiusPlacement __state) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) RoadFeatureModule activeModule = _activeModule; if (activeModule == null) { return; } try { __state = activeModule.BeginRadiusMutation(__instance, __0, __1); } catch (Exception ex) { __state?.Restore(activeModule.Log); __state = null; activeModule.Log.LogError((object)("Terrain-radius placement override failed before placement; vanilla radii were retained: " + ex)); } } private static Exception PlayerPlacePieceFinalizer(Exception __exception, RadiusPlacement __state) { RoadFeatureModule activeModule = _activeModule; try { __state?.Restore(activeModule?.Log); } catch (Exception ex) { try { if (activeModule != null) { activeModule.Log.LogError((object)("Terrain-radius placement cleanup failed: " + ex)); } } catch { } } return __exception; } private static void TerrainCompDoOperationPrefix(Vector3 __0, Settings __2, ref RadiusMutation __state) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) RoadFeatureModule activeModule = _activeModule; RadiusPlacement currentRadiusPlacement = CurrentRadiusPlacement; if (activeModule == null || currentRadiusPlacement == null || __2 == null || !currentRadiusPlacement.MatchesPosition(__0)) { return; } try { TerrainOpSettingsTarget target = new TerrainOpSettingsTarget(__2); if (TerrainRadiusOperationGuard.MatchesAuthoritativeSettings(currentRadiusPlacement.Kind, target, currentRadiusPlacement.TargetRadius)) { __state = new RadiusMutation((ITerrainRadiusMutationTarget)target); __state.Apply(currentRadiusPlacement.TargetRadius); } } catch (Exception ex) { __state?.Restore(activeModule.Log); __state = null; activeModule.Log.LogError((object)("Terrain-radius authoritative operation override failed; this operation retained its resolved radius: " + ex)); } } private static Exception TerrainCompDoOperationFinalizer(Exception __exception, RadiusMutation __state) { RoadFeatureModule activeModule = _activeModule; try { __state?.Restore(activeModule?.Log); } catch (Exception ex) { try { if (activeModule != null) { activeModule.Log.LogError((object)("Terrain-radius authoritative operation cleanup failed: " + ex)); } } catch { } } return __exception; } private static void GameCameraUpdateCameraPrefix(ref bool __state) { __state = false; RoadFeatureModule activeModule = _activeModule; if (activeModule == null) { return; } try { if (activeModule.ShouldSuppressCameraZoom()) { _suppressCameraMouseWheelDepth++; __state = true; } } catch (Exception ex) { activeModule.Log.LogError((object)("Camera-wheel routing failed closed to vanilla zoom: " + ex)); } } private static Exception GameCameraUpdateCameraFinalizer(Exception __exception, bool __state) { if (__state && _suppressCameraMouseWheelDepth > 0) { _suppressCameraMouseWheelDepth--; } return __exception; } private static bool ZInputGetMouseScrollWheelPrefix(ref float __result) { if (_activeModule == null || _suppressCameraMouseWheelDepth <= 0) { return true; } __result = 0f; return false; } private static void KeyHintsUpdateHintsPostfix(KeyHints __instance) { RoadFeatureModule activeModule = _activeModule; if (activeModule == null) { return; } try { activeModule._radiusControlHint.Update(__instance, activeModule.ShouldShowTerrainRadiusControlHint()); } catch (Exception ex) { try { activeModule._radiusControlHint.FailForOwner(__instance); } catch { } try { activeModule.Log.LogError((object)("Terrain-radius control hint failed and was removed; gameplay controls remain active: " + ex)); } catch { } } } private bool ShouldShowTerrainRadiusControlHint() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !((Character)localPlayer).InPlaceMode() || ((Character)localPlayer).IsDead()) { return false; } bool flag = ZInput.IsGamepadActive(); if (flag) { return false; } bool flag2 = Hud.IsPieceSelectionVisible(); if (flag2) { return false; } PieceTable buildTool = localPlayer.GetBuildTool(); bool flag3 = buildTool == _radiusPieceTable && _radiusValidationResult && _terrainBrushes.Count == 3; TerrainBrushKind selectedBrush = TerrainBrushKind.None; Piece val = (flag3 ? buildTool.GetSelectedPiece() : null); if ((Object)(object)val != (Object)null && _terrainBrushes.TryGetValue(val, out var value) && (Object)(object)value.TerrainOp != (Object)null && HasExpectedVanillaRadii(value.Kind, value.TerrainOp.m_settings)) { selectedBrush = value.Kind; } return TerrainRadiusControlHintRouting.ShouldShow(isLocalPlayerAvailable: true, inPlaceMode: true, isDead: false, flag, flag2, flag3, selectedBrush); } private bool ShouldSuppressCameraZoom() { Player localPlayer = Player.m_localPlayer; PieceTable table = (((Object)(object)localPlayer != (Object)null) ? localPlayer.GetBuildTool() : null); bool altHeld = ZInput.GetKey((KeyCode)308, false) || ZInput.GetKey((KeyCode)307, false); return CameraWheelRouting.ShouldSuppressZoom((Object)(object)localPlayer != (Object)null, (Object)(object)localPlayer != (Object)null && ((Character)localPlayer).InPlaceMode(), (Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsDead(), IsLikelyHoePieceTable(table), altHeld, Hud.IsPieceSelectionVisible()); } private static void ZNetSceneOnDestroyPrefix() { RoadFeatureModule activeModule = _activeModule; if (activeModule == null) { return; } activeModule.ResetTerrainRadiusRuntime(); try { activeModule._radiusControlHint.Detach(); } catch (Exception ex) { try { activeModule.Log.LogWarning((object)("Terrain-radius control hint cleanup failed during scene destruction: " + ex)); } catch { } } } private void RefreshTerrainRadiusBrushes(PieceTable table) { RestoreScaledPlacementGhost(); _terrainBrushes.Clear(); _radiusPieceTable = table; _radiusValidationFrame = -1; _radiusValidationResult = false; if ((Object)(object)table == (Object)null) { return; } Dictionary<TerrainBrushKind, List<PieceTableEntryInspection>> dictionary = new Dictionary<TerrainBrushKind, List<PieceTableEntryInspection>> { [TerrainBrushKind.LevelGround] = new List<PieceTableEntryInspection>(), [TerrainBrushKind.Pathen] = new List<PieceTableEntryInspection>(), [TerrainBrushKind.PavedRoad] = new List<PieceTableEntryInspection>() }; foreach (PieceTableEntryInspection item in InspectPieceTable(table)) { TerrainBrushKind terrainBrushKind = VanillaTerrainBrushClassifier.Classify(item.BrushShape); if (terrainBrushKind != TerrainBrushKind.None && item.TerrainOps.Length == 1 && !((Object)(object)item.TerrainOps[0] == (Object)null) && HasExpectedVanillaRadii(terrainBrushKind, item.TerrainOps[0].m_settings)) { dictionary[terrainBrushKind].Add(item); } } if (dictionary[TerrainBrushKind.LevelGround].Count != 1 || dictionary[TerrainBrushKind.Pathen].Count != 1 || dictionary[TerrainBrushKind.PavedRoad].Count != 1) { if (!_loggedRadiusDiscoveryFailure) { _loggedRadiusDiscoveryFailure = true; base.Log.LogWarning((object)("Terrain-radius controls were left disabled for this piece table because exact vanilla brush discovery was not unique (Level Ground=" + dictionary[TerrainBrushKind.LevelGround].Count + ", Pathen=" + dictionary[TerrainBrushKind.Pathen].Count + ", Paved Road=" + dictionary[TerrainBrushKind.PavedRoad].Count + ").")); } return; } foreach (KeyValuePair<TerrainBrushKind, List<PieceTableEntryInspection>> item2 in dictionary) { PieceTableEntryInspection pieceTableEntryInspection = item2.Value[0]; TerrainOp val = pieceTableEntryInspection.TerrainOps[0]; _terrainBrushes.Add(pieceTableEntryInspection.Piece, new TerrainBrushBinding(item2.Key, pieceTableEntryInspection.Piece, val, val.GetRadius())); } } private static bool HasExpectedVanillaRadii(TerrainBrushKind kind, Settings settings) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_009e: 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_00f1: Invalid comparison between Unknown and I4 if (settings == null) { return false; } switch (kind) { case TerrainBrushKind.LevelGround: if (!settings.m_level && !settings.m_raise && settings.m_smooth && settings.m_paintCleared && (int)settings.m_paintType == 0 && Approximately(settings.GetRadius(), 3f) && Approximately(settings.m_smoothRadius, 3f)) { return Approximately(settings.m_paintRadius, 3f); } return false; case TerrainBrushKind.Pathen: if (!settings.m_level && !settings.m_raise && !settings.m_smooth && settings.m_paintCleared && (int)settings.m_paintType == 0 && Approximately(settings.GetRadius(), 2f)) { return Approximately(settings.m_paintRadius, 2f); } return false; case TerrainBrushKind.PavedRoad: if (!settings.m_level && !settings.m_raise && settings.m_smooth && settings.m_paintCleared && (int)settings.m_paintType == 2 && Approximately(settings.GetRadius(), 3f) && Approximately(settings.m_smoothRadius, 3f)) { return Approximately(settings.m_paintRadius, 2.2f); } return false; default: return false; } } private bool RevalidateTerrainRadiusBindings(bool force) { int frameCount = Time.frameCount; if (!force && _radiusValidationFrame == frameCount) { return _radiusValidationResult; } _radiusValidationFrame = frameCount; _radiusValidationResult = false; if ((Object)(object)_radiusPieceTable == (Object)null || _terrainBrushes.Count != 3) { return false; } Dictionary<TerrainBrushKind, List<PieceTableEntryInspection>> dictionary = new Dictionary<TerrainBrushKind, List<PieceTableEntryInspection>> { [TerrainBrushKind.LevelGround] = new List<PieceTableEntryInspection>(), [TerrainBrushKind.Pathen] = new List<PieceTableEntryInspection>(), [TerrainBrushKind.PavedRoad] = new List<PieceTableEntryInspection>() }; foreach (PieceTableEntryInspection item in InspectPieceTable(_radiusPieceTable)) { TerrainBrushKind terrainBrushKind = VanillaTerrainBrushClassifier.Classify(item.BrushShape); if (terrainBrushKind != TerrainBrushKind.None && item.TerrainOps.Length == 1 && !((Object)(object)item.TerrainOps[0] == (Object)null) && HasExpectedVanillaRadii(terrainBrushKind, item.TerrainOps[0].m_settings)) { dictionary[terrainBrushKind].Add(item); } } foreach (KeyValuePair<TerrainBrushKind, List<PieceTableEntryInspection>> item2 in dictionary) { if (item2.Value.Count != 1) { return false; } PieceTableEntryInspection pieceTableEntryInspection = item2.Value[0]; if ((Object)(object)pieceTableEntryInspection.Piece == (Object)null || !_terrainBrushes.TryGetValue(pieceTableEntryInspection.Piece, out var value) || value.Kind != item2.Key || value.Piece != pieceTableEntryInspection.Piece || value.TerrainOp != pieceTableEntryInspection.TerrainOps[0] || !Approximately(value.VanillaRadius, pieceTableEntryInspection.TerrainOps[0].GetRadius())) { return false; } } _radiusValidationResult = true; return true; } private void UpdateTerrainRadiusInput(Player player, bool takeInput, GameObject placementGhost) { if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).InPlaceMode() || ((Character)player).IsDead() || !takeInput || Hud.IsPieceSelectionVisible()) { RestoreScaledPlacementGhost(); return; } PieceTable buildTool = player.GetBuildTool(); if (buildTool != _radiusPieceTable) { RefreshTerrainRadiusBrushes(buildTool); } Piece val = (((Object)(object)buildTool != (Object)null) ? buildTool.GetSelectedPiece() : null); if ((Object)(object)val == (Object)null || !_terrainBrushes.TryGetValue(val, out var value)) { RestoreScaledPlacementGhost(); } else { if (!SynchronizePlacementGhost(placementGhost, value) || (!ZInput.GetKey((KeyCode)308, false) && !ZInput.GetKey((KeyCode)307, false))) { return; } float mouseScrollWheel = ZInput.GetMouseScrollWheel(); if (mouseScrollWheel != 0f) { TerrainRadiusSelection terrainRadiusSelection = _terrainRadii[value.Kind]; TerrainRadiusSelection value2 = terrainRadiusSelection.Scroll(mouseScrollWheel); if (!Approximately(value2.Radius, terrainRadiusSelection.Radius)) { _terrainRadii[value.Kind] = value2; SynchronizePlacementGhost(placementGhost, value); } } } } private bool SynchronizePlacementGhost(GameObject placementGhost, TerrainBrushBinding binding) { //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_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Invalid comparison between Unknown and I4 if (binding == null || (Object)(object)binding.TerrainOp == (Object)null || !RevalidateTerrainRadiusBindings(force: false) || !HasExpectedVanillaRadii(binding.Kind, binding.TerrainOp.m_settings)) { RestoreScaledPlacementGhost(); return false; } if ((Object)(object)placementGhost == (Object)null) { RestoreScaledPlacementGhost(); return false; } Transform val = placementGhost.transform.Find("_GhostOnly"); if ((Object)(object)val == (Object)null) { RestoreScaledPlacementGhost(); return false; } GameObject gameObject = ((Component)val).gameObject; if (!TerrainIndicatorRouting.CanSynchronize((Object)(object)gameObject != (Object)null, gameObject.activeInHierarchy)) { RestoreScaledPlacementGhost(); return false; } if (_scaledPlacementGhost != gameObject) { RestoreScaledPlacementGhost(); _scaledPlacementGhost = gameObject; _scaledIndicatorTransforms.Add(new ScaledIndicatorTransform(val)); ParticleSystem[] componentsInChildren = gameObject.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null) && ((Component)val2).transform != val) { MainModule main = val2.main; if (TerrainIndicatorRouting.ShouldScaleOwnTransform(isGhostOnlyMarker: false, (int)((MainModule)(ref main)).scalingMode == 1)) { _scaledIndicatorTransforms.Add(new ScaledIndicatorTransform(((Component)val2).transform)); } } } } float factor = _terrainRadii[binding.Kind].ScaleFor(binding.VanillaRadius); foreach (ScaledIndicatorTransform scaledIndicatorTransform in _scaledIndicatorTransforms) { scaledIndicatorTransform.Apply(factor); } binding.IndicatorSynchronized = true; return true; } private RadiusPlacement BeginRadiusMutation(Player player, Piece piece, Vector3 position) { //IL_0107: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).InPlaceMode() || ((Character)player).IsDead() || (Object)(object)piece == (Object)null) { return null; } PieceTable buildTool = player.GetBuildTool(); if (buildTool != _radiusPieceTable) { RefreshTerrainRadiusBrushes(buildTool); } if ((Object)(object)buildTool == (Object)null || buildTool.GetSelectedPiece() != piece) { return null; } if (!_terrainBrushes.TryGetValue(piece, out var value) || !value.IndicatorSynchronized || (Object)(object)value.TerrainOp == (Object)null) { return null; } if (!RevalidateTerrainRadiusBindings(force: true)) { RestoreScaledPlacementGhost(); return null; } if (!HasExpectedVanillaRadii(value.Kind, value.TerrainOp.m_settings) || (Object)(object)_scaledPlacementGhost == (Object)null || !_scaledPlacementGhost.activeInHierarchy || !AreIndicatorTransformsApplied()) { return null; } TerrainRadiusSelection terrainRadiusSelection = _terrainRadii[value.Kind]; if (Approximately(terrainRadiusSelection.Radius, value.VanillaRadius)) { return null; } RadiusPlacement radiusPlacement = new RadiusPlacement(value.Kind, terrainRadiusSelection.Radius, position, value.TerrainOp); radiusPlacement.ApplyAndActivate(); return radiusPlacement; } private void ResetTerrainRadiusRuntime() { RestoreScaledPlacementGhost(); foreach (TerrainBrushBinding value in _terrainBrushes.Values) { value.IndicatorSynchronized = false; } _terrainBrushes.Clear(); _radiusPieceTable = null; _radiusValidationFrame = -1; _radiusValidationResult = false; RestoreActiveRadiusPlacements(); _suppressCameraMouseWheelDepth = 0; } private bool AreIndicatorTransformsApplied() { if (_scaledIndicatorTransforms.Count == 0) { return false; } foreach (ScaledIndicatorTransform scaledIndicatorTransform in _scaledIndicatorTransforms) { if (!scaledIndicatorTransform.IsApplied()) { return false; } } return true; } private void RestoreScaledPlacementGhost() { foreach (ScaledIndicatorTransform scaledIndicatorTransform in _scaledIndicatorTransforms) { scaledIndicatorTransform.Restore(); } foreach (TerrainBrushBinding value in _terrainBrushes.Values) { value.IndicatorSynchronized = false; } _scaledIndicatorTransforms.Clear(); _scaledPlacementGhost = null; } private static bool Approximately(float left, float right) { if (!float.IsNaN(left) && !float.IsNaN(right)) { return Math.Abs(left - right) <= 0.0001f; } return false; } private static bool Approximately(Vector3 left, Vector3 right) { //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_0013: 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_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) if (Approximately(left.x, right.x) && Approximately(left.y, right.y)) { return Approximately(left.z, right.z); } return false; } private List<PieceTableEntryInspection> InspectPieceTable(PieceTable table) { //IL_01ca: Unknown result type (might be due to invalid IL or missing references) List<PieceTableEntryInspection> list = new List<PieceTableEntryInspection>(); if (table.m_pieces == null) { return list; } foreach (GameObject piece in table.m_pieces) { if ((Object)(object)piece == (Object)null) { continue; } Piece component = piece.GetComponent<Piece>(); TerrainOp component2 = piece.GetComponent<TerrainOp>(); Piece[] componentsInChildren = piece.GetComponentsInChildren<Piece>(true); TerrainOp[] componentsInChildren2 = piece.GetComponentsInChildren<TerrainOp>(true); TerrainModifier[] componentsInChildren3 = piece.GetComponentsInChildren<TerrainModifier>(true); Piece val = ((componentsInChildren.Length == 1 && componentsInChildren[0] == component) ? component : null); TerrainOp val2 = ((componentsInChildren2.Length == 1 && componentsInChildren2[0] == component2) ? component2 : null); Settings val3 = (((Object)(object)val2 != (Object)null) ? val2.m_settings : null); Requirement[] array = (((Object)(object)val != (Object)null) ? val.m_resources : null); int resourceRequirementCount = ((array != null) ? array.Length : 0); int num = 0; int num2 = 0; if (array != null) { Requirement[] array2 = array; foreach (Requirement val4 in array2) { if (val4 != null && !((Object)(object)val4.m_resItem == (Object)null) && val4.m_amount == 1) { num++; if (IsExpectedStoneResource(val4.m_resItem)) { num2++; } } } } bool hasStationRequirement = (Object)(object)val != (Object)null && (Object)(object)val.m_craftingStation != (Object)null; TerrainBrushShape brushShape = new TerrainBrushShape(componentsInChildren.Length, (Object)(object)val != (Object)null, componentsInChildren2.Length, (Object)(object)val2 != (Object)null, componentsInChildren3.Length, ((Object)piece).name, ((Object)(object)val != (Object)null) ? val.m_name : null, (Object)(object)val != (Object)null && val.m_canRotate, val3?.m_level ?? false, val3?.m_raise ?? false, val3?.m_smooth ?? false, val3?.m_paintCleared ?? false, (val3 == null) ? (-1) : ((int)val3.m_paintType), hasStationRequirement, resourceRequirementCount, num, num2); list.Add(new PieceTableEntryInspection(val, componentsInChildren2, brushShape)); } return list; } private static bool IsExpectedStoneResource(ItemDrop resource) { if ((Object)(object)resource == (Object)null || (Object)(object)((Component)resource).gameObject == (Object)null) { return false; } string name = ((Object)((Component)resource).gameObject).name; if (!string.Equals(name, "Stone", StringComparison.Ordinal)) { return string.Equals(name, "Stone(Clone)", StringComparison.Ordinal); } return true; } private static bool IsLikelyHoePieceTable(PieceTable table) { string a = (((Object)(object)table != (Object)null && (Object)(object)((Component)table).gameObject != (Object)null) ? ((Object)((Component)table).gameObject).name : null); if (!string.Equals(a, "_HoePieceTable", StringComparison.Ordinal)) { return string.Equals(a, "_HoePieceTable(Clone)", StringComparison.Ordinal); } return true; } private static void RestoreActiveRadiusPlacements() { while (_activeRadiusPlacements != null && _activeRadiusPlacements.Count != 0) { _activeRadiusPlacements.Peek().Restore(_activeModule?.Log); } _activeRadiusPlacements = null; } private static void GetRunSpeedFactorPostfix(Player __instance, ref float __result) { RoadFeatureModule activeModule = _activeModule; if (activeModule != null && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && ((Character)__instance).IsRunning()) { __result *= activeModule.CurrentTuning().SpeedMultiplier(activeModule.ResolveSurface(__instance)); } } private static void ModifyRunStaminaDrainPostfix(Character ___m_character, ref float drain) { RoadFeatureModule activeModule = _activeModule; Player val = (Player)(object)((___m_character is Player) ? ___m_character : null); if (activeModule != null && !((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { drain *= activeModule.CurrentTuning().StaminaMultiplier(activeModule.ResolveSurface(val)); } } private RoadTuning CurrentTuning() { return new RoadTuning(_dirtSpeed.Value, _dirtStamina.Value, _pavedSpeed.Value, _pavedStamina.Value); } private RoadSurface ResolveSurface(Player player) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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) double nowSeconds = Time.unscaledTime; if (!((Character)player).IsOnGround()) { return _surfaceTracker.Observe(TerrainSurface.NonTerrain, nowSeconds); } object? obj = GetLastGroundColliderMethod?.Invoke(player, null); Component val = (Component)((obj is Component) ? obj : null); if ((Object)(object)val == (Object)null) { return _surfaceTracker.Observe(TerrainSurface.NonTerrain, nowSeconds); } Heightmap val2 = val.GetComponent<Heightmap>() ?? val.GetComponentInParent<Heightmap>(); if ((Object)(object)val2 == (Object)null) { return _surfaceTracker.Observe(TerrainSurface.NonTerrain, nowSeconds); } Color paintMask = val2.GetPaintMask(((Component)player).transform.position); TerrainSurface observed = TerrainClassifier.Classify(paintMask.r, paintMask.g, paintMask.b); return _surfaceTracker.Observe(observed, nowSeconds); } } [BepInPlugin("com.jstack424.treadwell", "Treadwell", "1.0.1")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.jstack424.treadwell"; public const string PluginName = "Treadwell"; public const string PluginVersion = "1.0.1"; private FeatureHost _features; private void Awake() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, 100f); ConfigEntry<bool> enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enable mod", true, "Master switch for all Treadwell features."); ConfigEntry<float> dirtSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Road bonuses", "Dirt sprint speed bonus (%)", 10f, new ConfigDescription("Extra sprint speed on vanilla dirt paths.", (AcceptableValueBase)(object)val, Array.Empty<object>())); ConfigEntry<float> dirtStamina = ((BaseUnityPlugin)this).Config.Bind<float>("Road bonuses", "Dirt sprint stamina reduction (%)", 10f, new ConfigDescription("Reduction to sprint stamina drain on vanilla dirt paths.", (AcceptableValueBase)(object)val, Array.Empty<object>())); ConfigEntry<float> pavedSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Road bonuses", "Paved sprint speed bonus (%)", 20f, new ConfigDescription("Extra sprint speed on vanilla paved roads.", (AcceptableValueBase)(object)val, Array.Empty<object>())); ConfigEntry<float> pavedStamina = ((BaseUnityPlugin)this).Config.Bind<float>("Road bonuses", "Paved sprint stamina reduction (%)", 20f, new ConfigDescription("Reduction to sprint stamina drain on vanilla paved roads.", (AcceptableValueBase)(object)val, Array.Empty<object>())); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Treadwell 1.0.1 (1948087b100c762de2d66d7a53312765ce279bc1) loading."); _features = new FeatureHost(new IFeatureModule[1] { new RoadFeatureModule(enabled, dirtSpeed, dirtStamina, pavedSpeed, pavedStamina, ((BaseUnityPlugin)this).Logger) }, ((BaseUnityPlugin)this).Logger); CompatibilityResult compatibilityResult = CompatibilityGate.Evaluate(_features); if (!compatibilityResult.IsCompatible) { ((BaseUnityPlugin)this).Logger.LogError((object)("Compatibility gate failed. Treadwell is fully disabled before any gameplay hooks were installed: " + compatibilityResult.Reason)); _features.Dispose(); _features = null; return; } try { _features.Start(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Compatibility gate passed: " + compatibilityResult.Reason + "; configured feature modules are enabled.")); } catch (Exception ex) { _features.Stop(); ((BaseUnityPlugin)this).Logger.LogError((object)("Treadwell disabled because feature installation failed: " + ex)); } } private void OnDisable() { _features?.Stop(); } private void OnDestroy() { _features?.Dispose(); _features = null; } } internal sealed class TerrainRadiusControlHint { internal const string ObjectName = "TreadwellTerrainRadiusControlHint"; internal const string ActionText = "Change Size"; internal const string ShortcutText = "Alt + Scroll"; private KeyHints _owner; private KeyHints _failedOwner; private GameObject _hint; internal void Update(KeyHints owner, bool visible) { if ((Object)(object)owner == (Object)null) { Detach(); return; } if ((Object)(object)_hint != (Object)null && _owner != owner) { Detach(); } if (_failedOwner == owner) { return; } if ((Object)(object)_failedOwner != (Object)null) { _failedOwner = null; } if (!visible) { if ((Object)(object)_hint != (Object)null) { _hint.SetActive(false); } return; } if ((Object)(object)_hint == (Object)null) { Attach(owner); } if ((Object)(object)_hint != (Object)null) { _hint.SetActive(true); } } internal void FailForOwner(KeyHints owner) { try { Detach(); } finally { _failedOwner = owner; } } internal void Detach() { KeyHints owner = _owner; GameObject hint = _hint; _owner = null; _failedOwner = null; _hint = null; Exception ex = null; try { if ((Object)(object)hint != (Object)null) { hint.SetActive(false); } } catch (Exception ex2) { ex = ex2; } try { if ((Object)(object)hint != (Object)null) { Object.Destroy((Object)(object)hint); } } catch (Exception ex3) { if (ex == null) { ex = ex3; } } try { DestroyStaleOwnedHints(owner, hint); } catch (Exception ex4) { if (ex == null) { ex = ex4; } } if (ex != null) { throw ex; } } private void Attach(KeyHints owner) { //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Expected O, but got Unknown Detach(); DestroyStaleOwnedHints(owner, null); GameObject buildHints = owner.m_buildHints; TextMeshProUGUI buildAlternativePlacingKey = owner.m_buildAlternativePlacingKey; GameObject val = (((Object)(object)buildAlternativePlacingKey != (Object)null) ? ((Component)buildAlternativePlacingKey).gameObject : null); Transform val2 = (((Object)(object)buildHints != (Object)null) ? buildHints.transform : null); UIInputHint val3 = (((Object)(object)buildHints != (Object)null) ? buildHints.GetComponent<UIInputHint>() : null); GameObject val4 = (((Object)(object)val3 != (Object)null) ? val3.m_gamepadHint : null); GameObject val5 = (((Object)(object)val3 != (Object)null) ? val3.m_mouseKeyboardHint : null); Transform val6 = (((Object)(object)val4 != (Object)null) ? val4.transform : null); Transform val7 = (((Object)(object)val5 != (Object)null) ? val5.transform : null); Transform val8 = (((Object)(object)val != (Object)null) ? val.transform : null); LayoutElement val9 = (((Object)(object)val != (Object)null) ? val.GetComponent<LayoutElement>() : null); if (!((Object)(object)val2 != (Object)null) || !((Object)(object)val8 != (Object)null) || !((Object)(object)val6 != (Object)null) || !((Object)(object)val7 != (Object)null) || !TerrainRadiusHintLayoutRouting.CanAttach(val6 != val7, val6.parent == val2, val7.parent == val2, val8.parent == val6, (Object)(object)val4.GetComponent<HorizontalLayoutGroup>() != (Object)null, (Object)(object)val5.GetComponent<HorizontalLayoutGroup>() != (Object)null, (Object)(object)val9 != (Object)null && !val9.ignoreLayout)) { _failedOwner = owner; return; } GameObject val10 = (GameObject)Object.Instantiate((Object)(object)val, val7, false); _owner = owner; _hint = val10; TMP_Text component = val10.GetComponent<TMP_Text>(); if ((Object)(object)component == (Object)null) { throw new InvalidOperationException("the cloned vanilla placement hint contains no TMP text"); } Localization instance = Localization.instance; if (instance != null) { instance.RemoveTextFromCache(component); } component.text = "Alt + Scroll — Change Size"; ((Object)val10).name = "TreadwellTerrainRadiusControlHint"; val10.transform.SetAsLastSibling(); val10.SetActive(false); } private static void DestroyStaleOwnedHints(KeyHints owner, GameObject retained) { if ((Object)(object)owner == (Object)null || (Object)(object)owner.m_buildHints == (Object)null) { return; } Transform[] componentsInChildren = owner.m_buildHints.GetComponentsInChildren<Transform>(true); foreach (Transform val in componentsInChildren) { GameObject val2 = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : null); if ((Object)(object)val2 != (Object)null && val2 != retained && string.Equals(((Object)val2).name, "TreadwellTerrainRadiusControlHint", StringComparison.Ordinal)) { Object.Destroy((Object)(object)val2); } } } } internal static class GeneratedBuildInfo { public const string Version = "1.0.1"; public const string Commit = "1948087b100c762de2d66d7a53312765ce279bc1"; } } namespace Treadwell.Core { public enum TerrainSurface { Natural, DirtPath, Cultivated, PavedRoad, NonTerrain } public enum RoadSurface { None, Dirt, Paved } public static class TerrainClassifier { public const float MinimumPaintStrength = 0.1f; public const float DominanceMargin = 0.02f; public static TerrainSurface Classify(float red, float green, float blue) { if (!IsFinite(red) || !IsFinite(green) || !IsFinite(blue)) { return TerrainSurface.Natural; } if (green >= 0.1f && green >= red - 0.02f && green >= blue - 0.02f) { return TerrainSurface.Cultivated; } if (blue >= 0.1f && blue > red + 0.02f && blue > green + 0.02f) { return TerrainSurface.PavedRoad; } if (red >= 0.1f && red > green + 0.02f && red > blue + 0.02f) { return TerrainSurface.DirtPath; } return TerrainSurface.Natural; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public sealed class RoadTuning { public float DirtSpeedPercent { get; } public float DirtStaminaReductionPercent { get; } public float PavedSpeedPercent { get; } public float PavedStaminaReductionPercent { get; } public RoadTuning(float dirtSpeedPercent, float dirtStaminaReductionPercent, float pavedSpeedPercent, float pavedStaminaReductionPercent) { DirtSpeedPercent = ClampPercent(dirtSpeedPercent); DirtStaminaReductionPercent = ClampPercent(dirtStaminaReductionPercent); PavedSpeedPercent = ClampPercent(pavedSpeedPercent); PavedStaminaReductionPercent = ClampPercent(pavedStaminaReductionPercent); } public float SpeedMultiplier(RoadSurface surface) { return surface switch { RoadSurface.Dirt => 1f + DirtSpeedPercent / 100f, RoadSurface.Paved => 1f + PavedSpeedPercent / 100f, _ => 1f, }; } public float StaminaMultiplier(RoadSurface surface) { return surface switch { RoadSurface.Dirt => 1f - DirtStaminaReductionPercent / 100f, RoadSurface.Paved => 1f - PavedStaminaReductionPercent / 100f, _ => 1f, }; } public static float ClampPercent(float value) { if (float.IsNaN(value) || float.IsNegativeInfinity(value)) { return 0f; } if (float.IsPositiveInfinity(value)) { return 100f; } if (value < 0f) { return 0f; } if (!(value > 100f)) { return value; } return 100f; } } public sealed class RoadSurfaceTracker { private readonly double _naturalGapHoldSeconds; private RoadSurface _active; private double _lastRoadTime = double.NegativeInfinity; private double _lastObservationTime = double.NegativeInfinity; public RoadSurfaceTracker(double naturalGapHoldSeconds) { if (double.IsNaN(naturalGapHoldSeconds) || double.IsInfinity(naturalGapHoldSeconds) || naturalGapHoldSeconds < 0.0) { throw new ArgumentOutOfRangeException("naturalGapHoldSeconds"); } _naturalGapHoldSeconds = naturalGapHoldSeconds; } public RoadSurface Observe(TerrainSurface observed, double nowSeconds) { if (double.IsNaN(nowSeconds) || double.IsInfinity(nowSeconds) || nowSeconds < _lastObservationTime) { Reset(); return RoadSurface.None; } _lastObservationTime = nowSeconds; switch (observed) { case TerrainSurface.DirtPath: case TerrainSurface.PavedRoad: _active = ((observed == TerrainSurface.DirtPath) ? RoadSurface.Dirt : RoadSurface.Paved); _lastRoadTime = nowSeconds; return _active; case TerrainSurface.Cultivated: case TerrainSurface.NonTerrain: ResetAt(nowSeconds); return RoadSurface.None; default: if (_active != RoadSurface.None && nowSeconds - _lastRoadTime <= _naturalGapHoldSeconds) { return _active; } ResetAt(nowSeconds); return RoadSurface.None; } } public void Reset() { ResetAt(double.NegativeInfinity); } private void ResetAt(double observationTime) { _active = RoadSurface.None; _lastRoadTime = double.NegativeInfinity; _lastObservationTime = observationTime; } } public static class ExactRuntimeContract { public static MethodInfo[] FindMethods(Type type, string name, BindingFlags flags, Type returnType, IReadOnlyList<Type> parameterTypes, Func<MethodInfo, bool>? additionalCheck = null) { return (from method in type.GetMethods(flags) where string.Equals(method.Name, name, StringComparison.Ordinal) where method.ReturnType == returnType where ParametersEqual(method.GetParameters(), parameterTypes) where additionalCheck == null || additionalCheck(method) select method).ToArray(); } public static ConstructorInfo[] FindConstructors(Type type, BindingFlags flags, IReadOnlyList<Type> parameterTypes) { return (from constructor in type.GetConstructors(flags) where ParametersEqual(constructor.GetParameters(), parameterTypes) select constructor).ToArray(); } public static FieldInfo[] FindFields(Type type, string name, BindingFlags flags, Type fieldType) { return (from field in type.GetFields(flags) where string.Equals(field.Name, name, StringComparison.Ordinal) where field.FieldType == fieldType select field).ToArray(); } public static PropertyInfo[] FindProperties(Type type, string name, BindingFlags flags, Type propertyType) { return (from property in type.GetProperties(flags) where string.Equals(property.Name, name, StringComparison.Ordinal) where property.PropertyType == propertyType select property).ToArray(); } private static bool ParametersEqual(ParameterInfo[] observed, IReadOnlyList<Type> expected) { if (observed.Length != expected.Count) { return false; } for (int i = 0; i < observed.Length; i++) { if (observed[i].ParameterType != expected[i]) { return false; } } return true; } } public static class TransactionalInstall { public static void Run(Action install, params Action[] rollbackSteps) { if (install == null) { throw new ArgumentNullException("install"); } if (rollbackSteps == null) { throw new ArgumentNullException("rollbackSteps"); } try { install(); } catch (Exception item) { List<Exception> list = new List<Exception> { item }; foreach (Action action in rollbackSteps) { if (action != null) { try { action(); } catch (Exception item2) { list.Add(item2); } } } if (list.Count > 1) { throw new AggregateException("Installation failed and rollback was incomplete.", list); } throw; } } } public enum TerrainBrushKind { None, LevelGround, Pathen, PavedRoad } public readonly struct TerrainBrushShape { public int PieceCount { get; } public bool HasRootPiece { get; } public int TerrainOpCount { get; } public bool HasRootTerrainOp { get; } public int TerrainModifierCount { get; } public string PrefabName { get; } public string PieceName { get; } public bool CanRotate { get; } public bool Level { get; } public bool Raise { get; } public bool Smooth { get; } public bool PaintCleared { get; } public int PaintType { get; } public bool HasStationRequirement { get; } public int ResourceRequirementCount { get; } public int SingleUnitResourceRequirementCount { get; } public int SingleUnitStoneResourceRequirementCount { get; } public TerrainBrushShape(int pieceCount, bool hasRootPiece, int terrainOpCount, bool hasRootTerrainOp, int terrainModifierCount, string prefabName, string pieceName, bool canRotate, bool level, bool raise, bool smooth, bool paintCleared, int paintType, bool hasStationRequirement, int resourceRequirementCount, int singleUnitResourceRequirementCount, int singleUnitStoneResourceRequirementCount) { PieceCount = pieceCount; HasRootPiece = hasRootPiece; TerrainOpCount = terrainOpCount; HasRootTerrainOp = hasRootTerrainOp; TerrainModifierCount = terrainModifierCount; PrefabName = prefabName; PieceName = pieceName; CanRotate = canRotate; Level = level; Raise = raise; Smooth = smooth; PaintCleared = paintCleared; PaintType = paintType; HasStationRequirement = hasStationRequirement; ResourceRequirementCount = resourceRequirementCount; SingleUnitResourceRequirementCount = singleUnitResourceRequirementCount; SingleUnitStoneResourceRequirementCount = singleUnitStoneResourceRequirementCount; } } public static class VanillaTerrainBrushClassifier { public const int DirtPaintType = 0; public const int PavedPaintType = 2; public static TerrainBrushKind Classify(TerrainBrushShape shape) { if (shape.PieceCount != 1 || !shape.HasRootPiece || shape.TerrainOpCount != 1 || !shape.HasRootTerrainOp || shape.TerrainModifierCount != 0 || shape.CanRotate) { return TerrainBrushKind.None; } if (shape.Level || shape.Raise || !shape.PaintCleared) { return TerrainBrushKind.None; } if (MatchesPieceIdentity(shape, "$piece_levelground") && shape.Smooth && shape.PaintType == 0 && !shape.HasStationRequirement && shape.ResourceRequirementCount == 0) { return TerrainBrushKind.LevelGround; } if (MatchesPieceIdentity(shape, "$piece_path") && !shape.Smooth && shape.PaintType == 0 && !shape.HasStationRequirement && shape.ResourceRequirementCount == 0) { return TerrainBrushKind.Pathen; } if (MatchesPieceIdentity(shape, "$piece_pavedroad") && shape.Smooth && shape.PaintType == 2 && shape.HasStationRequirement && shape.ResourceRequirementCount == 1 && shape.SingleUnitResourceRequirementCount == 1 && shape.SingleUnitStoneResourceRequirementCount == 1) { return TerrainBrushKind.PavedRoad; } return TerrainBrushKind.None; } private static bool MatchesPieceIdentity(TerrainBrushShape shape, string pieceName) { return string.Equals(shape.PieceName, pieceName, StringComparison.Ordinal); } } public readonly struct TerrainRadiusSelection { public const float MinimumRadius = 1f; public const float MaximumRadius = 10f; public const float LevelGroundMaximumRadius = 8f; public const float Step = 0.5f; private readonly float _maximumRadius; public float Radius { get; } public float Maximum => _maximumRadius; public TerrainRadiusSelection(float radius) : this(radius, 10f) { } public TerrainRadiusSelection(TerrainBrushKind kind, float radius) : this(radius, MaximumFor(kind)) { } private TerrainRadiusSelection(float radius, float maximumRadius) { _maximumRadius = maximumRadius; Radius = ClampAndQuantize(radius, 2f, maximumRadius); } public TerrainRadiusSelection Scroll(float wheelDelta) { if (float.IsNaN(wheelDelta) || wheelDelta == 0f) { return this; } return new TerrainRadiusSelection(Radius + ((wheelDelta > 0f) ? 0.5f : (-0.5f)), _maximumRadius); } public static float MaximumFor(TerrainBrushKind kind) { if (kind != TerrainBrushKind.LevelGround) { return 10f; } return 8f; } public float ScaleFor(float vanillaRadius) { if (!IsFinitePositive(vanillaRadius)) { return 1f; } return Radius / vanillaRadius; } private static float ClampAndQuantize(float value, float fallback, float maximumRadius) { if (float.IsNaN(value) || float.IsInfinity(value)) { value = fallback;