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));
}
}
}