Decompiled source of SosigFacesForModders v0.1.2

NGA.SosigFacesForModders.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FistVR;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyCompany("NGA")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Persistent player progression! Raid, stash loot, and deploy with seemless scene/loadout saving.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("NGA.SosigFacesForModders")]
[assembly: AssemblyTitle("BepInEx Plugin Title")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string id = null, string name = null, string version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string id = null, string name = null, string version = null)
		{
		}
	}
}
namespace NGA.SosigFacesForModders
{
	[BepInPlugin("NGA.SosigFacesForModders", "SosigFacesForModders", "0.0.1")]
	[BepInDependency("nrgill28.Sodalite", "1.0.0")]
	[BepInProcess("h3vr.exe")]
	public class SosigFaces : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(Sosig))]
		[HarmonyPatch("ProcessDamage")]
		[HarmonyPatch(new Type[]
		{
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(Vector3),
			typeof(SosigLink)
		})]
		public class SosigProcessDamage : MonoBehaviour
		{
			private static void Postfix(Sosig __instance, float damage_p, float damage_c, float damage_b, float damage_t, Vector3 point, SosigLink link)
			{
				if (!checkSkip)
				{
					((Component)__instance).GetComponent<EnemyFaceController>()?.SetState("Damaged");
				}
			}
		}

		[HarmonyPatch(typeof(Sosig))]
		[HarmonyPatch("SosigDies")]
		public class SosigSosigDies : MonoBehaviour
		{
			private static void Postfix(Sosig __instance, DamageClass damClass, SosigDeathType deathType)
			{
				if (!checkSkip)
				{
					((Component)__instance).GetComponent<EnemyFaceController>()?.SetState("Dead");
				}
			}
		}

		[HarmonyPatch(typeof(Sosig))]
		[HarmonyPatch("BrainUpdate")]
		public class SosigBrainUpdate : MonoBehaviour
		{
			private static void Postfix(Sosig __instance)
			{
				//IL_000f: 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_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				//IL_0053: Expected I4, but got Unknown
				if (!checkSkip)
				{
					bool flag = false;
					SosigOrder currentOrder = __instance.CurrentOrder;
					SosigOrder val = currentOrder;
					switch ((int)val)
					{
					case 0:
					case 1:
					case 3:
					case 4:
					case 6:
					case 8:
					case 9:
					case 10:
						flag = false;
						break;
					default:
						flag = true;
						break;
					}
					string state = (flag ? "Assaulting" : "Idle");
					((Component)__instance).GetComponent<EnemyFaceController>()?.SetState(state);
				}
			}
		}

		[HarmonyPatch(typeof(Sosig))]
		[HarmonyPatch("Start")]
		public class SosigAwake : MonoBehaviour
		{
			private static void Postfix(Sosig __instance)
			{
				LogWarning("Trying spawn sosig Start 1");
				if (checkSkip)
				{
					return;
				}
				foreach (SosigLink link in __instance.Links)
				{
					Transform transform = ((Component)link).transform;
					if (((Object)((Component)link).transform).name != "Sosig_Torso")
					{
						LogWarning("Link not Sosig_Torso");
						continue;
					}
					if ((Object)(object)((Component)link).transform == (Object)(object)GM.CurrentPlayerBody.Torso)
					{
						LogWarning("This is the player's torso");
						break;
					}
					Transform val = transform.Find("Head");
					if ((Object)(object)val == (Object)null)
					{
						LogWarning("Head not found!");
						break;
					}
					EnemyFaceController enemyFaceController = ((Component)__instance).gameObject.AddComponent<EnemyFaceController>();
					enemyFaceController.Init(val);
				}
			}
		}

		[HarmonyPatch(typeof(Sosig), "Configure")]
		public class SosigConfig : MonoBehaviour
		{
			private static void Postfix(Sosig __instance, SosigConfigTemplate t)
			{
				if ((Object)(object)t == (Object)null)
				{
					LogMessage("[Sosig.Configure] SosigConfigTemplate was null!");
				}
				__instance.TestingSosigTemplate = t;
				if ((Object)(object)__instance.TestingSosigTemplate == (Object)null)
				{
					LogMessage("[Sosig.Configure] TestingSosigTemplate is null after setting config!");
				}
			}
		}

		private class FaceThingComponent : MonoBehaviour
		{
			public GameObject attached_face = null;
		}

		public class EnemyFaceController : MonoBehaviour
		{
			private Material faceMaterial;

			private SosigFaceModConfig activeModConfig;

			private Coroutine damageCoroutine;

			public GameObject attached_face = null;

			private Renderer faceRenderer = null;

			private string currentState = "";

			private bool dead = false;

			private bool damaged = false;

			private static readonly int MainTexID = Shader.PropertyToID("_MainTex");

			private static readonly int EmissionTexID = Shader.PropertyToID("_EmissionMap");

			public void Init(Transform headTransform)
			{
				//IL_0050: Unknown result type (might be due to invalid IL or missing references)
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
				//IL_0347: Unknown result type (might be due to invalid IL or missing references)
				//IL_0351: Expected O, but got Unknown
				Sosig component = ((Component)this).GetComponent<Sosig>();
				if ((Object)(object)component == (Object)null || (Object)(object)component.TestingSosigTemplate == (Object)null)
				{
					Logger.LogError((object)"[EnemyFaceController] Sosig or required references not initialized.");
					Object.Destroy((Object)(object)this);
					return;
				}
				SosigEnemyID sosigID = GetIDFromConfig(component.TestingSosigTemplate);
				activeModConfig = allConfigs.FirstOrDefault((SosigFaceModConfig c) => c.SosigEnemyIdsApplyTo != null && c.SosigEnemyIdsApplyTo.Contains((int)sosigID));
				if (activeModConfig == null)
				{
					activeModConfig = allConfigs.FirstOrDefault((SosigFaceModConfig c) => c.ApplyToAllSosigs);
				}
				if (activeModConfig == null)
				{
					LogMessage($"[EnemyFaceController] No SosigFaceModConfig found for ID: {sosigID}. Destroying component.");
					Object.Destroy((Object)(object)this);
					return;
				}
				bool flag = false;
				List<string> list = new List<string>();
				if (activeModConfig.UseCustomMaterialInFace && !string.IsNullOrEmpty(activeModConfig.CustomMaterialInFaceName))
				{
					Renderer[] array = component.Links.SelectMany((SosigLink link) => ((Component)link).gameObject.GetComponentsInChildren<Renderer>()).ToArray();
					Renderer[] array2 = array;
					foreach (Renderer val in array2)
					{
						for (int j = 0; j < val.sharedMaterials.Length; j++)
						{
							Material val2 = val.sharedMaterials[j];
							list.Add(((Object)val2).name);
							if ((Object)(object)val2 != (Object)null && ((Object)val2).name.Equals(activeModConfig.CustomMaterialInFaceName))
							{
								Material[] materials = val.materials;
								faceMaterial = materials[j];
								val.materials = materials;
								LoadEmotionsForMod(activeModConfig);
								faceRenderer = val;
								flag = true;
								LogMessage("[EnemyFaceController] Found and instantiated custom material: " + activeModConfig.CustomMaterialInFaceName + " on object: " + ((Object)((Component)val).gameObject).name);
								break;
							}
						}
						if (flag)
						{
							break;
						}
					}
				}
				if (!flag && activeModConfig.UseCustomMaterialInFace)
				{
					Logger.LogError((object)("Did not match any materials with name::::->" + activeModConfig.CustomMaterialInFaceName + " (Instance)"));
					foreach (string item in list)
					{
						Logger.LogError((object)item);
					}
				}
				if (!activeModConfig.UseCustomMaterialInFace && !flag && (Object)(object)attached_face != (Object)null)
				{
					attached_face = SpawnFaceOnHead(headTransform);
					Transform val3 = attached_face.transform.Find("default");
					if (!((Object)(object)val3 != (Object)null))
					{
						Logger.LogError((object)"'default' object not found among children of face.");
						Object.Destroy((Object)(object)this);
						return;
					}
					Renderer component2 = ((Component)val3).GetComponent<Renderer>();
					if (!((Object)(object)component2 != (Object)null))
					{
						Logger.LogError((object)"Renderer component not found on the 'default' object.");
						Object.Destroy((Object)(object)this);
						return;
					}
					faceMaterial = new Material(component2.material);
					component2.material = faceMaterial;
					faceRenderer = component2;
					LogMessage("[EnemyFaceController] Using default attached_face material.");
				}
				SetState("Idle");
			}

			public void SetState(string state)
			{
				if ((Object)(object)faceMaterial == (Object)null || activeModConfig == null || dead)
				{
					return;
				}
				List<Texture2D> list = null;
				switch (state)
				{
				case "Dead":
					if (damageCoroutine != null)
					{
						((MonoBehaviour)this).StopCoroutine(damageCoroutine);
					}
					list = GetTexturesForEmotion("Dead");
					SetMaterialTextures(list);
					dead = true;
					currentState = "Dead";
					break;
				case "Idle":
					if (!damaged && state != currentState)
					{
						currentState = state;
						list = GetTexturesForEmotion("Idle");
						SetMaterialTextures(list);
					}
					break;
				case "Assaulting":
					if (!damaged && state != currentState)
					{
						currentState = state;
						list = GetTexturesForEmotion("Assaulting");
						SetMaterialTextures(list);
					}
					break;
				case "Damaged":
					if (!damaged)
					{
						if (damageCoroutine != null)
						{
							((MonoBehaviour)this).StopCoroutine(damageCoroutine);
						}
						list = GetTexturesForEmotion("Damaged");
						SetMaterialTextures(list);
						damageCoroutine = ((MonoBehaviour)this).StartCoroutine(DamageTimeout());
					}
					break;
				}
			}

			private void SetMaterialTextures(List<Texture2D> textures)
			{
				//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ab: Expected O, but got Unknown
				//IL_0062: Unknown result type (might be due to invalid IL or missing references)
				//IL_0068: Expected O, but got Unknown
				if (!((Object)(object)faceMaterial == (Object)null) && !((Object)(object)faceRenderer == (Object)null) && (textures == null || textures.Count != 0))
				{
					Texture2D randomTexture = GetRandomTexture(textures);
					faceMaterial.SetTexture(MainTexID, (Texture)(object)randomTexture);
					if (activeModConfig.MakeCustomEmissiveUseTexture)
					{
						MaterialPropertyBlock val = new MaterialPropertyBlock();
						faceRenderer.GetPropertyBlock(val);
						val.SetTexture(EmissionTexID, (Texture)(object)randomTexture);
						faceRenderer.SetPropertyBlock(val);
						faceMaterial.EnableKeyword("_EMISSION");
					}
					else
					{
						MaterialPropertyBlock val2 = new MaterialPropertyBlock();
						faceRenderer.GetPropertyBlock(val2);
						val2.SetTexture(EmissionTexID, (Texture)null);
						faceRenderer.SetPropertyBlock(val2);
						faceMaterial.DisableKeyword("_EMISSION");
					}
				}
			}

			private IEnumerator DamageTimeout()
			{
				damaged = true;
				yield return (object)new WaitForSeconds(2f);
				SetState(currentState);
				damaged = false;
			}

			private List<Texture2D> GetTexturesForEmotion(string emotion)
			{
				string text = activeModConfig.ModFolderName + emotion;
				if (textures_cache != null && textures_cache.TryGetValue(text, out var value))
				{
					return value;
				}
				Logger.LogError((object)("[EnemyFaceController] No textures found in cache for key: " + text + "."));
				return new List<Texture2D>();
			}

			private Texture2D GetRandomTexture(List<Texture2D> textures)
			{
				if (textures == null || textures.Count == 0)
				{
					return null;
				}
				return textures[Random.Range(0, textures.Count)];
			}

			private static GameObject SpawnFaceOnHead(Transform headTransform)
			{
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: 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_0069: Unknown result type (might be due to invalid IL or missing references)
				if (!IM.OD.TryGetValue("NGA_FaceThing", out var value))
				{
					Logger.LogError((object)"NGA_FaceThing not found in IM.OD!");
					return null;
				}
				GameObject val = Object.Instantiate<GameObject>(((AnvilAsset)value).GetGameObject(), Vector3.zero, Quaternion.identity);
				val.transform.SetParent(headTransform, false);
				val.transform.localPosition = Vector3.zero;
				val.transform.localRotation = Quaternion.identity;
				return val;
			}
		}

		private static ConfigEntry<bool> GameEnabled;

		private static ConfigEntry<bool> DebugMode;

		private static bool checkSkip;

		private static bool checkVerbose;

		public static List<SosigFaceModConfig> allConfigs;

		public static Dictionary<string, List<Texture2D>> textures_cache = new Dictionary<string, List<Texture2D>>();

		public static Dictionary<SosigConfigTemplate, SosigEnemyID> configToIDMap = null;

		internal static ManualLogSource Logger { get; private set; }

		private void Awake()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			Logger = ((BaseUnityPlugin)this).Logger;
			Harmony val = new Harmony("NGA.SosigFacesForModders");
			LogMessage("New harmony");
			SetUpConfigFields();
			LogMessage("Setted the fields");
			Init();
			val.PatchAll();
			LogMessage("Hello, world! Sent from NGA.SosigFacesForModders 0.0.1");
		}

		private void SetUpConfigFields()
		{
			GameEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Overall", "ON/OFF.", true, "Turn mod On / Off");
			DebugMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug Mode", "ON/OFF.", false, "Enable verbose logging in commandline.");
		}

		private static bool CheckSkip()
		{
			checkSkip = !GameEnabled.Value;
			return checkSkip;
		}

		private static bool CheckVerbose()
		{
			checkVerbose = DebugMode.Value;
			return checkVerbose;
		}

		private void Init()
		{
			LoadConfigs(searchFoldersForFaceJsons());
			CheckSkip();
			CheckVerbose();
		}

		private static List<string> searchFoldersForFaceJsons()
		{
			try
			{
				string pluginPath = Paths.PluginPath;
				string[] files = Directory.GetFiles(pluginPath, "sffm_*.json", SearchOption.AllDirectories);
				return new List<string>(files);
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Error while searching for face jsons: " + ex.Message));
				return new List<string>();
			}
		}

		public static void LoadConfigs(List<string> allJsons)
		{
			allConfigs = new List<SosigFaceModConfig>();
			foreach (string allJson in allJsons)
			{
				if (!TryLoadJson<SosigFaceModConfigList>(allJson, out var obj, out var error))
				{
					Logger.LogError((object)("Bazinga! You coudln't parse SosigFaceModConfig list: " + error + " for file " + allJson));
				}
				else if (obj != null && obj.Configs != null)
				{
					allConfigs.AddRange(obj.Configs);
					LogMessage($"Loaded {obj.Configs.Count} config(s) from file: {allJson}");
				}
				else
				{
					Logger.LogWarning((object)("Config wrapper loaded from " + allJson + ", but 'Configs' list was null or empty."));
				}
			}
		}

		public static bool TryLoadJson<T>(string filePath, out T obj, out string error) where T : new()
		{
			obj = default(T);
			error = null;
			try
			{
				if (!File.Exists(filePath))
				{
					error = "File not found: " + filePath;
					return false;
				}
				string text = File.ReadAllText(filePath);
				obj = JsonUtility.FromJson<T>(text);
				if (obj == null)
				{
					error = "JsonUtility.FromJson returned null for " + typeof(T).Name;
					return false;
				}
				return true;
			}
			catch (Exception ex)
			{
				error = "LoadJson failed: " + ex.Message;
				Logger.LogError((object)error);
				return false;
			}
		}

		private static void LoadEmotionsForMod(SosigFaceModConfig mod)
		{
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Expected O, but got Unknown
			string path = Path.Combine(Paths.PluginPath, mod.ModFolderName);
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>
			{
				{ "Idle", mod.IdleFacesFileNames },
				{ "Assaulting", mod.AssaultingFacesFileNames },
				{ "Damaged", mod.DamagedFacesFileNames },
				{ "Dead", mod.DeadFacesFileNames }
			};
			foreach (KeyValuePair<string, List<string>> item in dictionary)
			{
				string key = item.Key;
				List<string> value = item.Value;
				if (value == null || value.Count == 0)
				{
					continue;
				}
				string text = mod.ModFolderName + key;
				if (textures_cache.ContainsKey(text))
				{
					continue;
				}
				List<Texture2D> list = new List<Texture2D>();
				foreach (string item2 in value)
				{
					string text2 = Path.Combine(path, item2);
					if (File.Exists(text2))
					{
						try
						{
							byte[] array = File.ReadAllBytes(text2);
							Texture2D val = new Texture2D(2, 2);
							val.LoadImage(array);
							list.Add(val);
						}
						catch (Exception ex)
						{
							Logger.LogError((object)("[SosigFaceMod] Failed to load texture at path: " + text2 + ". Error: " + ex.Message));
						}
					}
					else
					{
						LogMessage("[SosigFaceMod] Texture file not found: " + text2);
					}
				}
				if (list.Count > 0)
				{
					textures_cache.Add(text, list);
					LogMessage($"[SosigFaceMod] Cached {list.Count} textures for {text}.");
				}
			}
		}

		public static void BuildReverseLookupMap()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			LogMessage("Starting to build SosigConfigTemplate reverse lookup map...");
			configToIDMap = new Dictionary<SosigConfigTemplate, SosigEnemyID>();
			foreach (KeyValuePair<SosigEnemyID, SosigEnemyTemplate> item in ManagerSingleton<IM>.Instance.odicSosigObjsByID)
			{
				SosigEnemyID key = item.Key;
				SosigEnemyTemplate value = item.Value;
				List<SosigConfigTemplate> configTemplates = value.ConfigTemplates;
				if (configTemplates == null || configTemplates.Count == 0)
				{
					LogWarning($"Enemy Template {((Object)value).name} (ID: {key}) has an empty or null ConfigTemplates list.");
					continue;
				}
				foreach (SosigConfigTemplate item2 in configTemplates)
				{
					if ((Object)(object)item2 != (Object)null && !configToIDMap.ContainsKey(item2))
					{
						configToIDMap.Add(item2, key);
					}
				}
			}
			LogMessage($"Finished building map. Total unique Config Templates mapped: {configToIDMap.Count}");
		}

		public static SosigEnemyID GetIDFromConfig(SosigConfigTemplate config)
		{
			//IL_0032: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if (configToIDMap == null)
			{
				BuildReverseLookupMap();
			}
			if ((Object)(object)config == (Object)null)
			{
				Logger.LogError((object)"[GetIDFromConfig]: Given config was null!");
				return (SosigEnemyID)0;
			}
			if (configToIDMap.TryGetValue(config, out var value))
			{
				return value;
			}
			Logger.LogError((object)("Config Template " + ((Object)config).name + " not found in reverse lookup map!"));
			return (SosigEnemyID)0;
		}

		public static void LogMessage(string str)
		{
			if (checkVerbose)
			{
				Logger.LogMessage((object)str);
			}
		}

		public static void LogWarning(string str)
		{
			if (checkVerbose)
			{
				Logger.LogWarning((object)str);
			}
		}
	}
	[Serializable]
	public class SosigFaceModConfig
	{
		public string ModFolderName;

		public float xPosOffset;

		public float yPosOffset;

		public float zPosOffset;

		public List<string> IdleFacesFileNames;

		public List<string> AssaultingFacesFileNames;

		public List<string> DamagedFacesFileNames;

		public List<string> DeadFacesFileNames;

		public bool ApplyToAllSosigs;

		public List<int> SosigEnemyIdsApplyTo;

		public bool UseCustomMaterialInFace;

		public string CustomMaterialInFaceName;

		public bool MakeCustomEmissiveUseTexture;
	}
	[Serializable]
	public class SosigFaceModConfigList
	{
		public List<SosigFaceModConfig> Configs;
	}
}