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