Decompiled source of SosigEngagementTweaks v1.0.0

plugins/SosigEngagementTweaks.dll

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

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

[BepInPlugin("SosigEngagementTweaks", "Sosig Engagement Tweaks", "1.0.0")]
public class EngagementTweaksPlugin : BaseUnityPlugin
{
	internal static class Originals
	{
		private const int MaxEntries = 20000;

		private static readonly Dictionary<int, float> Floats = new Dictionary<int, float>();

		private static readonly Dictionary<int, Vector2> Vectors = new Dictionary<int, Vector2>();

		internal static float Float(int id, string key, float current)
		{
			int key2 = Combine(id, key);
			if (Floats.TryGetValue(key2, out var value))
			{
				return value;
			}
			if (Floats.Count > 20000)
			{
				Floats.Clear();
			}
			Floats[key2] = current;
			return current;
		}

		internal static Vector2 Vector(int id, string key, Vector2 current)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			int key2 = Combine(id, key);
			if (Vectors.TryGetValue(key2, out var value))
			{
				return value;
			}
			if (Vectors.Count > 20000)
			{
				Vectors.Clear();
			}
			Vectors[key2] = current;
			return current;
		}

		private static int Combine(int id, string key)
		{
			return (id * 397) ^ key.GetHashCode();
		}
	}

	public const string PluginGuid = "SosigEngagementTweaks";

	public const string PluginName = "Sosig Engagement Tweaks";

	public const string PluginVersion = "1.0.0";

	internal static ManualLogSource Log;

	internal static ConfigEntry<float> CfgRecogMultiplier;

	internal static ConfigEntry<float> CfgThreatRangeMultiplier;

	internal static ConfigEntry<float> CfgWeaponRangeMultiplier;

	internal static ConfigEntry<float> CfgAngularLimitMultiplier;

	internal static ConfigEntry<float> CfgHoldPositionDistance;

	internal static ConfigEntry<float> CfgMaxSpreadMultiplier;

	internal static ConfigEntry<bool> CfgVerbose;

	private void Awake()
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		BindConfig();
		Harmony harmony = new Harmony("SosigEngagementTweaks");
		TryPatch(harmony, typeof(SosigSightRangePatch));
		TryPatch(harmony, typeof(TargetVisibilityPatch));
		TryPatch(harmony, typeof(SosigWeaponRangePatch));
		TryPatch(harmony, typeof(HoldPositionPatch));
		TryPatch(harmony, typeof(SpreadFalloffPatch));
		Log.LogInfo((object)"Sosig Engagement Tweaks 1.0.0 loaded.");
	}

	private void BindConfig()
	{
		CfgRecogMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("1. Recognition Distance", "DistanceMultiplier", 1f, MakeRangeDescription("Scales how far anything can be noticed. Applies to how far every entity can be seen from (you and hostile Sosigs alike) and to a Sosig's own view range, so Sosigs will spot each other at long range too. 1 = vanilla, 2-10 = further.", 1f, 10f, 1f));
		CfgThreatRangeMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("1. Recognition Distance", "ThreatReactionMultiplier", 1f, MakeRangeDescription("Scales the distance at which a Sosig treats a target as a threat (MaxThreatingIFFReactionRange_Visual). Higher = it commits to a fight sooner. 1 = vanilla.", 1f, 10f, 1f));
		CfgWeaponRangeMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("2. Engagement Range", "MaxWeaponRangeMultiplier", 1f, MakeRangeDescription("Scales a weapon's maximum range (RangeMinMax upper bound). Past that distance a Sosig gives up shooting entirely and its AI treats the target as unreachable, so this is the main dial for engagement distance. 1 = vanilla, 2-4 is already a big change.", 1f, 10f, 1f));
		CfgAngularLimitMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("2. Engagement Range", "AimAngleToleranceMultiplier", 1f, MakeRangeDescription("Scales the aim-angle error a weapon tolerates before firing (MaxAngularFireRange). The vanilla curve tightens sharply with distance, which makes Sosigs stare without shooting; raise this together with the range multiplier. 1 = vanilla.", 1f, 10f, 1f));
		CfgHoldPositionDistance = ((BaseUnityPlugin)this).Config.Bind<float>("2. Engagement Range", "HoldPositionDistance", 0f, MakeRangeDescription("0 (default) = disabled, vanilla behaviour: Sosigs keep advancing while shooting. Set it above 0 and an armed Sosig with a hostile target closer than this stops moving and shoots from where it stands instead of closing in or looking for cover. Unit: meters.", 0f, 1000f, 10f));
		CfgMaxSpreadMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("3. Accuracy Falloff", "MaxSpreadMultiplier", 1f, MakeRangeDescription("1 (default) = distance falloff disabled, identical to vanilla. Raise it to let bullets spread more with distance (3-6 recommended) and to set the hard cap on that multiplier, so long-range fire cannot become completely hopeless. 1-50.", 1f, 50f, 1f));
		CfgVerbose = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Debug", "VerboseLogging", false, "Logs every multiplier application, shot and hold-position decision, e.g. \"shot at 213.4m -> spread 0.750 -> 2.500\". Very spammy, turn it off when done.");
	}

	internal static ConfigDescription MakeRangeDescription(string description, float min, float max, float step)
	{
		//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Expected O, but got Unknown
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Expected O, but got Unknown
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Expected O, but got Unknown
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Expected O, but got Unknown
		try
		{
			Type type = FindSodaliteType("Sodalite.ModPanel.AcceptableValueFloatRangeStep");
			if ((object)type != null)
			{
				ConstructorInfo constructor = type.GetConstructor(new Type[3]
				{
					typeof(float),
					typeof(float),
					typeof(float)
				});
				if ((object)constructor != null)
				{
					object obj = constructor.Invoke(new object[3] { min, max, step });
					AcceptableValueBase val = (AcceptableValueBase)((obj is AcceptableValueBase) ? obj : null);
					if (val != null)
					{
						return new ConfigDescription(description, val, new object[0]);
					}
				}
				if (Log != null)
				{
					Log.LogWarning((object)"Sodalite is present but its float range type is unusable; this entry gets no range and falls back to a text field.");
				}
				return new ConfigDescription(description, (AcceptableValueBase)null, new object[0]);
			}
		}
		catch (Exception ex)
		{
			if (Log != null)
			{
				Log.LogWarning((object)("Sodalite range binding failed (" + ex.Message + "); entry gets no range."));
			}
			return new ConfigDescription(description, (AcceptableValueBase)null, new object[0]);
		}
		return new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), new object[0]);
	}

	private static Type FindSodaliteType(string fullName)
	{
		Type type = Type.GetType(fullName + ", Sodalite");
		if ((object)type != null)
		{
			return type;
		}
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		for (int i = 0; i < assemblies.Length; i++)
		{
			try
			{
				type = assemblies[i].GetType(fullName, throwOnError: false);
				if ((object)type != null)
				{
					return type;
				}
			}
			catch (Exception)
			{
			}
		}
		return null;
	}

	private static void TryPatch(Harmony harmony, Type patchType)
	{
		try
		{
			harmony.PatchAll(patchType);
			Log.LogInfo((object)("patched: " + patchType.Name));
		}
		catch (Exception ex)
		{
			Log.LogError((object)("failed to patch " + patchType.Name + ": " + ex));
		}
	}

	internal static float Clamp(float v, float lo, float hi)
	{
		if (v < lo)
		{
			return lo;
		}
		if (v > hi)
		{
			return hi;
		}
		return v;
	}

	internal static float RecognitionMultiplier()
	{
		return Clamp(CfgRecogMultiplier.Value, 1f, 10f);
	}

	internal static float ThreatRangeMultiplier()
	{
		return Clamp(CfgThreatRangeMultiplier.Value, 1f, 10f);
	}

	internal static float WeaponRangeMultiplier()
	{
		return Clamp(CfgWeaponRangeMultiplier.Value, 1f, 10f);
	}

	internal static float AngularLimitMultiplier()
	{
		return Clamp(CfgAngularLimitMultiplier.Value, 1f, 10f);
	}

	internal static float HoldPositionDistance()
	{
		return Clamp(CfgHoldPositionDistance.Value, 0f, 1000f);
	}
}
[HarmonyPatch(typeof(Sosig), "Configure", new Type[] { typeof(SosigConfigTemplate) })]
public static class SosigSightRangePatch
{
	[HarmonyPostfix]
	public static void Postfix(Sosig __instance, SosigConfigTemplate __0)
	{
		if ((Object)(object)__instance == (Object)null)
		{
			return;
		}
		float num = EngagementTweaksPlugin.RecognitionMultiplier();
		if (!Mathf.Approximately(num, 1f))
		{
			float num2 = (((Object)(object)__0 != (Object)null) ? __0.ViewDistance : __instance.MaxSightRange);
			if (num2 <= 0f)
			{
				num2 = __instance.MaxSightRange;
			}
			if (num2 > 0f)
			{
				float num3 = (__instance.MaxSightRange = num2 * num);
				if ((Object)(object)__instance.E != (Object)null)
				{
					__instance.E.MaximumSightRange = num3;
				}
				if (EngagementTweaksPlugin.CfgVerbose.Value)
				{
					EngagementTweaksPlugin.Log.LogInfo((object)("view range " + num2 + " -> " + num3));
				}
			}
		}
		float num4 = EngagementTweaksPlugin.ThreatRangeMultiplier();
		if (Mathf.Approximately(num4, 1f))
		{
			return;
		}
		int instanceID = ((Object)__instance).GetInstanceID();
		float num5 = EngagementTweaksPlugin.Originals.Float(instanceID, "threat", __instance.MaxThreatingIFFReactionRange_Visual);
		if (num5 > 0f)
		{
			__instance.MaxThreatingIFFReactionRange_Visual = num5 * num4;
			if (EngagementTweaksPlugin.CfgVerbose.Value)
			{
				EngagementTweaksPlugin.Log.LogInfo((object)("threat reaction range " + num5 + " -> " + __instance.MaxThreatingIFFReactionRange_Visual));
			}
		}
	}
}
[HarmonyPatch(typeof(AIEntity), "GetMaxDistanceVisibleFrom", new Type[] { typeof(bool) })]
public static class TargetVisibilityPatch
{
	[HarmonyPostfix]
	public static void Postfix(ref float __result)
	{
		float num = EngagementTweaksPlugin.RecognitionMultiplier();
		if (!Mathf.Approximately(num, 1f))
		{
			__result *= num;
		}
	}
}
[HarmonyPatch(typeof(SosigWeapon), "Start")]
public static class SosigWeaponRangePatch
{
	[HarmonyPostfix]
	public static void Postfix(SosigWeapon __instance)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)__instance == (Object)null)
		{
			return;
		}
		int instanceID = ((Object)__instance).GetInstanceID();
		float num = EngagementTweaksPlugin.WeaponRangeMultiplier();
		if (!Mathf.Approximately(num, 1f))
		{
			Vector2 val = EngagementTweaksPlugin.Originals.Vector(instanceID, "range", __instance.RangeMinMax);
			if (val.y > 0f)
			{
				__instance.RangeMinMax = new Vector2(val.x, val.y * num);
				if (EngagementTweaksPlugin.CfgVerbose.Value)
				{
					EngagementTweaksPlugin.Log.LogInfo((object)("max weapon range " + val.y + " -> " + __instance.RangeMinMax.y));
				}
			}
		}
		float num2 = EngagementTweaksPlugin.AngularLimitMultiplier();
		if (Mathf.Approximately(num2, 1f))
		{
			return;
		}
		float num3 = EngagementTweaksPlugin.Originals.Float(instanceID, "angular", __instance.MaxAngularFireRange);
		if (num3 > 0f)
		{
			__instance.MaxAngularFireRange = num3 * num2;
			if (EngagementTweaksPlugin.CfgVerbose.Value)
			{
				EngagementTweaksPlugin.Log.LogInfo((object)("aim angle tolerance " + num3 + " -> " + __instance.MaxAngularFireRange));
			}
		}
	}
}
[HarmonyPatch(typeof(Sosig), "TryToGetTo")]
public static class HoldPositionPatch
{
	[HarmonyPrefix]
	public static bool Prefix(Sosig __instance)
	{
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			float num = EngagementTweaksPlugin.HoldPositionDistance();
			if (num <= 0f)
			{
				return true;
			}
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if (!ShouldHoldPosition(__instance, num))
			{
				return true;
			}
			NavMeshAgent agent = __instance.Agent;
			if ((Object)(object)agent != (Object)null && agent.isOnNavMesh)
			{
				agent.ResetPath();
				agent.velocity = Vector3.zero;
			}
			if (EngagementTweaksPlugin.CfgVerbose.Value)
			{
				EngagementTweaksPlugin.Log.LogInfo((object)("hold position: target inside " + num + "m, not advancing."));
			}
			return false;
		}
		catch (Exception)
		{
			return true;
		}
	}

	private static bool ShouldHoldPosition(Sosig sosig, float hold)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		if (sosig.Priority == null)
		{
			return false;
		}
		if (!sosig.Priority.HasFreshTarget())
		{
			return false;
		}
		if (!sosig.DoIHaveAGun())
		{
			return false;
		}
		Vector3 targetPoint = sosig.Priority.GetTargetPoint();
		float num = Vector3.Distance(((Component)sosig).transform.position, targetPoint);
		return num <= hold;
	}
}
[HarmonyPatch(typeof(SosigWeapon), "FireGun", new Type[] { typeof(float) })]
public static class SpreadFalloffPatch
{
	[HarmonyPrefix]
	public static void Prefix(SosigWeapon __instance, out float __state)
	{
		__state = -1f;
		if ((Object)(object)__instance == (Object)null || EngagementTweaksPlugin.CfgMaxSpreadMultiplier.Value <= 1f)
		{
			return;
		}
		Sosig sosigHoldingThis = __instance.SosigHoldingThis;
		if ((Object)(object)sosigHoldingThis == (Object)null)
		{
			return;
		}
		float projectileSpread = __instance.ProjectileSpread;
		if (projectileSpread <= 0f)
		{
			return;
		}
		float num = TargetDistance(__instance, sosigHoldingThis);
		if (num <= 0f)
		{
			return;
		}
		float num2 = SpreadFalloffTable.MultiplierFor(num);
		if (!(num2 <= 1.0001f))
		{
			__state = projectileSpread;
			__instance.ProjectileSpread = projectileSpread * num2;
			if (EngagementTweaksPlugin.CfgVerbose.Value)
			{
				EngagementTweaksPlugin.Log.LogInfo((object)("shot at " + num.ToString("F1") + "m -> spread " + projectileSpread.ToString("F3") + " -> " + __instance.ProjectileSpread.ToString("F3")));
			}
		}
	}

	[HarmonyPostfix]
	public static void Postfix(SosigWeapon __instance, float __state)
	{
		if (!(__state <= 0f) && !((Object)(object)__instance == (Object)null))
		{
			__instance.ProjectileSpread = __state;
		}
	}

	internal static float TargetDistance(SosigWeapon weapon, Sosig shooter)
	{
		//IL_0021: 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_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = (((Object)(object)weapon.Muzzle != (Object)null) ? weapon.Muzzle.position : ((Component)weapon).transform.position);
		SosigTargetPrioritySystem priority = shooter.Priority;
		if (priority == null)
		{
			return 0f;
		}
		Vector3 targetPoint = priority.GetTargetPoint();
		return Vector3.Distance(val, targetPoint);
	}
}
internal static class SpreadFalloffTable
{
	private static readonly float[] Distances = new float[7] { 0f, 100f, 200f, 300f, 400f, 500f, 600f };

	private static readonly float[] Losses = new float[7] { 0f, 20f, 40f, 50f, 50f, 70f, 80f };

	internal static float MultiplierFor(float distance)
	{
		float num = 0f;
		for (int i = 0; i < Distances.Length && distance >= Distances[i]; i++)
		{
			num = Losses[i];
		}
		if (num <= 0f)
		{
			return 1f;
		}
		float num2 = 100f / (100f - num);
		float value = EngagementTweaksPlugin.CfgMaxSpreadMultiplier.Value;
		if (value > 0f && num2 > value)
		{
			num2 = value;
		}
		return num2;
	}
}