Decompiled source of BuildModeNoAutoZoom v1.0.7

disableautozoom.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("DSP Build Mode No AutoZoom")]
[assembly: AssemblyDescription("Prevents build mode from changing the camera pose and extends maximum zoom-out distance.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DSP Build Mode No AutoZoom")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("0a889f29-f468-4803-b8f9-663f72777b5c")]
[assembly: AssemblyFileVersion("1.0.4.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.4.0")]
namespace BuildModeNoAutoZoom;

[BepInPlugin("lee.dsp.buildmode.noautozoom", "DSP Build Mode No AutoZoom", "1.0.7")]
public sealed class BuildModeNoAutoZoomPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "lee.dsp.buildmode.noautozoom";

	public const string PluginName = "DSP Build Mode No AutoZoom";

	public const string PluginVersion = "1.0.7";

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<float> ExtraMaxZoomOut;

	internal static ConfigEntry<int> ShiftClickPinFrames;

	internal static ConfigEntry<bool> DisableResearchAutoZoom;

	internal static ManualLogSource Log;

	private static bool _cameraPinAvailable;

	private void Awake()
	{
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable the mod.");
		ExtraMaxZoomOut = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ExtraMaxZoomOut", 2f, "Permanent extra maximum zoom-out distance in metres. Set to 0 to disable.");
		ShiftClickPinFrames = ((BaseUnityPlugin)this).Config.Bind<int>("General", "ShiftClickPinFrames", 8, "How long to preserve the camera pose while Shift-click enters build mode.");
		DisableResearchAutoZoom = ((BaseUnityPlugin)this).Config.Bind<bool>("Research Screen", "DisableAutoZoom", true, "Prevent selecting a technology from automatically zooming the research tree in.");
		Harmony harmony = new Harmony("lee.dsp.buildmode.noautozoom");
		_cameraPinAvailable = TryPatchCameraPin(harmony, out var status);
		TryPatchPoser(harmony, "RTSPoser", out var status2);
		TryPatchPoser(harmony, "PRTSPoser", out var status3);
		TryPatchResearchAutoZoom(harmony, out var status4);
		((BaseUnityPlugin)this).Logger.LogInfo((object)$"Compatibility: camera pin={status}; RTS zoom={status2}; PRTS zoom={status3}; research zoom={status4}.");
	}

	private void Update()
	{
		if (_cameraPinAvailable && Enabled.Value && Input.GetMouseButtonDown(0) && (Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303)) && !BlenderPinPatches.IsBlueprintModeActive())
		{
			BlenderPinPatches.OnShiftClick();
		}
	}

	private bool TryPatchCameraPin(Harmony harmony, out string status)
	{
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Expected O, but got Unknown
		if (!BlenderPinPatches.TryInitialize(out var error))
		{
			status = "disabled (" + error + ")";
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Camera pin disabled: " + error + "."));
			return false;
		}
		MethodInfo methodInfo = AccessTools.Method(typeof(CameraPoseBlender), "Calculate", (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			status = "disabled (Calculate missing)";
			((BaseUnityPlugin)this).Logger.LogWarning((object)"Camera pin disabled: CameraPoseBlender.Calculate was not found.");
			return false;
		}
		try
		{
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BlenderPinPatches), "CalculatePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			status = "enabled";
			return true;
		}
		catch (Exception ex)
		{
			status = "disabled (patch failed)";
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Camera pin disabled because its Harmony patch failed: " + ex.Message));
			return false;
		}
	}

	private bool TryPatchPoser(Harmony harmony, string typeName, out string status)
	{
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Expected O, but got Unknown
		Type type = typeof(CameraPoseBlender).Assembly.GetType(typeName, throwOnError: false);
		if (type == null)
		{
			status = "unavailable";
			return false;
		}
		MethodInfo methodInfo = AccessTools.Method(type, "Calculate", (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			status = "disabled (Calculate missing)";
			((BaseUnityPlugin)this).Logger.LogWarning((object)(typeName + " zoom extension disabled: Calculate missing."));
			return false;
		}
		if (!PoserFields.TryCreate(type, out var fields, out var error))
		{
			status = "disabled (" + error + ")";
			((BaseUnityPlugin)this).Logger.LogWarning((object)(typeName + " zoom extension disabled: " + error + "."));
			return false;
		}
		try
		{
			PermanentZoomPatches.Register(type, fields);
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PermanentZoomPatches), "CalculatePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			status = "enabled";
			return true;
		}
		catch (Exception ex)
		{
			PermanentZoomPatches.Unregister(type);
			status = "disabled (patch failed)";
			((BaseUnityPlugin)this).Logger.LogWarning((object)(typeName + " zoom extension disabled because its Harmony patch failed: " + ex.Message));
			return false;
		}
	}

	private bool TryPatchResearchAutoZoom(Harmony harmony, out string status)
	{
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Expected O, but got Unknown
		//IL_00a4: Expected O, but got Unknown
		MethodInfo methodInfo = AccessTools.PropertySetter(typeof(UITechTree), "selected");
		if (methodInfo == null)
		{
			status = "disabled (selected setter missing)";
			((BaseUnityPlugin)this).Logger.LogWarning((object)"Research auto-zoom prevention disabled: UITechTree.selected setter missing.");
			return false;
		}
		if (!ResearchAutoZoomPatch.TryInitialize(out var error))
		{
			status = "disabled (" + error + ")";
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Research auto-zoom prevention disabled: " + error + "."));
			return false;
		}
		try
		{
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ResearchAutoZoomPatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(ResearchAutoZoomPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			status = "enabled";
			return true;
		}
		catch (Exception ex)
		{
			status = "disabled (patch failed)";
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Research auto-zoom prevention disabled because its Harmony patch failed: " + ex.Message));
			return false;
		}
	}
}
internal static class ResearchAutoZoomPatch
{
	private static FieldRef<UITechTree, float> _viewScaleTarget;

	internal static bool TryInitialize(out string error)
	{
		FieldInfo fieldInfo = AccessTools.Field(typeof(UITechTree), "viewScaleTarget");
		if (fieldInfo == null || fieldInfo.FieldType != typeof(float))
		{
			error = "UITechTree.viewScaleTarget is missing or incompatible";
			return false;
		}
		try
		{
			_viewScaleTarget = AccessTools.FieldRefAccess<UITechTree, float>("viewScaleTarget");
			error = null;
			return true;
		}
		catch (Exception ex)
		{
			error = "UITechTree.viewScaleTarget accessor failed: " + ex.Message;
			return false;
		}
	}

	public static void Prefix(UITechTree __instance, out float __state)
	{
		__state = _viewScaleTarget.Invoke(__instance);
	}

	public static void Postfix(UITechTree __instance, UITechNode __0, float __state)
	{
		if ((Object)(object)__0 != (Object)null && BuildModeNoAutoZoomPlugin.Enabled.Value && BuildModeNoAutoZoomPlugin.DisableResearchAutoZoom.Value)
		{
			_viewScaleTarget.Invoke(__instance) = __state;
		}
	}
}
internal sealed class ReferenceComparer<T> : IEqualityComparer<T> where T : class
{
	internal static readonly ReferenceComparer<T> Instance = new ReferenceComparer<T>();

	public bool Equals(T x, T y)
	{
		return x == y;
	}

	public int GetHashCode(T obj)
	{
		return RuntimeHelpers.GetHashCode(obj);
	}
}
internal static class BlenderPinPatches
{
	private sealed class BlenderState
	{
		internal int LastNonBuildIndex;

		internal bool HasNonBuildIndex;

		internal int Baseline;

		internal bool PinActive;

		internal int ProtectedEpochApplied;
	}

	private static FieldRef<CameraPoseBlender, int> _index;

	private static readonly Dictionary<CameraPoseBlender, BlenderState> States = new Dictionary<CameraPoseBlender, BlenderState>(ReferenceComparer<CameraPoseBlender>.Instance);

	private static readonly List<CameraPoseBlender> DeadBlenders = new List<CameraPoseBlender>(64);

	private static bool _lastProtectedActive;

	private static int _protectedEpoch;

	private static int _forcedPinFrames;

	private static int _lastForcedPinFrame = -1;

	private static int _pruneCountdown = 600;

	internal static bool TryInitialize(out string error)
	{
		FieldInfo fieldInfo = AccessTools.Field(typeof(CameraPoseBlender), "index");
		if (fieldInfo == null || fieldInfo.FieldType != typeof(int))
		{
			error = "CameraPoseBlender.index is missing or incompatible";
			return false;
		}
		FieldInfo fieldInfo2 = AccessTools.Field(typeof(PlayerController), "actionBuild");
		if (fieldInfo2 == null || fieldInfo2.FieldType != typeof(PlayerAction_Build))
		{
			error = "PlayerController.actionBuild is missing or incompatible";
			return false;
		}
		if (GetMemberType((MemberInfo)(((object)AccessTools.Property(typeof(PlayerAction_Build), "active")) ?? ((object)(AccessTools.Field(typeof(PlayerAction_Build), "active") ?? AccessTools.Field(typeof(PlayerAction_Build), "<active>k__BackingField"))))) != typeof(bool))
		{
			error = "PlayerAction_Build.active is missing or incompatible";
			return false;
		}
		FieldInfo fieldInfo3 = AccessTools.Field(typeof(PlayerController), "cmd");
		if (fieldInfo3 == null || fieldInfo3.FieldType != typeof(CommandState))
		{
			error = "PlayerController.cmd is missing or incompatible";
			return false;
		}
		FieldInfo fieldInfo4 = AccessTools.Field(typeof(CommandState), "type");
		if (fieldInfo4 == null || fieldInfo4.FieldType != typeof(ECommand))
		{
			error = "CommandState.type is missing or incompatible";
			return false;
		}
		if (GetMemberType((MemberInfo)(((object)AccessTools.Property(typeof(PlayerAction_Build), "blueprintMode")) ?? ((object)(AccessTools.Field(typeof(PlayerAction_Build), "blueprintMode") ?? AccessTools.Field(typeof(PlayerAction_Build), "<blueprintMode>k__BackingField"))))) != typeof(EBlueprintMode))
		{
			error = "PlayerAction_Build.blueprintMode is missing or incompatible";
			return false;
		}
		FieldInfo fieldInfo5 = AccessTools.Field(typeof(UIGame), "viewMode");
		if (fieldInfo5 == null || fieldInfo5.FieldType != typeof(EViewMode))
		{
			error = "UIGame.viewMode is missing or incompatible";
			return false;
		}
		try
		{
			_index = AccessTools.FieldRefAccess<CameraPoseBlender, int>("index");
			error = null;
			return true;
		}
		catch (Exception ex)
		{
			error = "CameraPoseBlender.index accessor failed: " + ex.Message;
			return false;
		}
	}

	private static Type GetMemberType(MemberInfo member)
	{
		PropertyInfo propertyInfo = member as PropertyInfo;
		if (propertyInfo != null)
		{
			return propertyInfo.PropertyType;
		}
		FieldInfo fieldInfo = member as FieldInfo;
		if (!(fieldInfo == null))
		{
			return fieldInfo.FieldType;
		}
		return null;
	}

	internal static void OnShiftClick()
	{
		CameraPoseBlender[] array = Resources.FindObjectsOfTypeAll<CameraPoseBlender>();
		if (array == null)
		{
			return;
		}
		foreach (CameraPoseBlender val in array)
		{
			if (!((Object)(object)val == (Object)null))
			{
				BlenderState state = GetState(val);
				int baseline = (state.LastNonBuildIndex = _index.Invoke(val));
				state.HasNonBuildIndex = true;
				state.Baseline = baseline;
				state.PinActive = true;
			}
		}
		_forcedPinFrames = Math.Max(1, BuildModeNoAutoZoomPlugin.ShiftClickPinFrames.Value);
		_lastForcedPinFrame = Time.frameCount;
	}

	internal static bool IsBlueprintModeActive()
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Invalid comparison between Unknown and I4
		Player mainPlayer = GameMain.mainPlayer;
		PlayerAction_Build val = ((mainPlayer == null) ? null : mainPlayer.controller?.actionBuild);
		if (val != null)
		{
			return (int)val.blueprintMode > 0;
		}
		return false;
	}

	public static void CalculatePrefix(CameraPoseBlender __instance)
	{
		if ((Object)(object)__instance == (Object)null || !BuildModeNoAutoZoomPlugin.Enabled.Value)
		{
			return;
		}
		AdvanceForcedPinWindow();
		PruneDeadBlenders();
		bool flag = IsProtectedCameraModeActive();
		if (flag != _lastProtectedActive)
		{
			_lastProtectedActive = flag;
			if (flag)
			{
				_protectedEpoch++;
			}
		}
		BlenderState state = GetState(__instance);
		int num = _index.Invoke(__instance);
		if (!flag && _forcedPinFrames <= 0)
		{
			state.LastNonBuildIndex = num;
			state.HasNonBuildIndex = true;
			state.PinActive = false;
		}
		else if (!IsBlueprintModeActive())
		{
			if (flag && state.ProtectedEpochApplied != _protectedEpoch)
			{
				state.ProtectedEpochApplied = _protectedEpoch;
				state.Baseline = (state.HasNonBuildIndex ? state.LastNonBuildIndex : num);
				state.PinActive = true;
			}
			else if (!flag && _forcedPinFrames > 0 && !state.PinActive)
			{
				state.Baseline = (state.HasNonBuildIndex ? state.LastNonBuildIndex : num);
				state.PinActive = true;
			}
			if (state.PinActive && num != state.Baseline)
			{
				_index.Invoke(__instance) = state.Baseline;
			}
		}
	}

	private static BlenderState GetState(CameraPoseBlender blender)
	{
		if (!States.TryGetValue(blender, out var value))
		{
			value = new BlenderState();
			States.Add(blender, value);
		}
		return value;
	}

	private static bool IsProtectedCameraModeActive()
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Invalid comparison between Unknown and I4
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Invalid comparison between Unknown and I4
		Player mainPlayer = GameMain.mainPlayer;
		PlayerController val = ((mainPlayer != null) ? mainPlayer.controller : null);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		PlayerAction_Build actionBuild = val.actionBuild;
		if ((int)val.cmd.type != 5 && (int)UIGame.viewMode != 1)
		{
			if (actionBuild != null)
			{
				return actionBuild.active;
			}
			return false;
		}
		return true;
	}

	private static void AdvanceForcedPinWindow()
	{
		int frameCount = Time.frameCount;
		if (_forcedPinFrames > 0 && frameCount != _lastForcedPinFrame)
		{
			_forcedPinFrames--;
			_lastForcedPinFrame = frameCount;
		}
	}

	private static void PruneDeadBlenders()
	{
		if (--_pruneCountdown > 0)
		{
			return;
		}
		_pruneCountdown = 600;
		DeadBlenders.Clear();
		foreach (KeyValuePair<CameraPoseBlender, BlenderState> state in States)
		{
			if ((Object)(object)state.Key == (Object)null)
			{
				DeadBlenders.Add(state.Key);
			}
		}
		for (int i = 0; i < DeadBlenders.Count; i++)
		{
			States.Remove(DeadBlenders[i]);
		}
	}
}
internal sealed class PoserFields
{
	internal readonly FieldInfo Minimum;

	internal readonly FieldInfo Maximum;

	internal readonly FieldInfo Distance;

	internal readonly FieldInfo Coefficient;

	internal readonly FieldInfo WantedCoefficient;

	internal readonly FieldInfo BeginCoefficient;

	private PoserFields(Type type)
	{
		Minimum = AccessTools.Field(type, "distMin");
		Maximum = AccessTools.Field(type, "distMax");
		Distance = AccessTools.Field(type, "dist");
		Coefficient = AccessTools.Field(type, "distCoef");
		WantedCoefficient = GetOptionalFloatField(type, "distCoefWanted");
		BeginCoefficient = GetOptionalFloatField(type, "distCoefBegin");
	}

	internal static bool TryCreate(Type type, out PoserFields fields, out string error)
	{
		fields = new PoserFields(type);
		if (!IsFloat(fields.Minimum) || !IsFloat(fields.Maximum) || !IsFloat(fields.Distance) || !IsFloat(fields.Coefficient))
		{
			fields = null;
			error = "required distance fields are missing or incompatible";
			return false;
		}
		error = null;
		return true;
	}

	private static bool IsFloat(FieldInfo field)
	{
		if (field != null)
		{
			return field.FieldType == typeof(float);
		}
		return false;
	}

	private static FieldInfo GetOptionalFloatField(Type type, string name)
	{
		FieldInfo fieldInfo = AccessTools.Field(type, name);
		if (!IsFloat(fieldInfo))
		{
			return null;
		}
		return fieldInfo;
	}
}
internal static class PermanentZoomPatches
{
	private static readonly Dictionary<Type, PoserFields> Fields = new Dictionary<Type, PoserFields>(2);

	private static readonly HashSet<object> Completed = new HashSet<object>(ReferenceComparer<object>.Instance);

	private static readonly HashSet<Type> Failed = new HashSet<Type>();

	internal static void Register(Type type, PoserFields fields)
	{
		Fields[type] = fields;
	}

	internal static void Unregister(Type type)
	{
		Fields.Remove(type);
	}

	public static void CalculatePrefix(object __instance)
	{
		if (!BuildModeNoAutoZoomPlugin.Enabled.Value || __instance == null)
		{
			return;
		}
		float value = BuildModeNoAutoZoomPlugin.ExtraMaxZoomOut.Value;
		if (value <= 0.0001f || Completed.Contains(__instance))
		{
			return;
		}
		Type type = __instance.GetType();
		if (Failed.Contains(type) || !Fields.TryGetValue(type, out var value2))
		{
			return;
		}
		try
		{
			float num = (float)value2.Minimum.GetValue(__instance);
			float num2 = (float)value2.Maximum.GetValue(__instance);
			float num3 = (float)value2.Distance.GetValue(__instance);
			float num4 = num2 + value;
			float num5 = num4 - num;
			if (!(num5 <= 0.0001f))
			{
				float num6 = Mathf.Clamp01((num3 - num) / num5);
				value2.Maximum.SetValue(__instance, num4);
				value2.Coefficient.SetValue(__instance, num6);
				if (value2.WantedCoefficient != null)
				{
					value2.WantedCoefficient.SetValue(__instance, num6);
				}
				if (value2.BeginCoefficient != null)
				{
					value2.BeginCoefficient.SetValue(__instance, num6);
				}
				Completed.Add(__instance);
			}
		}
		catch (Exception ex)
		{
			Failed.Add(type);
			BuildModeNoAutoZoomPlugin.Log.LogWarning((object)(type.Name + " zoom extension disabled after a runtime compatibility failure: " + ex.Message));
		}
	}
}