Decompiled source of DSPOutputCap v0.1.0

plugins/DSPOutputCap.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace DSPOutputCap;

[BepInPlugin("dsp.outputcap", "DSP Output Stack Cap", "9.0.0")]
public class OutputCapPlugin : BaseUnityPlugin
{
	public const string GUID = "dsp.outputcap";

	internal static ManualLogSource Log;

	internal static ConfigEntry<int> LimitSmelt;

	internal static ConfigEntry<int> LimitAssemble;

	internal static ConfigEntry<int> LimitOther;

	internal static ConfigEntry<bool> VerboseLogging;

	internal static ConfigEntry<KeyCode> PanelKey;

	internal static ConfigEntry<bool> ShowPanelOnStart;

	internal static int LimSmelt = 5;

	internal static int LimAssemble = 5;

	internal static int LimOther = 5;

	private Harmony _harmony;

	private static FieldInfo _asmProducedField;

	private static FieldInfo _asmReplicatingField;

	internal static int BlockedCycles;

	private bool _panelVisible;

	private bool _panelShownOnce;

	private Rect _panelRect = new Rect(24f, 24f, 390f, 0f);

	private bool _configDirty;

	private GUIStyle _boxStyle;

	[ThreadStatic]
	private static HashSet<int> _stoppedEntityIds;

	private static int _powerZeroed;

	private static bool _powerDumped;

	private static int _powerPostfixCalls;

	[ThreadStatic]
	private static PlanetFactory _cachedFactory;

	private void Awake()
	{
		//IL_0159: Unknown result type (might be due to invalid IL or missing references)
		//IL_0163: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		LimitSmelt = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "Smelt", 5, "冶炼类设备(电弧熔炉等,ERecipeType.Smelt)的堆积上限。达到该数量即进入游戏自带的「产物堆积」状态:停产、停止耗电,物品被取走后自动恢复。原版容量为 100 个。");
		LimitAssemble = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "Assemble", 5, "制造台的堆积上限(ERecipeType.Assemble)。原版容量为 productCounts x 9。");
		LimitOther = ((BaseUnityPlugin)this).Config.Bind<int>("Limits", "Other", 5, "其他配方类型的堆积上限(化工厂、精炼厂、粒子对撞机等)。原版容量为 productCounts x 19。");
		VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Verbose", false, "输出更详细的日志,仅供排查使用。");
		PanelKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("InGamePanel", "ToggleKey", (KeyCode)288, "显示/隐藏游戏内设置面板的按键。");
		ShowPanelOnStart = ((BaseUnityPlugin)this).Config.Bind<bool>("InGamePanel", "ShowOnStart", false, "读档后自动显示设置面板。");
		SyncLimits();
		_asmProducedField = typeof(AssemblerComponent).GetField("produced");
		_asmReplicatingField = typeof(AssemblerComponent).GetField("replicating");
		Log.LogInfo((object)("字段缓存 -- produced=" + (_asmProducedField != null) + ", replicating=" + (_asmReplicatingField != null)));
		_harmony = new Harmony("dsp.outputcap");
		PatchAll();
		Log.LogInfo((object)("已加载 v9.0.0(公式改写 + 停产时电力归零)。限制值 -- 冶炼=" + LimSmelt + "(未生效), 制造=" + LimAssemble + ", 其他=" + LimOther));
	}

	private static void SyncLimits()
	{
		LimSmelt = LimitSmelt.Value;
		LimAssemble = LimitAssemble.Value;
		LimOther = LimitOther.Value;
	}

	private void PatchAll()
	{
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Expected O, but got Unknown
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Expected O, but got Unknown
		//IL_018f: Unknown result type (might be due to invalid IL or missing references)
		//IL_019c: Expected O, but got Unknown
		MethodInfo methodInfo = FindInstanceMethod(typeof(AssemblerComponent), "InternalUpdate");
		if (methodInfo == null)
		{
			Log.LogError((object)"未找到 AssemblerComponent.InternalUpdate,插件无法工作。");
			return;
		}
		try
		{
			MethodInfo method = typeof(OutputCapPlugin).GetMethod("CapTranspiler", BindingFlags.Static | BindingFlags.NonPublic);
			if (method != null)
			{
				_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"公式改写已应用: AssemblerComponent.InternalUpdate");
			}
			else
			{
				Log.LogWarning((object)"未找到 CapTranspiler,跳过公式改写。");
			}
		}
		catch (Exception ex)
		{
			Log.LogError((object)("公式改写失败(前缀仍会工作): " + ex.Message));
		}
		try
		{
			MethodInfo method2 = typeof(OutputCapPlugin).GetMethod("AssemblerPrefix", BindingFlags.Static | BindingFlags.NonPublic);
			if (method2 != null)
			{
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"停产前缀已应用: AssemblerComponent.InternalUpdate");
			}
			else
			{
				Log.LogWarning((object)"未找到 AssemblerPrefix。");
			}
		}
		catch (Exception ex2)
		{
			Log.LogError((object)("停产前缀挂载失败: " + ex2.Message));
		}
		try
		{
			MethodInfo methodInfo2 = FindMethod(typeof(PowerConsumerComponent), "SetRequiredEnergy", new Type[2]
			{
				typeof(bool),
				typeof(int)
			});
			if (methodInfo2 != null)
			{
				MethodInfo method3 = typeof(OutputCapPlugin).GetMethod("PowerPostfix", BindingFlags.Static | BindingFlags.NonPublic);
				if (method3 != null)
				{
					_harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)"电力归零补丁已应用: PowerConsumerComponent.SetRequiredEnergy(bool,int)");
				}
			}
			else
			{
				Log.LogWarning((object)"未找到 SetRequiredEnergy(bool,int),电力归零不可用。");
			}
		}
		catch (Exception ex3)
		{
			Log.LogError((object)("电力归零补丁挂载失败: " + ex3.Message));
		}
	}

	private static MethodInfo FindMethod(Type type, string name, Type[] paramTypes)
	{
		try
		{
			return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, paramTypes, null);
		}
		catch
		{
			return null;
		}
	}

	private static void PowerPostfix(ref PowerConsumerComponent __instance)
	{
		if (_stoppedEntityIds == null || !_stoppedEntityIds.Contains(__instance.entityId))
		{
			return;
		}
		try
		{
			__instance.requiredEnergy = 0L;
			_powerZeroed++;
			_powerPostfixCalls++;
		}
		catch
		{
		}
	}

	private static void DumpPowerOnce(int entityId)
	{
		if (_powerDumped || VerboseLogging == null || !VerboseLogging.Value)
		{
			return;
		}
		try
		{
			PlanetFactory val = FindFactoryFor(entityId);
			if (val == null)
			{
				return;
			}
			PowerSystem powerSystem = val.powerSystem;
			if (powerSystem == null)
			{
				return;
			}
			PowerConsumerComponent[] consumerPool = powerSystem.consumerPool;
			if (consumerPool == null)
			{
				return;
			}
			for (int i = 0; i < consumerPool.Length; i++)
			{
				if (consumerPool[i].entityId == entityId)
				{
					_powerDumped = true;
					Log.LogInfo((object)("【电力诊断】entityId=" + entityId + " 运转档=" + consumerPool[i].workEnergyPerTick + " 待机档=" + consumerPool[i].idleEnergyPerTick + " 当前需求=" + consumerPool[i].requiredEnergy));
					break;
				}
			}
		}
		catch
		{
		}
	}

	private static MethodInfo FindInstanceMethod(Type type, string name)
	{
		MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		for (int i = 0; i < methods.Length; i++)
		{
			if (methods[i].Name == name)
			{
				return methods[i];
			}
		}
		return null;
	}

	private static IEnumerable<CodeInstruction> CapTranspiler(IEnumerable<CodeInstruction> instructions)
	{
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: Expected O, but got Unknown
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		MethodInfo method = typeof(OutputCapPlugin).GetMethod("LimitForAssemble", BindingFlags.Static | BindingFlags.NonPublic);
		MethodInfo method2 = typeof(OutputCapPlugin).GetMethod("LimitForOther", BindingFlags.Static | BindingFlags.NonPublic);
		int num = 0;
		int num2 = 0;
		for (int i = 0; i < list.Count - 1; i++)
		{
			CodeInstruction val = list[i];
			if (!(val.opcode != OpCodes.Ldc_I4_S) && !(list[i + 1].opcode != OpCodes.Mul))
			{
				int num3;
				try
				{
					num3 = Convert.ToInt32(val.operand);
				}
				catch
				{
					continue;
				}
				if (num3 == 9 && method != null)
				{
					list[i] = new CodeInstruction(OpCodes.Call, (object)method);
					num++;
				}
				else if (num3 == 19 && method2 != null)
				{
					list[i] = new CodeInstruction(OpCodes.Call, (object)method2);
					num2++;
				}
			}
		}
		Log.LogInfo((object)("IL 改写完成:制造乘数 " + num + " 处,默认乘数 " + num2 + " 处。"));
		return list;
	}

	private static int LimitForAssemble()
	{
		return LimAssemble;
	}

	private static int LimitForOther()
	{
		return LimOther;
	}

	private static PlanetFactory FindFactoryFor(int entityId)
	{
		try
		{
			if (_cachedFactory != null)
			{
				FactorySystem factorySystem = _cachedFactory.factorySystem;
				if (factorySystem != null && OwnsEntity(factorySystem, entityId))
				{
					return _cachedFactory;
				}
			}
			GameData data = GameMain.data;
			if (data == null || data.factories == null)
			{
				return null;
			}
			for (int i = 0; i < data.factories.Length; i++)
			{
				PlanetFactory val = data.factories[i];
				if (val == null)
				{
					continue;
				}
				FactorySystem factorySystem2 = val.factorySystem;
				if (factorySystem2 == null)
				{
					continue;
				}
				AssemblerComponent[] assemblerPool = factorySystem2.assemblerPool;
				if (assemblerPool == null)
				{
					continue;
				}
				for (int j = 0; j < assemblerPool.Length; j++)
				{
					if (assemblerPool[j].id != 0 && assemblerPool[j].entityId == entityId)
					{
						_cachedFactory = val;
						return val;
					}
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("定位星球工厂失败: " + ex.Message));
		}
		return null;
	}

	private static bool OwnsEntity(FactorySystem fs, int entityId)
	{
		AssemblerComponent[] assemblerPool = fs.assemblerPool;
		if (assemblerPool == null)
		{
			return false;
		}
		for (int i = 0; i < assemblerPool.Length; i++)
		{
			if (assemblerPool[i].id != 0 && assemblerPool[i].entityId == entityId)
			{
				return true;
			}
		}
		return false;
	}

	private static void ZeroPower(int entityId)
	{
		try
		{
			PlanetFactory val = FindFactoryFor(entityId);
			if (val == null)
			{
				return;
			}
			PowerSystem powerSystem = val.powerSystem;
			if (powerSystem == null)
			{
				return;
			}
			PowerConsumerComponent[] consumerPool = powerSystem.consumerPool;
			if (consumerPool == null)
			{
				return;
			}
			for (int i = 0; i < consumerPool.Length; i++)
			{
				if (consumerPool[i].id != 0 && consumerPool[i].entityId == entityId)
				{
					consumerPool[i].requiredEnergy = 0L;
					break;
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("电力归零失败: " + ex.Message));
		}
	}

	private static bool AssemblerPrefix(AssemblerComponent __instance)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			int[] produced = GetProduced(__instance);
			if (produced == null)
			{
				return true;
			}
			if (_stoppedEntityIds == null)
			{
				_stoppedEntityIds = new HashSet<int>();
			}
			_stoppedEntityIds.Remove(__instance.entityId);
			int slots = ProductSlots(__instance.recipeExecuteData, produced);
			int num = ResolveLimit(__instance.recipeType);
			if (num < 0)
			{
				return true;
			}
			if (!IsAtLimit(produced, slots, num))
			{
				return true;
			}
			SetReplicatingFalse(__instance);
			ZeroPower(__instance.entityId);
			if (_stoppedEntityIds == null)
			{
				_stoppedEntityIds = new HashSet<int>();
			}
			_stoppedEntityIds.Add(__instance.entityId);
			DumpPowerOnce(__instance.entityId);
			BlockedCycles++;
			if (VerboseLogging != null && VerboseLogging.Value)
			{
				Log.LogInfo((object)string.Concat("停产:类型=", __instance.recipeType, " 已达上限 ", num, ",电力已归零。"));
			}
			return false;
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("停产判定异常(已放行): " + ex.Message));
			return true;
		}
	}

	private static void SetReplicatingFalse(AssemblerComponent instance)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!(_asmReplicatingField == null))
			{
				object obj = instance;
				_asmReplicatingField.SetValue(obj, false);
			}
		}
		catch
		{
		}
	}

	private static int ResolveLimit(ERecipeType type)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Invalid comparison between Unknown and I4
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Invalid comparison between Unknown and I4
		if ((int)type == 1)
		{
			return LimSmelt;
		}
		if ((int)type == 4)
		{
			return LimAssemble;
		}
		return LimOther;
	}

	private static int[] GetProduced(AssemblerComponent instance)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (_asmProducedField == null)
			{
				return null;
			}
			return (int[])_asmProducedField.GetValue(instance);
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("读取 produced 失败: " + ex.Message));
			return null;
		}
	}

	private static int ProductSlots(RecipeExecuteData red, int[] produced)
	{
		if (produced == null)
		{
			return 0;
		}
		if (red != null && red.products != null && red.products.Length > 0)
		{
			return Math.Min(red.products.Length, produced.Length);
		}
		return produced.Length;
	}

	private static bool IsAtLimit(int[] produced, int slots, int limit)
	{
		for (int i = 0; i < slots; i++)
		{
			if (produced[i] >= limit)
			{
				return true;
			}
		}
		return false;
	}

	private void Update()
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			KeyCode value = PanelKey.Value;
			if ((int)value != 0 && Input.GetKeyDown(value))
			{
				_panelVisible = !_panelVisible;
				Log.LogInfo((object)("设置面板 " + (_panelVisible ? "已显示" : "已隐藏")));
			}
			if (ShowPanelOnStart.Value && !_panelShownOnce && GameMain.data != null)
			{
				_panelShownOnce = true;
				_panelVisible = true;
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("热键处理出错: " + ex.Message));
			PanelKey.Value = (KeyCode)0;
		}
		if (_configDirty)
		{
			_configDirty = false;
			try
			{
				SyncLimits();
				((BaseUnityPlugin)this).Config.Save();
			}
			catch (Exception ex2)
			{
				Log.LogWarning((object)("配置保存失败: " + ex2.Message));
			}
		}
	}

	private void OnGUI()
	{
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_0072: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Expected O, but got Unknown
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Expected O, but got Unknown
		if (!_panelVisible)
		{
			return;
		}
		try
		{
			if (_boxStyle == null)
			{
				_boxStyle = new GUIStyle(GUI.skin.box);
				_boxStyle.normal.textColor = Color.white;
				_boxStyle.padding = new RectOffset(12, 12, 10, 10);
				_boxStyle.alignment = (TextAnchor)0;
			}
			_panelRect = GUILayout.Window(1146310723, _panelRect, new WindowFunction(DrawPanel), string.Concat("输出堆叠上限   [", PanelKey.Value, " 隐藏]"), (GUILayoutOption[])(object)new GUILayoutOption[0]);
		}
		catch (Exception ex)
		{
			_panelVisible = false;
			Log.LogWarning((object)("面板绘制出错: " + ex.Message));
		}
	}

	private void DrawPanel(int windowId)
	{
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical(_boxStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("达到上限后设备显示「产物堆积」并停止生产与耗电,", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("物品被运走后自动恢复。改动立即生效。", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Space(8f);
		DrawLimitRow("冶炼炉", LimitSmelt, "原版容量 100 个");
		DrawLimitRow("制造台", LimitAssemble, "原版容量 = 单次产出 x 9");
		DrawLimitRow("化工厂 / 精炼厂等", LimitOther, "原版容量 = 单次产出 x 19");
		GUILayout.Space(8f);
		GUILayout.Label("快捷设定(三类同时生效):", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("全部设为 5", (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			SetAllLimits(5);
		}
		if (GUILayout.Button("10", (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			SetAllLimits(10);
		}
		if (GUILayout.Button("20", (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			SetAllLimits(20);
		}
		if (GUILayout.Button("50", (GUILayoutOption[])(object)new GUILayoutOption[0]))
		{
			SetAllLimits(50);
		}
		GUILayout.EndHorizontal();
		GUILayout.Space(8f);
		GUILayout.Label("配置文件:BepInEx\\config\\dsp.outputcap.cfg", (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.EndVertical();
		GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _panelRect)).width, 22f));
	}

	private void DrawLimitRow(string label, ConfigEntry<int> entry, string hint)
	{
		GUILayout.Label(label, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button("-1", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }))
		{
			StepLimit(entry, -1);
		}
		if (GUILayout.Button("-10", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(46f) }))
		{
			StepLimit(entry, -10);
		}
		GUILayout.Label("  " + entry.Value + "  ", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) });
		if (GUILayout.Button("+1", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }))
		{
			StepLimit(entry, 1);
		}
		if (GUILayout.Button("+10", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(46f) }))
		{
			StepLimit(entry, 10);
		}
		if (GUILayout.Button("+100", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f) }))
		{
			StepLimit(entry, 100);
		}
		GUILayout.EndHorizontal();
		GUILayout.Label("    " + hint, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Space(6f);
	}

	private void StepLimit(ConfigEntry<int> entry, int delta)
	{
		int num = entry.Value + delta;
		if (num < 1)
		{
			num = 1;
		}
		if (num > 100000)
		{
			num = 100000;
		}
		entry.Value = num;
		_configDirty = true;
	}

	private void SetAllLimits(int value)
	{
		LimitSmelt.Value = value;
		LimitAssemble.Value = value;
		LimitOther.Value = value;
		_configDirty = true;
	}
}