using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("NutcrackerFireWarning.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("NutcrackerFireWarning")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("NutcrackerFireWarning")] [assembly: AssemblyTitle("NutcrackerFireWarning")] [assembly: AssemblyVersion("1.0.0.0")] [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 NutcrackerShotUI { internal readonly struct NutcrackerCombatState { public readonly bool AimingGun; public readonly bool ReloadingGun; public readonly float TimeSinceFiringGun; public readonly float AimDuration; public NutcrackerCombatState(bool aimingGun, bool reloadingGun, float timeSinceFiringGun, float aimDuration) { AimingGun = aimingGun; ReloadingGun = reloadingGun; TimeSinceFiringGun = timeSinceFiringGun; AimDuration = aimDuration; } } internal static class NutcrackerCombatStateReader { private static readonly FieldRef AimingGunRef = CreateFieldRef("aimingGun"); private static readonly FieldRef ReloadingGunRef = CreateFieldRef("reloadingGun"); private static readonly FieldRef TimeSinceFiringGunRef = CreateFieldRef("timeSinceFiringGun"); public static bool TryRead(NutcrackerEnemyAI nutcracker, out NutcrackerCombatState combatState) { combatState = default(NutcrackerCombatState); if ((Object)(object)nutcracker == (Object)null || AimingGunRef == null || ReloadingGunRef == null || TimeSinceFiringGunRef == null) { return false; } combatState = new NutcrackerCombatState(AimingGunRef.Invoke(nutcracker), ReloadingGunRef.Invoke(nutcracker), TimeSinceFiringGunRef.Invoke(nutcracker), GetAimDuration(nutcracker)); return true; } public static float GetAimDuration(NutcrackerEnemyAI nutcracker) { if (((EnemyAI)nutcracker).enemyHP <= 1) { return 0.5f; } ShotgunItem gun = nutcracker.gun; if ((Object)(object)gun != (Object)null && gun.shellsLoaded == 1) { return 1.3f; } return 1.75f; } private static FieldRef CreateFieldRef(string fieldName) { try { return AccessTools.FieldRefAccess(fieldName); } catch { return null; } } } internal static class NutcrackerConfigLanguageDetector { internal const string ChineseProjectGuid = "cn.codex.v81testchn"; private const string ChineseProjectFolderName = "V81TestChn"; private const string ChineseProjectDllName = "V81TestChn.dll"; public static bool ShouldUseChineseConfig() { try { if (Chainloader.PluginInfos != null && Chainloader.PluginInfos.ContainsKey("cn.codex.v81testchn")) { return true; } } catch { } return ShouldUseChineseConfig(null, Paths.PluginPath); } internal static bool ShouldUseChineseConfig(IEnumerable pluginGuids, string pluginRoot) { if (ContainsChineseProjectGuid(pluginGuids)) { return true; } return ContainsChineseProjectDll(pluginRoot); } private static bool ContainsChineseProjectGuid(IEnumerable pluginGuids) { if (pluginGuids == null) { return false; } foreach (string pluginGuid in pluginGuids) { if (string.Equals(pluginGuid, "cn.codex.v81testchn", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool ContainsChineseProjectDll(string pluginRoot) { if (string.IsNullOrWhiteSpace(pluginRoot) || !Directory.Exists(pluginRoot)) { return false; } if (File.Exists(Path.Combine(pluginRoot, "V81TestChn", "V81TestChn.dll")) || File.Exists(Path.Combine(pluginRoot, "V81TestChn.dll"))) { return true; } try { foreach (string item in Directory.EnumerateFiles(pluginRoot, "V81TestChn.dll", SearchOption.AllDirectories)) { if (string.Equals(Path.GetFileName(item), "V81TestChn.dll", StringComparison.OrdinalIgnoreCase)) { return true; } } } catch { return false; } return false; } } internal sealed class NutcrackerModelFireWindowOutline { private sealed class MeshOutlineClone { private readonly Renderer source; private readonly GameObject cloneObject; private readonly MeshFilter cloneFilter; private readonly MeshRenderer cloneMeshRenderer; private readonly SkinnedMeshRenderer cloneSkinnedRenderer; private readonly SkinnedMeshRenderer skinnedSource; private readonly Mesh sourceSkinnedMesh; private readonly Mesh sourceStaticMesh; private readonly Mesh expandedMesh; private readonly Material[] outlineMaterials; private readonly Vector3 sourceLocalScale; private readonly bool canExpandMesh; private float lastOutlineWidth = -1f; private MeshOutlineClone(Renderer source, GameObject cloneObject, MeshFilter cloneFilter, MeshRenderer cloneMeshRenderer, SkinnedMeshRenderer cloneSkinnedRenderer, SkinnedMeshRenderer skinnedSource, Mesh sourceSkinnedMesh, Mesh sourceStaticMesh, Mesh expandedMesh, Material[] outlineMaterials, bool canExpandMesh) { //IL_006d: 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) this.source = source; this.cloneObject = cloneObject; this.cloneFilter = cloneFilter; this.cloneMeshRenderer = cloneMeshRenderer; this.cloneSkinnedRenderer = cloneSkinnedRenderer; this.skinnedSource = skinnedSource; this.sourceSkinnedMesh = sourceSkinnedMesh; this.sourceStaticMesh = sourceStaticMesh; this.expandedMesh = expandedMesh; this.outlineMaterials = outlineMaterials; this.canExpandMesh = canExpandMesh; sourceLocalScale = ((Component)source).transform.localScale; } public static MeshOutlineClone TryCreate(Renderer source, Material material) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Expected O, but got Unknown //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null || (Object)(object)material == (Object)null) { return null; } Mesh val = null; Mesh val2 = null; Mesh val3 = null; Mesh val4 = null; bool flag = false; SkinnedMeshRenderer val5 = (SkinnedMeshRenderer)(object)((source is SkinnedMeshRenderer) ? source : null); if ((Object)(object)val5 != (Object)null) { if ((Object)(object)val5.sharedMesh == (Object)null) { return null; } val4 = val5.sharedMesh; flag = val4.isReadable; if (flag) { val2 = Object.Instantiate(val4); ((Object)val2).name = ((Object)source).name + "_NutcrackerOutlineExpandedMesh"; val2.MarkDynamic(); val = val2; } else { val = val4; } } else { MeshFilter component = ((Component)source).GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component.sharedMesh == (Object)null) { return null; } val3 = component.sharedMesh; flag = val3.isReadable; if (flag) { val2 = Object.Instantiate(val3); ((Object)val2).name = ((Object)source).name + "_NutcrackerOutlineExpandedMesh"; val = val2; } else { val = val3; } } GameObject val6 = new GameObject(((Object)source).name + "_NutcrackerMeshOutline"); ((Object)val6).hideFlags = (HideFlags)61; val6.layer = ((Component)source).gameObject.layer; val6.transform.SetParent(((Component)source).transform.parent, false); MeshFilter val7 = null; MeshRenderer val8 = null; SkinnedMeshRenderer val9 = null; Material[] sharedMaterials = CreateOutlineMaterials(source.sharedMaterials, material); if ((Object)(object)val5 != (Object)null) { val9 = val6.AddComponent(); val9.sharedMesh = val; ((Renderer)val9).sharedMaterials = sharedMaterials; val9.bones = val5.bones; val9.rootBone = val5.rootBone; val9.quality = val5.quality; val9.updateWhenOffscreen = true; ((Renderer)val9).localBounds = ExpandBounds(((Renderer)val5).localBounds, 0.25f); ConfigureCloneRenderer((Renderer)(object)val9); } else { val7 = val6.AddComponent(); val8 = val6.AddComponent(); val7.sharedMesh = val; ((Renderer)val8).sharedMaterials = sharedMaterials; ConfigureCloneRenderer((Renderer)(object)val8); } val6.SetActive(false); MeshOutlineClone meshOutlineClone = new MeshOutlineClone(source, val6, val7, val8, val9, val5, val4, val3, val2, sharedMaterials, flag); meshOutlineClone.CopyTransform(1f); return meshOutlineClone; } public bool Update(Color color, float outlineWidth, float scale) { //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null || (Object)(object)cloneObject == (Object)null || !source.enabled) { SetVisible(visible: false); return false; } if ((Object)(object)skinnedSource != (Object)null) { if (canExpandMesh && (Object)(object)expandedMesh != (Object)null && !Mathf.Approximately(lastOutlineWidth, outlineWidth)) { ExpandMeshAlongNormals(sourceSkinnedMesh, expandedMesh, outlineWidth); cloneSkinnedRenderer.sharedMesh = expandedMesh; ((Renderer)cloneSkinnedRenderer).localBounds = ExpandBounds(((Renderer)skinnedSource).localBounds, outlineWidth + 0.25f); } } else if (canExpandMesh && (Object)(object)expandedMesh != (Object)null && !Mathf.Approximately(lastOutlineWidth, outlineWidth)) { ExpandMeshAlongNormals(sourceStaticMesh, expandedMesh, outlineWidth); cloneFilter.sharedMesh = expandedMesh; } lastOutlineWidth = outlineWidth; SetMaterialsColor(color); CopyTransform(scale); SetVisible(visible: true); return true; } public string GetShaderSummary() { if (outlineMaterials == null || outlineMaterials.Length == 0 || (Object)(object)outlineMaterials[0] == (Object)null || (Object)(object)outlineMaterials[0].shader == (Object)null) { return "none"; } return ((Object)outlineMaterials[0].shader).name; } public void SetVisible(bool visible) { if ((Object)(object)cloneObject != (Object)null && cloneObject.activeSelf != visible) { cloneObject.SetActive(visible); } } public void Dispose() { if ((Object)(object)expandedMesh != (Object)null) { Object.Destroy((Object)(object)expandedMesh); } if (outlineMaterials != null) { for (int i = 0; i < outlineMaterials.Length; i++) { if ((Object)(object)outlineMaterials[i] != (Object)null) { Object.Destroy((Object)(object)outlineMaterials[i]); } } } if ((Object)(object)cloneObject != (Object)null) { Object.Destroy((Object)(object)cloneObject); } } private void CopyTransform(float scale) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)source).transform; Transform transform2 = cloneObject.transform; transform2.localPosition = transform.localPosition; transform2.localRotation = transform.localRotation; transform2.localScale = sourceLocalScale * scale; } private void SetMaterialsColor(Color color) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (outlineMaterials != null) { for (int i = 0; i < outlineMaterials.Length; i++) { SetMaterialColor(outlineMaterials[i], color); } } } private static void ConfigureCloneRenderer(Renderer renderer) { renderer.shadowCastingMode = (ShadowCastingMode)0; renderer.receiveShadows = false; renderer.allowOcclusionWhenDynamic = false; renderer.forceRenderingOff = false; renderer.enabled = true; } private static Bounds ExpandBounds(Bounds bounds, float amount) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) ((Bounds)(ref bounds)).Expand(amount); return bounds; } private static void ExpandMeshAlongNormals(Mesh sourceMesh, Mesh targetMesh, float outlineWidth) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)sourceMesh == (Object)null) && !((Object)(object)targetMesh == (Object)null)) { Vector3[] vertices = sourceMesh.vertices; Vector3[] normals = sourceMesh.normals; if (normals == null || normals.Length != vertices.Length) { sourceMesh.RecalculateNormals(); normals = sourceMesh.normals; } Vector3[] array = (Vector3[])(object)new Vector3[vertices.Length]; for (int i = 0; i < vertices.Length; i++) { Vector3 val = ((normals.Length > i) ? ((Vector3)(ref normals[i])).normalized : Vector3.zero); array[i] = vertices[i] + val * outlineWidth; } targetMesh.Clear(); targetMesh.vertices = array; targetMesh.normals = normals; targetMesh.tangents = sourceMesh.tangents; targetMesh.uv = sourceMesh.uv; targetMesh.uv2 = sourceMesh.uv2; targetMesh.colors = sourceMesh.colors; targetMesh.subMeshCount = sourceMesh.subMeshCount; for (int j = 0; j < sourceMesh.subMeshCount; j++) { targetMesh.SetTriangles(sourceMesh.GetTriangles(j), j); } Bounds bounds = sourceMesh.bounds; ((Bounds)(ref bounds)).Expand(outlineWidth * 2f + 0.02f); targetMesh.bounds = bounds; } } private static Material[] CreateOutlineMaterials(Material[] sourceMaterials, Material fallbackMaterial) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) int num = ((sourceMaterials != null) ? sourceMaterials.Length : 0); num = Mathf.Max(1, num); Material[] array = (Material[])(object)new Material[num]; for (int i = 0; i < array.Length; i++) { Material val = ((sourceMaterials != null && i < sourceMaterials.Length) ? sourceMaterials[i] : null); Material val2 = (((Object)(object)val != (Object)null) ? Object.Instantiate(val) : Object.Instantiate(fallbackMaterial)); ((Object)val2).name = (((Object)(object)val != (Object)null) ? ((Object)val).name : "Fallback") + "_NutcrackerOutlineMaterial"; ((Object)val2).hideFlags = (HideFlags)61; ConfigureOutlineMaterial(val2); SetMaterialColor(val2, new Color(1f, 0.02f, 0.02f, 0.86f)); array[i] = val2; } return array; } } private sealed class SourceRendererPulse { private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor"); private static readonly int ColorId = Shader.PropertyToID("_Color"); private static readonly int UnlitColorId = Shader.PropertyToID("_UnlitColor"); private static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor"); private static readonly int EmissiveColorId = Shader.PropertyToID("_EmissiveColor"); private static readonly int EmissiveColorLdrId = Shader.PropertyToID("_EmissiveColorLDR"); private readonly Renderer renderer; private readonly MaterialPropertyBlock originalBlock = new MaterialPropertyBlock(); private readonly MaterialPropertyBlock pulseBlock = new MaterialPropertyBlock(); private bool capturedOriginalBlock; private bool hadOriginalBlock; private bool active; private SourceRendererPulse(Renderer renderer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown this.renderer = renderer; } public static SourceRendererPulse TryCreate(Renderer renderer) { if ((Object)(object)renderer == (Object)null) { return null; } return new SourceRendererPulse(renderer); } public bool Apply(Color color, float emissionMultiplier) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)renderer == (Object)null || !renderer.enabled) { Restore(); return false; } CaptureOriginalBlockIfNeeded(); renderer.GetPropertyBlock(pulseBlock); Color val = default(Color); ((Color)(ref val))..ctor(color.r * emissionMultiplier, color.g * emissionMultiplier, color.b * emissionMultiplier, 1f); pulseBlock.SetColor(BaseColorId, color); pulseBlock.SetColor(ColorId, color); pulseBlock.SetColor(UnlitColorId, color); pulseBlock.SetColor(EmissionColorId, val); pulseBlock.SetColor(EmissiveColorId, val); pulseBlock.SetColor(EmissiveColorLdrId, val); renderer.SetPropertyBlock(pulseBlock); active = true; return true; } public void Restore() { if (!((Object)(object)renderer == (Object)null) && active) { if (hadOriginalBlock) { renderer.SetPropertyBlock(originalBlock); } else { renderer.SetPropertyBlock((MaterialPropertyBlock)null); } active = false; } } public void Dispose() { Restore(); } private void CaptureOriginalBlockIfNeeded() { if (!capturedOriginalBlock) { renderer.GetPropertyBlock(originalBlock); hadOriginalBlock = !originalBlock.isEmpty; capturedOriginalBlock = true; } } } private const float ScreenPadding = 18f; private const float MinScreenWidth = 72f; private const float MinScreenHeight = 120f; private readonly NutcrackerEnemyAI nutcracker; private readonly Material meshMaterial; private readonly List meshClones = new List(); private readonly List sourcePulses = new List(); private readonly List eligibleRenderers = new List(); private GameObject screenRoot; private Canvas screenCanvas; private RectTransform screenCanvasRect; private RectTransform screenBoxRect; private Image screenTop; private Image screenBottom; private Image screenLeft; private Image screenRight; private Renderer[] cachedRenderers; private float nextRendererRefreshTime; private bool meshBuilt; private bool loggedMeshVisible; private bool loggedScreenVisible; public NutcrackerModelFireWindowOutline(NutcrackerEnemyAI nutcracker, Transform parent) { this.nutcracker = nutcracker; meshMaterial = CreateMeshMaterial(); } public void Dispose() { ClearMeshClones(); if ((Object)(object)meshMaterial != (Object)null) { Object.Destroy((Object)(object)meshMaterial); } if ((Object)(object)screenRoot != (Object)null) { Object.Destroy((Object)(object)screenRoot); } } public void Update(ModelWarningPhase phase) { if (phase == ModelWarningPhase.None || (Object)(object)nutcracker == (Object)null) { loggedMeshVisible = false; loggedScreenVisible = false; SetMeshVisible(visible: false); SetScreenVisible(visible: false); return; } bool flag = IsModelStateTintEnabled(); bool flag2 = flag && UpdateStateTint(phase); if (phase != ModelWarningPhase.FireWindow || !IsModelFireWindowEnabled()) { loggedScreenVisible = false; SetCloneShellVisible(visible: false); SetScreenVisible(visible: false); if (!flag2) { RestoreSourcePulses(); } } else if (GetMode() == ModelOutlineMode.ScreenBox) { SetCloneShellVisible(visible: false); if (!flag2) { RestoreSourcePulses(); } UpdateScreenBox(); } else { SetScreenVisible(visible: false); UpdateMeshSilhouette(!flag); } } private void UpdateMeshSilhouette(bool includeSourcePulse) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) EnsureMeshClones(); if (meshClones.Count == 0 && (!includeSourcePulse || sourcePulses.Count == 0)) { SetCloneShellVisible(visible: false); if (includeSourcePulse) { RestoreSourcePulses(); } return; } float pulseAlpha = GetPulseAlpha(); float pulseIntensity = GetPulseIntensity(); Color color = Color.Lerp(new Color(1f, 0.02f, 0.02f, pulseAlpha), new Color(1f, 1f, 1f, pulseAlpha), Mathf.PingPong(Time.time * 10f, 1f)); SetMaterialColor(meshMaterial, color, pulseIntensity); float num = Mathf.Max(0.01f, NutcrackerShotConfig.ModelOutlineWidth.Value); float num2 = Mathf.Max(1f, NutcrackerShotConfig.MeshOutlineScale.Value); int num3 = 0; int num4 = 0; if (includeSourcePulse) { for (int i = 0; i < sourcePulses.Count; i++) { if (sourcePulses[i].Apply(color, pulseIntensity)) { num4++; } } } for (int j = 0; j < meshClones.Count; j++) { if (meshClones[j].Update(color, num, num2)) { num3++; } } if (num3 == 0 && num4 == 0) { SetCloneShellVisible(visible: false); if (includeSourcePulse) { RestoreSourcePulses(); } } else if (IsDebugLoggingEnabled() && !loggedMeshVisible) { loggedMeshVisible = true; string text = ((meshClones.Count > 0) ? meshClones[0].GetShaderSummary() : "none"); Plugin.Log.LogInfo((object)$"Nutcracker #{((EnemyAI)nutcracker).thisEnemyIndex} mesh silhouette outline visible. CloneRenderers={num3} SourcePulses={num4} Width={num:0.000} Scale={num2:0.000} Shader={text}"); } } private void EnsureMeshClones() { if (meshBuilt) { return; } meshBuilt = true; RefreshRenderersIfNeeded(force: true); if (cachedRenderers == null) { return; } for (int i = 0; i < eligibleRenderers.Count; i++) { Renderer val = eligibleRenderers[i]; if (ShouldUseCloneShell()) { MeshOutlineClone meshOutlineClone = MeshOutlineClone.TryCreate(val, meshMaterial); if (meshOutlineClone != null) { meshClones.Add(meshOutlineClone); } } if (ShouldUseSourcePulse() || IsModelStateTintEnabled()) { SourceRendererPulse sourceRendererPulse = SourceRendererPulse.TryCreate(val); if (sourceRendererPulse != null) { sourcePulses.Add(sourceRendererPulse); } } } if (IsDebugLoggingEnabled()) { Plugin.Log.LogInfo((object)$"Nutcracker #{((EnemyAI)nutcracker).thisEnemyIndex} built mesh silhouette outline clones: {meshClones.Count}, source pulses: {sourcePulses.Count}"); } if (ShouldDumpModelAudit()) { DumpModelAudit(); } } private bool UpdateStateTint(ModelWarningPhase phase) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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) EnsureMeshClones(); if (sourcePulses.Count == 0) { return false; } Color stateTintColor = GetStateTintColor(phase); float emissionMultiplier = ((phase == ModelWarningPhase.FireWindow) ? GetFireWindowTintIntensity() : GetChaseTintIntensity()); int num = 0; for (int i = 0; i < sourcePulses.Count; i++) { if (sourcePulses[i].Apply(stateTintColor, emissionMultiplier)) { num++; } } if (IsDebugLoggingEnabled() && num > 0 && !loggedMeshVisible) { loggedMeshVisible = true; Plugin.Log.LogInfo((object)$"Nutcracker #{((EnemyAI)nutcracker).thisEnemyIndex} model state tint visible. Phase={phase} SourcePulses={num}"); } return num > 0; } private void DumpModelAudit() { if (cachedRenderers == null) { return; } Plugin.Log.LogInfo((object)$"Nutcracker #{((EnemyAI)nutcracker).thisEnemyIndex} model audit: renderers={cachedRenderers.Length}, outlineClones={meshClones.Count}"); for (int i = 0; i < cachedRenderers.Length; i++) { Renderer val = cachedRenderers[i]; if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).GetComponentInParent() != (Object)null) { continue; } string text = "none"; int num = 0; int num2 = 0; SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((val is SkinnedMeshRenderer) ? val : null); if ((Object)(object)val2 != (Object)null && (Object)(object)val2.sharedMesh != (Object)null) { text = ((Object)val2.sharedMesh).name; num = val2.sharedMesh.vertexCount; num2 = val2.sharedMesh.subMeshCount; } else { MeshFilter component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component.sharedMesh != (Object)null) { text = ((Object)component.sharedMesh).name; num = component.sharedMesh.vertexCount; num2 = component.sharedMesh.subMeshCount; } } Plugin.Log.LogInfo((object)$"Nutcracker model audit renderer[{i}]: type={GetRendererTypeName(val)}, eligible={IsModelOutlineRenderer(val)}, path={GetPath(((Component)val).transform)}, mesh={text}, vertices={num}, subMeshes={num2}, materials={val.sharedMaterials.Length}"); } } private void ClearMeshClones() { for (int i = 0; i < meshClones.Count; i++) { meshClones[i].Dispose(); } meshClones.Clear(); for (int j = 0; j < sourcePulses.Count; j++) { sourcePulses[j].Dispose(); } sourcePulses.Clear(); meshBuilt = false; } private void SetMeshVisible(bool visible) { SetCloneShellVisible(visible); if (!visible) { RestoreSourcePulses(); } } private void SetCloneShellVisible(bool visible) { for (int i = 0; i < meshClones.Count; i++) { meshClones[i].SetVisible(visible); } } private void RestoreSourcePulses() { for (int i = 0; i < sourcePulses.Count; i++) { sourcePulses[i].Restore(); } } private void UpdateScreenBox() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: 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_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) EnsureScreenBox(); if (!TryGetBounds(out var bounds)) { loggedScreenVisible = false; SetScreenVisible(visible: false); return; } Camera activeCamera = GetActiveCamera(); if ((Object)(object)activeCamera == (Object)null || !TryProjectBounds(bounds, activeCamera, out var screenRect)) { loggedScreenVisible = false; SetScreenVisible(visible: false); return; } screenCanvasRect.sizeDelta = new Vector2((float)Screen.width, (float)Screen.height); float num = Mathf.Max(72f, ((Rect)(ref screenRect)).width + 36f); float num2 = Mathf.Max(120f, ((Rect)(ref screenRect)).height + 36f); Vector2 center = ((Rect)(ref screenRect)).center; screenBoxRect.anchoredPosition = new Vector2(center.x - (float)Screen.width * 0.5f, center.y - (float)Screen.height * 0.5f); screenBoxRect.sizeDelta = new Vector2(num, num2); float thickness = Mathf.Max(3f, NutcrackerShotConfig.ModelOutlineWidth.Value * 120f); SetStripThickness(((Graphic)screenTop).rectTransform, thickness, horizontal: true); SetStripThickness(((Graphic)screenBottom).rectTransform, thickness, horizontal: true); SetStripThickness(((Graphic)screenLeft).rectTransform, thickness, horizontal: false); SetStripThickness(((Graphic)screenRight).rectTransform, thickness, horizontal: false); Color color = Color.Lerp(new Color(1f, 0.02f, 0.02f, 0.96f), Color.white, Mathf.PingPong(Time.unscaledTime * 10f, 1f)); ((Graphic)screenTop).color = color; ((Graphic)screenBottom).color = color; ((Graphic)screenLeft).color = color; ((Graphic)screenRight).color = color; SetScreenVisible(visible: true); if (IsDebugLoggingEnabled() && !loggedScreenVisible) { loggedScreenVisible = true; Plugin.Log.LogInfo((object)$"Nutcracker #{((EnemyAI)nutcracker).thisEnemyIndex} screen outline visible. Rect={num:0}x{num2:0} Center={center.x:0},{center.y:0}"); } } private void EnsureScreenBox() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)screenRoot != (Object)null)) { screenRoot = new GameObject("NutcrackerFireWindowScreenOutline"); ((Object)screenRoot).hideFlags = (HideFlags)61; screenCanvas = screenRoot.AddComponent(); screenCanvas.renderMode = (RenderMode)0; screenCanvas.sortingOrder = 2500; screenCanvasRect = ((Component)screenCanvas).GetComponent(); GameObject val = new GameObject("ProjectedModelBox"); val.transform.SetParent(screenRoot.transform, false); screenBoxRect = val.AddComponent(); screenBoxRect.anchorMin = new Vector2(0.5f, 0.5f); screenBoxRect.anchorMax = new Vector2(0.5f, 0.5f); screenBoxRect.pivot = new Vector2(0.5f, 0.5f); screenTop = CreateStrip("Top", (Transform)(object)screenBoxRect); screenBottom = CreateStrip("Bottom", (Transform)(object)screenBoxRect); screenLeft = CreateStrip("Left", (Transform)(object)screenBoxRect); screenRight = CreateStrip("Right", (Transform)(object)screenBoxRect); AnchorHorizontal(((Graphic)screenTop).rectTransform, 1f); AnchorHorizontal(((Graphic)screenBottom).rectTransform, 0f); AnchorVertical(((Graphic)screenLeft).rectTransform, 0f); AnchorVertical(((Graphic)screenRight).rectTransform, 1f); SetScreenVisible(visible: false); } } private static Material CreateMeshMaterial() { //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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_0077: Expected O, but got Unknown Material val = new Material(Shader.Find("HDRP/Unlit") ?? Shader.Find("Hidden/Internal-Colored") ?? Shader.Find("Sprites/Default") ?? Shader.Find("UI/Default")) { name = "NutcrackerMeshSilhouetteOutlineMaterial", hideFlags = (HideFlags)61 }; ConfigureOutlineMaterial(val); SetMaterialColor(val, new Color(1f, 0.02f, 0.02f, 0.86f)); return val; } private static void ConfigureOutlineMaterial(Material material) { if (!((Object)(object)material == (Object)null)) { material.renderQueue = 5500; material.SetOverrideTag("RenderType", "Transparent"); TrySetInt(material, "_Cull", 0); TrySetInt(material, "_CullMode", 0); TrySetInt(material, "_CullModeForward", 0); TrySetInt(material, "_SrcBlend", 5); TrySetInt(material, "_DstBlend", 10); TrySetInt(material, "_AlphaSrcBlend", 5); TrySetInt(material, "_AlphaDstBlend", 10); TrySetInt(material, "_ZWrite", 0); TrySetInt(material, "_ZTest", 8); TrySetInt(material, "_ZTestTransparent", 8); TrySetInt(material, "_ZTestDepthEqualForOpaque", 8); TrySetFloat(material, "_SurfaceType", 1f); TrySetFloat(material, "_BlendMode", 0f); TrySetFloat(material, "_AlphaCutoffEnable", 0f); TrySetFloat(material, "_EnableFogOnTransparent", 0f); TrySetFloat(material, "_UseEmissiveIntensity", 1f); TrySetFloat(material, "_EmissiveIntensity", 2.5f); material.EnableKeyword("_SURFACE_TYPE_TRANSPARENT"); material.EnableKeyword("_BLENDMODE_ALPHA"); } } private static void SetMaterialColor(Material material, Color color, float emissionMultiplier = 3f) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)material == (Object)null)) { if (material.HasProperty("_BaseColor")) { material.SetColor("_BaseColor", color); } if (material.HasProperty("_UnlitColor")) { material.SetColor("_UnlitColor", color); } if (material.HasProperty("_Color")) { material.SetColor("_Color", color); } Color val = default(Color); ((Color)(ref val))..ctor(color.r * emissionMultiplier, color.g * emissionMultiplier, color.b * emissionMultiplier, color.a); if (material.HasProperty("_EmissionColor")) { material.SetColor("_EmissionColor", val); } if (material.HasProperty("_EmissiveColor")) { material.SetColor("_EmissiveColor", val); } if (material.HasProperty("_EmissiveColorLDR")) { material.SetColor("_EmissiveColorLDR", val); } } } private static void TrySetInt(Material material, string property, int value) { if ((Object)(object)material != (Object)null && material.HasProperty(property)) { material.SetInt(property, value); } } private static void TrySetFloat(Material material, string property, float value) { if ((Object)(object)material != (Object)null && material.HasProperty(property)) { material.SetFloat(property, value); } } private static Image CreateStrip(string name, Transform parent) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); Image obj = val.AddComponent(); ((Graphic)obj).raycastTarget = false; ((Graphic)obj).color = Color.clear; return obj; } private static void AnchorHorizontal(RectTransform strip, float y) { //IL_0007: 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_002d: 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) strip.anchorMin = new Vector2(0f, y); strip.anchorMax = new Vector2(1f, y); strip.pivot = new Vector2(0.5f, 0.5f); strip.anchoredPosition = Vector2.zero; } private static void AnchorVertical(RectTransform strip, float x) { //IL_0007: 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_002d: 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) strip.anchorMin = new Vector2(x, 0f); strip.anchorMax = new Vector2(x, 1f); strip.pivot = new Vector2(0.5f, 0.5f); strip.anchoredPosition = Vector2.zero; } private static void SetStripThickness(RectTransform strip, float thickness, bool horizontal) { //IL_0017: 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) strip.sizeDelta = (horizontal ? new Vector2(0f, thickness) : new Vector2(thickness, 0f)); } private static bool TryProjectBounds(Bounds bounds, Camera camera, out Rect screenRect) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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_000f: 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_0034: 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) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) Vector3 min = ((Bounds)(ref bounds)).min; Vector3 max = ((Bounds)(ref bounds)).max; float minX = float.MaxValue; float minY = float.MaxValue; float maxX = float.MinValue; float maxY = float.MinValue; bool anyVisible = false; ProjectCorner(camera, new Vector3(min.x, min.y, min.z), ref anyVisible, ref minX, ref minY, ref maxX, ref maxY); ProjectCorner(camera, new Vector3(max.x, min.y, min.z), ref anyVisible, ref minX, ref minY, ref maxX, ref maxY); ProjectCorner(camera, new Vector3(min.x, max.y, min.z), ref anyVisible, ref minX, ref minY, ref maxX, ref maxY); ProjectCorner(camera, new Vector3(max.x, max.y, min.z), ref anyVisible, ref minX, ref minY, ref maxX, ref maxY); ProjectCorner(camera, new Vector3(min.x, min.y, max.z), ref anyVisible, ref minX, ref minY, ref maxX, ref maxY); ProjectCorner(camera, new Vector3(max.x, min.y, max.z), ref anyVisible, ref minX, ref minY, ref maxX, ref maxY); ProjectCorner(camera, new Vector3(min.x, max.y, max.z), ref anyVisible, ref minX, ref minY, ref maxX, ref maxY); ProjectCorner(camera, new Vector3(max.x, max.y, max.z), ref anyVisible, ref minX, ref minY, ref maxX, ref maxY); if (!anyVisible) { screenRect = default(Rect); return false; } minX = Mathf.Clamp(minX, 0f, (float)Screen.width); maxX = Mathf.Clamp(maxX, 0f, (float)Screen.width); minY = Mathf.Clamp(minY, 0f, (float)Screen.height); maxY = Mathf.Clamp(maxY, 0f, (float)Screen.height); if (maxX <= minX || maxY <= minY) { screenRect = default(Rect); return false; } screenRect = Rect.MinMaxRect(minX, minY, maxX, maxY); return true; } private static void ProjectCorner(Camera camera, Vector3 corner, ref bool anyVisible, ref float minX, ref float minY, ref float maxX, ref float maxY) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_002d: 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_004f: Unknown result type (might be due to invalid IL or missing references) Vector3 val = camera.WorldToScreenPoint(corner); if (!(val.z <= 0.01f)) { anyVisible = true; minX = Mathf.Min(minX, val.x); minY = Mathf.Min(minY, val.y); maxX = Mathf.Max(maxX, val.x); maxY = Mathf.Max(maxY, val.y); } } private bool TryGetBounds(out Bounds bounds) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) RefreshRenderersIfNeeded(force: false); bounds = default(Bounds); bool flag = false; if (cachedRenderers == null) { return false; } for (int i = 0; i < eligibleRenderers.Count; i++) { Renderer val = eligibleRenderers[i]; if (!((Object)(object)val == (Object)null) && val.enabled) { if (!flag) { bounds = val.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(val.bounds); } } } return flag; } private void RefreshRenderersIfNeeded(bool force) { if (!force && cachedRenderers != null && Time.time < nextRendererRefreshTime) { return; } cachedRenderers = ((Component)nutcracker).GetComponentsInChildren(false); eligibleRenderers.Clear(); for (int i = 0; i < cachedRenderers.Length; i++) { Renderer val = cachedRenderers[i]; if (IsModelOutlineRenderer(val)) { eligibleRenderers.Add(val); } } nextRendererRefreshTime = Time.time + 1f; } private static Camera GetActiveCamera() { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val != (Object)null && (Object)(object)val.gameplayCamera != (Object)null) { return val.gameplayCamera; } return Camera.main; } private void SetScreenVisible(bool visible) { if ((Object)(object)screenCanvas != (Object)null && ((Behaviour)screenCanvas).enabled != visible) { ((Behaviour)screenCanvas).enabled = visible; } } private static ModelOutlineMode GetMode() { if (NutcrackerShotConfig.ModelOutlineMode != null) { return NutcrackerShotConfig.ModelOutlineMode.Value; } return ModelOutlineMode.MeshSilhouette; } private static ModelPulseMode GetPulseMode() { if (NutcrackerShotConfig.ModelPulseMode != null) { return NutcrackerShotConfig.ModelPulseMode.Value; } return ModelPulseMode.SourcePulse; } private static bool ShouldUseSourcePulse() { ModelPulseMode pulseMode = GetPulseMode(); if (pulseMode != 0) { return pulseMode == ModelPulseMode.Both; } return true; } private static bool ShouldUseCloneShell() { ModelPulseMode pulseMode = GetPulseMode(); if (pulseMode != ModelPulseMode.CloneShell) { return pulseMode == ModelPulseMode.Both; } return true; } private static bool IsModelFireWindowEnabled() { if (NutcrackerShotConfig.EnableModelOutlineFireWindow != null) { return NutcrackerShotConfig.EnableModelOutlineFireWindow.Value; } return true; } private static bool IsModelStateTintEnabled() { if (NutcrackerShotConfig.EnableModelStateTint != null) { return NutcrackerShotConfig.EnableModelStateTint.Value; } return false; } private static Color GetStateTintColor(ModelWarningPhase phase) { //IL_0032: 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) if (phase == ModelWarningPhase.FireWindow) { return new Color(1f, 0.02f, 0.02f, GetFireWindowTintAlpha()); } return new Color(1f, 1f, 1f, GetChaseTintAlpha()); } private static float GetPulseIntensity() { if (NutcrackerShotConfig.ModelPulseIntensity != null) { return Mathf.Clamp(NutcrackerShotConfig.ModelPulseIntensity.Value, 0f, 12f); } return 4f; } private static float GetPulseAlpha() { if (NutcrackerShotConfig.ModelPulseAlpha != null) { return Mathf.Clamp01(NutcrackerShotConfig.ModelPulseAlpha.Value); } return 0.92f; } private static float GetChaseTintAlpha() { if (NutcrackerShotConfig.ModelChaseTintAlpha != null) { return Mathf.Clamp01(NutcrackerShotConfig.ModelChaseTintAlpha.Value); } return 0.58f; } private static float GetChaseTintIntensity() { if (NutcrackerShotConfig.ModelChaseTintIntensity != null) { return Mathf.Clamp(NutcrackerShotConfig.ModelChaseTintIntensity.Value, 0f, 12f); } return 1.8f; } private static float GetFireWindowTintAlpha() { if (NutcrackerShotConfig.ModelFireWindowTintAlpha != null) { return Mathf.Clamp01(NutcrackerShotConfig.ModelFireWindowTintAlpha.Value); } return 0.96f; } private static float GetFireWindowTintIntensity() { if (NutcrackerShotConfig.ModelFireWindowTintIntensity != null) { return Mathf.Clamp(NutcrackerShotConfig.ModelFireWindowTintIntensity.Value, 0f, 12f); } return 5f; } private static bool IsDebugLoggingEnabled() { if (NutcrackerShotConfig.EnableDebugLogs != null) { return NutcrackerShotConfig.EnableDebugLogs.Value; } return false; } private static bool ShouldDumpModelAudit() { if (NutcrackerShotConfig.DumpModelAudit != null) { return NutcrackerShotConfig.DumpModelAudit.Value; } return false; } private static bool IsModelOutlineRenderer(Renderer renderer) { if ((Object)(object)renderer == (Object)null || renderer is LineRenderer || renderer is ParticleSystemRenderer) { return false; } if ((Object)(object)((Component)renderer).GetComponentInParent() != (Object)null) { return false; } string path = GetPath(((Component)renderer).transform); if (ContainsAny(path, "MapDot", "ScanNode", "BloodSpurtParticle", "Collider", "NutcrackerMeshOutline", "NutcrackerFireWindow")) { return false; } SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((renderer is SkinnedMeshRenderer) ? renderer : null); if ((Object)(object)val != (Object)null) { string value = (((Object)(object)val.sharedMesh == (Object)null) ? string.Empty : ((Object)val.sharedMesh).name); if (!ContainsAny(path, "LOD0")) { return ContainsAny(value, "LOD0"); } return true; } MeshFilter component = ((Component)renderer).GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component.sharedMesh == (Object)null) { return false; } string name = ((Object)component.sharedMesh).name; if (!ContainsAny(path, "Gun", "Shotgun")) { return ContainsAny(name, "Gun", "Shotgun"); } return true; } private static string GetRendererTypeName(Renderer renderer) { if (renderer is SkinnedMeshRenderer) { return "SkinnedMeshRenderer"; } if (renderer is MeshRenderer) { return "MeshRenderer"; } if (renderer is ParticleSystemRenderer) { return "ParticleSystemRenderer"; } if (renderer is LineRenderer) { return "LineRenderer"; } return "Renderer"; } private static bool ContainsAny(string value, params string[] fragments) { if (string.IsNullOrEmpty(value)) { return false; } for (int i = 0; i < fragments.Length; i++) { if (value.IndexOf(fragments[i], StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static string GetPath(Transform transform) { if ((Object)(object)transform == (Object)null) { return string.Empty; } string text = ((Object)transform).name; Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } internal enum ModelWarningPhase { None, Chase, FireWindow } internal static class NutcrackerModelWarningPhaseSelector { public static ModelWarningPhase Select(bool modelEnabled, bool stateTintEnabled, bool fireWindowActive, bool chaseActive) { if (!modelEnabled) { return ModelWarningPhase.None; } if (fireWindowActive) { return ModelWarningPhase.FireWindow; } if (!(stateTintEnabled && chaseActive)) { return ModelWarningPhase.None; } return ModelWarningPhase.Chase; } } [HarmonyPatch(typeof(NutcrackerEnemyAI))] internal static class NutcrackerPatches { [HarmonyPostfix] [HarmonyPatch("Update")] private static void UpdatePostfix(NutcrackerEnemyAI __instance) { if (NutcrackerShotConfig.IsModEnabled() && !((Object)(object)__instance == (Object)null) && !((EnemyAI)__instance).isEnemyDead && NutcrackerCombatStateReader.TryRead(__instance, out var combatState)) { NutcrackerShotIndicator nutcrackerShotIndicator = NutcrackerShotIndicator.GetExisting(__instance); if ((Object)(object)nutcrackerShotIndicator == (Object)null && NutcrackerShotIndicator.ShouldCreateForCombatState(__instance, combatState)) { nutcrackerShotIndicator = NutcrackerShotIndicator.For(__instance); } nutcrackerShotIndicator?.ObserveCombatState(combatState); } } [HarmonyPostfix] [HarmonyPatch("AimGunClientRpc")] private static void AimGunClientRpcPostfix(NutcrackerEnemyAI __instance) { if (NutcrackerShotConfig.IsModEnabled()) { NutcrackerShotIndicator.For(__instance)?.BeginAim(NutcrackerCombatStateReader.GetAimDuration(__instance)); } } [HarmonyPostfix] [HarmonyPatch("FireGunClientRpc")] private static void FireGunClientRpcPostfix(NutcrackerEnemyAI __instance) { if (NutcrackerShotConfig.IsModEnabled()) { NutcrackerShotIndicator.For(__instance)?.MarkFired(); } } [HarmonyPostfix] [HarmonyPatch("ReloadGunClientRpc")] private static void ReloadGunClientRpcPostfix(NutcrackerEnemyAI __instance) { if (NutcrackerShotConfig.IsModEnabled()) { NutcrackerShotIndicator.For(__instance)?.BeginReload(1.74f); } } [HarmonyPostfix] [HarmonyPatch("KillEnemy")] private static void KillEnemyPostfix(NutcrackerEnemyAI __instance) { NutcrackerShotIndicator.Remove(__instance); } } internal static class NutcrackerShotConfig { internal const bool DefaultEnableModelOutlineFireWindow = false; internal const bool DefaultEnableModelStateTint = true; private static bool useChineseDescriptions; public static ConfigEntry EnableMod { get; private set; } public static ConfigEntry EnableUiFireWindow { get; private set; } public static ConfigEntry EnableModelOutlineFireWindow { get; private set; } public static ConfigEntry EnableModelStateTint { get; private set; } public static ConfigEntry ModelOutlineWidth { get; private set; } public static ConfigEntry ModelOutlineMode { get; private set; } public static ConfigEntry ModelPulseMode { get; private set; } public static ConfigEntry MeshOutlineScale { get; private set; } public static ConfigEntry ModelPulseIntensity { get; private set; } public static ConfigEntry ModelPulseAlpha { get; private set; } public static ConfigEntry ModelChaseTintAlpha { get; private set; } public static ConfigEntry ModelChaseTintIntensity { get; private set; } public static ConfigEntry ModelFireWindowTintAlpha { get; private set; } public static ConfigEntry ModelFireWindowTintIntensity { get; private set; } public static ConfigEntry ModelWarningMaxDistance { get; private set; } public static ConfigEntry ModelWarningRequireCameraVisible { get; private set; } public static ConfigEntry FireWindowSeconds { get; private set; } public static ConfigEntry PreAimMaxDistance { get; private set; } public static ConfigEntry MonitorActiveScanInterval { get; private set; } public static ConfigEntry MonitorIdleScanInterval { get; private set; } public static ConfigEntry EnableDebugLogs { get; private set; } public static ConfigEntry DumpModelAudit { get; private set; } public static void Bind(ConfigFile config) { useChineseDescriptions = NutcrackerConfigLanguageDetector.ShouldUseChineseConfig(); EnableMod = config.Bind("General", "EnableMod", true, Description("Enable Nutcracker Fire Warning. When disabled, the plugin does not create or update warning UI, model warnings, or fallback scans.", "启用 Nutcracker Fire Warning 总开关。关闭后不会创建或更新预警 UI、模型预警或备用扫描。")); EnableUiFireWindow = config.Bind("Warnings", "EnableUiFireWindow", true, Description("Show the world-space side warning bar, including countdown, FIRE pulse, reload bar, and pre-aim danger bar.", "显示胡桃身侧的世界空间竖条 UI,包括倒计时、FIRE 脉冲、换弹条和预瞄危险提示。")); EnableModelOutlineFireWindow = config.Bind("Warnings", "EnableModelOutlineFireWindow", false, Description("Enable the extra fire-window-only model overlay: red-white pulse, clone shell, or screen box depending on ModelOutlineMode and ModelPulseMode. Usually keep this disabled when EnableModelStateTint is enabled.", "启用仅在最终开火窗口出现的额外模型叠加预警:根据 ModelOutlineMode 和 ModelPulseMode 显示红白脉冲、克隆外壳或屏幕框。启用 EnableModelStateTint 时通常保持关闭。")); EnableModelStateTint = config.Bind("Warnings", "EnableModelStateTint", true, Description("Enable the recommended model state warning: white while the Nutcracker is chasing a target, red during the final fire window.", "启用推荐的模型状态预警:胡桃追击目标时模型变白,进入最终开火窗口时模型变红。")); ModelOutlineWidth = config.Bind("Warnings", "ModelOutlineWidth", 0.075f, Description("Line width for ScreenBox mode. In MeshSilhouette mode this is the normal-expanded edge thickness.", "ScreenBox 模式的线宽;MeshSilhouette 模式下为法线扩展轮廓厚度。")); ModelOutlineMode = config.Bind("Warnings", "ModelOutlineMode", NutcrackerShotUI.ModelOutlineMode.MeshSilhouette, Description("Model outline style. MeshSilhouette follows the Nutcracker model; ScreenBox draws a reliable HUD fallback rectangle.", "模型轮廓样式。MeshSilhouette 贴合胡桃模型;ScreenBox 绘制稳定的屏幕空间备用矩形。")); ModelPulseMode = config.Bind("Warnings", "ModelPulseMode", NutcrackerShotUI.ModelPulseMode.SourcePulse, Description("Implementation used by the extra fire-window overlay in MeshSilhouette mode. SourcePulse recolors the original model; CloneShell adds a cloned shell; Both enables both.", "MeshSilhouette 模式下额外开火窗口叠加层的实现方式。SourcePulse 重染原模型;CloneShell 添加克隆外壳;Both 同时启用两者。")); MeshOutlineScale = config.Bind("Warnings", "MeshOutlineScale", 1f, Description("Extra transform scale for MeshSilhouette outline clones. Keep this at 1 for the most exact model edge.", "MeshSilhouette 克隆轮廓的额外缩放。保持 1 可获得最贴近模型边缘的效果。")); ModelPulseIntensity = config.Bind("Warnings", "ModelPulseIntensity", 4f, Description("Emission multiplier for the extra red-white fire-window overlay.", "额外红白开火窗口叠加层的发光强度倍率。")); ModelPulseAlpha = config.Bind("Warnings", "ModelPulseAlpha", 0.92f, Description("Alpha used by the extra red-white fire-window overlay.", "额外红白开火窗口叠加层的不透明度。")); ModelChaseTintAlpha = config.Bind("Warnings", "ModelChaseTintAlpha", 0.58f, Description("Alpha used when the Nutcracker model is tinted white while chasing.", "胡桃追击目标时白色模型染色的不透明度。")); ModelChaseTintIntensity = config.Bind("Warnings", "ModelChaseTintIntensity", 1.8f, Description("Emission multiplier for the white chase-state model tint.", "胡桃追击目标时白色模型染色的发光强度倍率。")); ModelFireWindowTintAlpha = config.Bind("Warnings", "ModelFireWindowTintAlpha", 0.96f, Description("Alpha used when the Nutcracker model is tinted red during the final fire window.", "胡桃进入最终开火窗口时红色模型染色的不透明度。")); ModelFireWindowTintIntensity = config.Bind("Warnings", "ModelFireWindowTintIntensity", 5f, Description("Emission multiplier for the red fire-window model tint.", "胡桃进入最终开火窗口时红色模型染色的发光强度倍率。")); ModelWarningMaxDistance = config.Bind("Warnings", "ModelWarningMaxDistance", 45f, Description("Maximum local-player distance for model warnings. Set to 0 or lower to disable distance filtering.", "模型预警相对本地玩家的最大显示距离。设为 0 或更低可关闭距离过滤。")); ModelWarningRequireCameraVisible = config.Bind("Warnings", "ModelWarningRequireCameraVisible", false, Description("Only show model warnings when the Nutcracker is inside the local camera viewport.", "仅当胡桃位于本地摄像机视野内时显示模型预警。")); FireWindowSeconds = config.Bind("Warnings", "FireWindowSeconds", 0.35f, Description("Seconds before the predicted shot when fire-window warnings activate.", "预测开火前多少秒进入最终开火窗口预警。")); PreAimMaxDistance = config.Bind("Warnings", "PreAimMaxDistance", 30f, Description("Maximum distance for the yellow pre-aim danger bar. Set to 0 or lower to disable the pre-aim bar.", "黄色预瞄危险条的最大距离。设为 0 或更低可关闭预瞄条。")); MonitorActiveScanInterval = config.Bind("Performance", "MonitorActiveScanInterval", 0.5f, Description("Fallback monitor scan interval while Nutcrackers are present.", "场上存在胡桃时,备用监视器的扫描间隔。")); MonitorIdleScanInterval = config.Bind("Performance", "MonitorIdleScanInterval", 2f, Description("Fallback monitor scan interval while no Nutcrackers are present.", "场上没有胡桃时,备用监视器的扫描间隔。")); EnableDebugLogs = config.Bind("Debug", "EnableDebugLogs", false, Description("Log aim/fire warning events to BepInEx LogOutput.log.", "将瞄准/开火预警事件记录到 BepInEx LogOutput.log。")); DumpModelAudit = config.Bind("Debug", "DumpModelAudit", false, Description("Log Nutcracker renderer and mesh names/counts once when the model outline is built.", "构建模型轮廓时记录一次胡桃 Renderer 和 Mesh 的名称/数量。")); } public static bool IsModEnabled() { if (EnableMod != null) { return EnableMod.Value; } return true; } private static string Description(string english, string chinese) { if (!useChineseDescriptions) { return english; } return chinese; } } internal enum ModelOutlineMode { MeshSilhouette, ScreenBox } internal enum ModelPulseMode { SourcePulse, CloneShell, Both } internal sealed class NutcrackerShotIndicator : MonoBehaviour { private enum WarningState { Idle, Aiming, Fired, Reloading } private const float BarWidth = 0.09f; private const float BarHeight = 1.35f; private const float SideOffset = 1.08f; private const float HeightOffset = 2.05f; private const float FiredHoldTime = 0.35f; private const float DefaultCriticalAimWindow = 0.35f; private const float PreAimMaxDistance = 30f; private const float PreAimCheckInterval = 0.12f; private const float PreAimAngleDegrees = 35f; private static readonly Color BackgroundColor = new Color(0.02f, 0.02f, 0.02f, 0.55f); private static readonly Color PreAimColor = new Color(1f, 0.72f, 0.12f, 0.55f); private static readonly Color AimColor = new Color(1f, 0.12f, 0.06f, 0.92f); private static readonly Color FiredColor = new Color(1f, 1f, 1f, 0.96f); private static readonly Color ReloadColor = new Color(0.2f, 0.65f, 1f, 0.55f); private static readonly float PreAimMinimumDot = Mathf.Cos(0.61086524f); private static readonly string[] CountdownLabels = CreateCountdownLabels(); private static readonly Dictionary Indicators = new Dictionary(); private NutcrackerEnemyAI nutcracker; private Canvas canvas; private Image background; private Image fill; private Image rim; private Image criticalPulse; private Text countdownText; private NutcrackerModelFireWindowOutline modelOutline; private WarningState state; private float stateStartedAt; private float stateDuration; private float lastPreAimAmount; private float nextPreAimCheckTime; private float cachedPreAimDanger; private bool observedAimingLastFrame; private bool observedReloadingLastFrame; private NutcrackerCombatState lastCombatState; public static NutcrackerShotIndicator For(NutcrackerEnemyAI nutcracker) { //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) if (!NutcrackerShotConfig.IsModEnabled() || (Object)(object)nutcracker == (Object)null) { return null; } NutcrackerShotIndicator existing = GetExisting(nutcracker); if ((Object)(object)existing != (Object)null) { return existing; } GameObject val = new GameObject("NutcrackerShotUI"); val.transform.SetParent(((Component)nutcracker).transform, false); existing = val.AddComponent(); existing.Initialize(nutcracker); Indicators[nutcracker] = existing; return existing; } public static NutcrackerShotIndicator GetExisting(NutcrackerEnemyAI nutcracker) { if ((Object)(object)nutcracker == (Object)null) { return null; } if (Indicators.TryGetValue(nutcracker, out var value)) { if ((Object)(object)value != (Object)null) { return value; } Indicators.Remove(nutcracker); } return null; } public static bool ShouldCreateForCombatState(NutcrackerEnemyAI nutcracker, NutcrackerCombatState combatState) { if (!NutcrackerShotConfig.IsModEnabled() || (Object)(object)nutcracker == (Object)null || ((EnemyAI)nutcracker).isEnemyDead) { return false; } if (combatState.AimingGun || combatState.ReloadingGun) { return true; } if (IsModelStateTintEnabled() && IsChaseTintCandidate(nutcracker, combatState)) { return true; } if (IsUiFireWindowEnabled() && GetPreAimMaxDistance() > 0f && ((EnemyAI)nutcracker).currentBehaviourStateIndex == 2 && (Object)(object)nutcracker.gun != (Object)null) { return nutcracker.gun.shellsLoaded > 0; } return false; } public static void Remove(NutcrackerEnemyAI nutcracker) { if (!((Object)(object)nutcracker == (Object)null)) { NutcrackerShotIndicator existing = GetExisting(nutcracker); if ((Object)(object)existing != (Object)null) { Indicators.Remove(nutcracker); Object.Destroy((Object)(object)((Component)existing).gameObject); } } } public void BeginAim(float duration) { if (NutcrackerShotConfig.IsModEnabled()) { if (duration <= 0f) { duration = 0.5f; } state = WarningState.Aiming; stateStartedAt = Time.time; stateDuration = duration; SetVisible(visible: true); if (IsDebugLoggingEnabled()) { Plugin.Log.LogInfo((object)$"Nutcracker #{((EnemyAI)nutcracker).thisEnemyIndex} aim warning started. Duration={duration:0.00}s Shells={nutcracker.gun?.shellsLoaded ?? (-1)} HP={((EnemyAI)nutcracker).enemyHP}"); } } } public void MarkFired() { if (NutcrackerShotConfig.IsModEnabled()) { state = WarningState.Fired; stateStartedAt = Time.time; stateDuration = 0.35f; SetVisible(visible: true); if (IsDebugLoggingEnabled()) { Plugin.Log.LogInfo((object)$"Nutcracker #{((EnemyAI)nutcracker).thisEnemyIndex} fired."); } } } public void BeginReload(float duration) { if (NutcrackerShotConfig.IsModEnabled()) { state = WarningState.Reloading; stateStartedAt = Time.time; stateDuration = duration; SetVisible(visible: true); } } public void ObserveCombatState(bool aimingGun, bool reloadingGun, float timeSinceFiringGun, float aimDuration) { if (!NutcrackerShotConfig.IsModEnabled()) { return; } if (aimingGun) { float num = Mathf.Max(0.05f, aimDuration); float num2 = Mathf.Clamp(timeSinceFiringGun, 0f, num); if (!observedAimingLastFrame || state != WarningState.Aiming) { state = WarningState.Aiming; stateDuration = num; stateStartedAt = Time.time - num2; SetVisible(visible: true); if (IsDebugLoggingEnabled()) { Plugin.Log.LogInfo((object)$"Nutcracker #{((EnemyAI)nutcracker).thisEnemyIndex} aim warning observed from Update. Duration={num:0.00}s Elapsed={num2:0.00}s Shells={nutcracker.gun?.shellsLoaded ?? (-1)} HP={((EnemyAI)nutcracker).enemyHP}"); } } } else if (observedAimingLastFrame && state == WarningState.Aiming) { state = WarningState.Idle; SetCriticalPulse(0f); UpdateModelWarningPhase(fireWindowActive: false); } if (reloadingGun && !observedReloadingLastFrame && state != WarningState.Aiming) { BeginReload(1.74f); } observedAimingLastFrame = aimingGun; observedReloadingLastFrame = reloadingGun; } public void ObserveCombatState(NutcrackerCombatState combatState) { lastCombatState = combatState; ObserveCombatState(combatState.AimingGun, combatState.ReloadingGun, combatState.TimeSinceFiringGun, combatState.AimDuration); } private void Initialize(NutcrackerEnemyAI owner) { nutcracker = owner; BuildWorldBar(); modelOutline = new NutcrackerModelFireWindowOutline(owner, ((Component)this).transform); SetVisible(visible: false); } private void OnDestroy() { modelOutline?.Dispose(); if ((Object)(object)nutcracker != (Object)null && Indicators.TryGetValue(nutcracker, out var value) && (Object)(object)value == (Object)(object)this) { Indicators.Remove(nutcracker); } } private void OnDisable() { SetModelWarningPhase(ModelWarningPhase.None); } private void LateUpdate() { if (!NutcrackerShotConfig.IsModEnabled() || (Object)(object)nutcracker == (Object)null || ((EnemyAI)nutcracker).isEnemyDead) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } if (IsUiFireWindowEnabled()) { UpdateTransform(); } UpdateState(); } private void BuildWorldBar() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) canvas = ((Component)this).gameObject.AddComponent(); canvas.renderMode = (RenderMode)2; canvas.sortingOrder = 200; RectTransform component = ((Component)canvas).GetComponent(); component.sizeDelta = new Vector2(0.09f, 1.35f); ((Transform)component).localScale = Vector3.one; background = CreateImage("Background", ((Component)this).transform, BackgroundColor); RectTransform rectTransform = ((Graphic)background).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; fill = CreateImage("Fill", ((Component)background).transform, AimColor); fill.type = (Type)3; fill.fillMethod = (FillMethod)1; fill.fillOrigin = 0; fill.fillAmount = 0f; RectTransform rectTransform2 = ((Graphic)fill).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; rectTransform2.offsetMin = new Vector2(0.012f, 0.012f); rectTransform2.offsetMax = new Vector2(-0.012f, -0.012f); rim = CreateImage("Rim", ((Component)this).transform, new Color(1f, 1f, 1f, 0.26f)); RectTransform rectTransform3 = ((Graphic)rim).rectTransform; rectTransform3.anchorMin = Vector2.zero; rectTransform3.anchorMax = Vector2.one; rectTransform3.offsetMin = Vector2.zero; rectTransform3.offsetMax = Vector2.zero; criticalPulse = CreateImage("CriticalPulse", ((Component)this).transform, new Color(1f, 0f, 0f, 0f)); RectTransform rectTransform4 = ((Graphic)criticalPulse).rectTransform; rectTransform4.anchorMin = Vector2.zero; rectTransform4.anchorMax = Vector2.one; rectTransform4.offsetMin = new Vector2(-0.018f, -0.018f); rectTransform4.offsetMax = new Vector2(0.018f, 0.018f); countdownText = CreateText("Countdown", ((Component)this).transform); RectTransform rectTransform5 = ((Graphic)countdownText).rectTransform; rectTransform5.anchorMin = new Vector2(0.5f, 1f); rectTransform5.anchorMax = new Vector2(0.5f, 1f); rectTransform5.pivot = new Vector2(0.5f, 0f); rectTransform5.sizeDelta = new Vector2(0.58f, 0.22f); rectTransform5.anchoredPosition = new Vector2(0f, 0.08f); } private static Image CreateImage(string name, Transform parent, Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); Image obj = val.AddComponent(); ((Graphic)obj).color = color; ((Graphic)obj).raycastTarget = false; return obj; } private static Text CreateText(string name, Transform parent) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); Text obj = val.AddComponent(); obj.font = Resources.GetBuiltinResource("Arial.ttf"); obj.alignment = (TextAnchor)4; ((Graphic)obj).color = new Color(1f, 1f, 1f, 0.95f); obj.fontSize = 42; obj.resizeTextForBestFit = true; obj.resizeTextMinSize = 14; obj.resizeTextMaxSize = 42; ((Graphic)obj).raycastTarget = false; return obj; } private void UpdateTransform() { //IL_0013: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //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_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: 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_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) Transform transform = ((Component)nutcracker).transform; ((Component)this).transform.position = transform.position + transform.right * 1.08f + Vector3.up * 2.05f; Camera activeCamera = GetActiveCamera(); if ((Object)(object)activeCamera != (Object)null) { Vector3 val = ((Component)this).transform.position - ((Component)activeCamera).transform.position; if (((Vector3)(ref val)).sqrMagnitude > 0.001f) { ((Component)this).transform.rotation = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up); } } } private void UpdateState() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) bool flag = IsUiFireWindowEnabled(); if (!flag) { HideUiElements(); } switch (state) { case WarningState.Aiming: UpdateAimingState(); return; case WarningState.Fired: UpdateTimedState(FiredColor, 1f, hideWhenFinished: true); return; case WarningState.Reloading: UpdateTimedState(ReloadColor, reverse: true, hideWhenFinished: true); return; } if (flag) { UpdatePreAim(); return; } lastPreAimAmount = 0f; UpdateModelWarningPhase(fireWindowActive: false); } private void UpdateTimedState(Color color, float alpha, bool hideWhenFinished) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) UpdateTimedState(color.WithAlpha(alpha), reverse: false, hideWhenFinished); } private void UpdateTimedState(Color color, bool reverse, bool hideWhenFinished) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Clamp01((Time.time - stateStartedAt) / Mathf.Max(0.01f, stateDuration)); if (IsUiFireWindowEnabled()) { ((Graphic)fill).color = color; fill.fillAmount = (reverse ? (1f - num) : num); SetCountdownText(null, Color.white); SetCriticalPulse(0f); } else { HideUiElements(); } UpdateModelWarningPhase(fireWindowActive: false); if (num >= 1f) { state = WarningState.Idle; if (hideWhenFinished) { SetVisible(visible: false); } } } private void UpdateAimingState() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) float num = Time.time - stateStartedAt; float num2 = Mathf.Max(0.01f, stateDuration); float num3 = Mathf.Clamp01(num / num2); float num4 = Mathf.Max(0f, num2 - num); float fireWindowSeconds = GetFireWindowSeconds(); float num5 = 1f - Mathf.Clamp01(num4 / fireWindowSeconds); bool flag = IsUiFireWindowEnabled(); if (flag) { fill.fillAmount = num3; ((Graphic)fill).color = Color.Lerp(AimColor, FiredColor, num5 * 0.45f); } else { HideUiElements(); } if (num4 <= fireWindowSeconds) { UpdateModelWarningPhase(fireWindowActive: true); if (flag) { float num6 = 0.45f + Mathf.PingPong(Time.time * 12f, 0.55f); SetCriticalPulse(num6); SetCountdownText("FIRE", Color.Lerp(AimColor, FiredColor, num6)); } } else { UpdateModelWarningPhase(fireWindowActive: false); if (flag) { SetCriticalPulse(0f); SetRemainingCountdown(num4); } } if (num3 >= 1f) { state = WarningState.Idle; UpdateModelWarningPhase(fireWindowActive: false); if (flag) { fill.fillAmount = 1f; SetCountdownText("NOW", FiredColor); SetCriticalPulse(0.85f); } else { SetCountdownText(null, Color.white); SetCriticalPulse(0f); } } } private void UpdatePreAim() { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (Time.time >= nextPreAimCheckTime) { cachedPreAimDanger = GetPreAimDanger(); nextPreAimCheckTime = Time.time + 0.12f; } float num = cachedPreAimDanger; lastPreAimAmount = Mathf.MoveTowards(lastPreAimAmount, num, Time.deltaTime * 4f); if (lastPreAimAmount <= 0.01f) { SetVisible(visible: false); fill.fillAmount = 0f; SetCountdownText(null, Color.white); SetCriticalPulse(0f); UpdateModelWarningPhase(fireWindowActive: false); } else { SetVisible(visible: true); ((Graphic)fill).color = PreAimColor; fill.fillAmount = Mathf.Clamp01(lastPreAimAmount); SetCountdownText("!", PreAimColor.WithAlpha(0.92f)); SetCriticalPulse(0f); UpdateModelWarningPhase(fireWindowActive: false); } } private float GetPreAimDanger() { //IL_007c: 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_0092: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) if (((EnemyAI)nutcracker).currentBehaviourStateIndex != 2 || (Object)(object)nutcracker.gun == (Object)null || nutcracker.gun.shellsLoaded <= 0) { return 0f; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null || val.isPlayerDead || (Object)(object)val.gameplayCamera == (Object)null) { return 0f; } Vector3 position = ((Component)val.gameplayCamera).transform.position; Vector3 position2 = nutcracker.gun.shotgunRayPoint.position; Vector3 val2 = position - position2; float preAimMaxDistance = GetPreAimMaxDistance(); if (preAimMaxDistance <= 0f || ((Vector3)(ref val2)).sqrMagnitude > preAimMaxDistance * preAimMaxDistance) { return 0f; } float magnitude = ((Vector3)(ref val2)).magnitude; if (magnitude <= 0.001f) { return 0f; } Vector3 val3 = val2 / magnitude; float num = Vector3.Dot(nutcracker.gun.shotgunRayPoint.forward, val3); if (num < PreAimMinimumDot) { return 0f; } if (Physics.Linecast(position2, position, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1)) { return 0f; } float num2 = 1f - Mathf.Clamp01(magnitude / preAimMaxDistance); float num3 = Mathf.InverseLerp(PreAimMinimumDot, 1f, num); return Mathf.Clamp01(0.2f + num2 * 0.35f + num3 * 0.45f); } private static Camera GetActiveCamera() { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val != (Object)null && (Object)(object)val.gameplayCamera != (Object)null) { return val.gameplayCamera; } return Camera.main; } private void SetVisible(bool visible) { bool flag = visible && IsUiFireWindowEnabled(); if ((Object)(object)canvas != (Object)null && ((Behaviour)canvas).enabled != flag) { ((Behaviour)canvas).enabled = flag; } } private void HideUiElements() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) SetVisible(visible: false); if ((Object)(object)fill != (Object)null) { fill.fillAmount = 0f; } SetCountdownText(null, Color.white); SetCriticalPulse(0f); } private void SetCountdownText(string text, Color color) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)countdownText == (Object)null)) { bool flag = !string.IsNullOrEmpty(text); string text2 = text ?? string.Empty; if (((Behaviour)countdownText).enabled != flag) { ((Behaviour)countdownText).enabled = flag; } if (countdownText.text != text2) { countdownText.text = text2; } ((Graphic)countdownText).color = color; } } private void SetRemainingCountdown(float remaining) { //IL_0024: 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) int num = Mathf.Clamp(Mathf.RoundToInt(remaining * 10f), 0, CountdownLabels.Length - 1); SetCountdownText(CountdownLabels[num], AimColor.WithAlpha(0.96f)); } private static string[] CreateCountdownLabels() { string[] array = new string[21]; for (int i = 0; i < array.Length; i++) { array[i] = ((float)i * 0.1f).ToString("0.0"); } return array; } private void SetCriticalPulse(float alpha) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)criticalPulse == (Object)null)) { Color color = ((Graphic)criticalPulse).color; color.a = Mathf.Clamp01(alpha) * 0.55f; ((Graphic)criticalPulse).color = color; } } private void SetModelWarningPhase(ModelWarningPhase phase) { modelOutline?.Update(phase); } private void UpdateModelWarningPhase(bool fireWindowActive) { bool flag = IsAnyModelWarningEnabled(); bool flag2 = IsModelStateTintEnabled(); bool flag3 = flag && ShouldShowModelWarningByDistanceAndCamera(); bool fireWindowActive2 = fireWindowActive && flag3 && (IsModelFireWindowEnabled() || flag2); bool chaseActive = flag3 && flag2 && IsChaseTintCandidate(nutcracker, lastCombatState); SetModelWarningPhase(NutcrackerModelWarningPhaseSelector.Select(flag, flag2, fireWindowActive2, chaseActive)); } private bool ShouldShowModelWarningByDistanceAndCamera() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0108: 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_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) if (!IsAnyModelWarningEnabled()) { return false; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; Camera val2 = (((Object)(object)val != (Object)null && (Object)(object)val.gameplayCamera != (Object)null) ? val.gameplayCamera : Camera.main); if ((Object)(object)val != (Object)null && val.isPlayerDead) { return false; } float modelWarningMaxDistance = GetModelWarningMaxDistance(); if (modelWarningMaxDistance > 0f) { Vector3 val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).transform.position : (((Object)(object)val != (Object)null) ? ((Component)val).transform.position : Vector3.zero)); if (val3 != Vector3.zero && Vector3.Distance(val3, ((Component)nutcracker).transform.position) > modelWarningMaxDistance) { return false; } } if (NutcrackerShotConfig.ModelWarningRequireCameraVisible != null && NutcrackerShotConfig.ModelWarningRequireCameraVisible.Value) { if ((Object)(object)val2 == (Object)null) { return false; } Vector3 val4 = val2.WorldToViewportPoint(((Component)nutcracker).transform.position + Vector3.up * 1.4f); if (val4.z <= 0f || val4.x < -0.1f || val4.x > 1.1f || val4.y < -0.1f || val4.y > 1.1f) { return false; } } return true; } private static bool IsChaseTintCandidate(NutcrackerEnemyAI nutcracker, NutcrackerCombatState combatState) { if ((Object)(object)nutcracker == (Object)null || ((EnemyAI)nutcracker).isEnemyDead || ((EnemyAI)nutcracker).currentBehaviourStateIndex != 2) { return false; } if (combatState.ReloadingGun || (Object)(object)nutcracker.gun == (Object)null || nutcracker.gun.shellsLoaded <= 0) { return false; } if (combatState.AimingGun) { return true; } if (nutcracker.lastPlayerSeenMoving != -1 && (Object)(object)((EnemyAI)nutcracker).targetPlayer != (Object)null) { return ((EnemyAI)nutcracker).movingTowardsTargetPlayer; } return false; } private static bool IsUiFireWindowEnabled() { if (NutcrackerShotConfig.EnableUiFireWindow != null) { return NutcrackerShotConfig.EnableUiFireWindow.Value; } return true; } private static bool IsModelFireWindowEnabled() { if (NutcrackerShotConfig.EnableModelOutlineFireWindow != null) { return NutcrackerShotConfig.EnableModelOutlineFireWindow.Value; } return true; } private static bool IsModelStateTintEnabled() { if (NutcrackerShotConfig.EnableModelStateTint != null) { return NutcrackerShotConfig.EnableModelStateTint.Value; } return false; } private static bool IsAnyModelWarningEnabled() { if (!IsModelFireWindowEnabled()) { return IsModelStateTintEnabled(); } return true; } private static float GetFireWindowSeconds() { if (NutcrackerShotConfig.FireWindowSeconds != null) { return Mathf.Clamp(NutcrackerShotConfig.FireWindowSeconds.Value, 0.05f, 2f); } return 0.35f; } private static float GetPreAimMaxDistance() { if (NutcrackerShotConfig.PreAimMaxDistance == null) { return 30f; } float value = NutcrackerShotConfig.PreAimMaxDistance.Value; if (!(value <= 0f)) { return Mathf.Clamp(value, 1f, 100f); } return 0f; } private static float GetModelWarningMaxDistance() { if (NutcrackerShotConfig.ModelWarningMaxDistance != null) { return NutcrackerShotConfig.ModelWarningMaxDistance.Value; } return 45f; } private static bool IsDebugLoggingEnabled() { if (NutcrackerShotConfig.EnableDebugLogs != null) { return NutcrackerShotConfig.EnableDebugLogs.Value; } return false; } } internal static class ColorExtensions { public static Color WithAlpha(this Color color, float alpha) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) color.a = alpha; return color; } } internal sealed class NutcrackerShotMonitor : MonoBehaviour { private float nextScanTime; private int lastObservedCount = -1; private void Awake() { Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private void Update() { if (!NutcrackerShotConfig.IsModEnabled()) { nextScanTime = Time.time + GetConfigInterval(NutcrackerShotConfig.MonitorIdleScanInterval, 2f); } else { if (Time.time < nextScanTime) { return; } NutcrackerEnemyAI[] array = Object.FindObjectsOfType(); nextScanTime = Time.time + GetNextScanInterval(array.Length); if (IsDebugLoggingEnabled() && array.Length != lastObservedCount) { lastObservedCount = array.Length; Plugin.Log.LogInfo((object)$"Nutcracker monitor found {array.Length} NutcrackerEnemyAI instance(s)."); } foreach (NutcrackerEnemyAI val in array) { if (!((Object)(object)val == (Object)null) && !((EnemyAI)val).isEnemyDead && NutcrackerCombatStateReader.TryRead(val, out var combatState)) { NutcrackerShotIndicator nutcrackerShotIndicator = NutcrackerShotIndicator.GetExisting(val); if ((Object)(object)nutcrackerShotIndicator == (Object)null && NutcrackerShotIndicator.ShouldCreateForCombatState(val, combatState)) { nutcrackerShotIndicator = NutcrackerShotIndicator.For(val); } nutcrackerShotIndicator?.ObserveCombatState(combatState); } } } } private static float GetNextScanInterval(int observedCount) { return Mathf.Clamp((observedCount > 0) ? GetConfigInterval(NutcrackerShotConfig.MonitorActiveScanInterval, 0.5f) : GetConfigInterval(NutcrackerShotConfig.MonitorIdleScanInterval, 2f), 0.02f, 5f); } private static float GetConfigInterval(ConfigEntry entry, float fallback) { return entry?.Value ?? fallback; } private static bool IsDebugLoggingEnabled() { if (NutcrackerShotConfig.EnableDebugLogs != null) { return NutcrackerShotConfig.EnableDebugLogs.Value; } return false; } } [BepInPlugin("aueser.lethalcompany.nutcrackerfirewarning", "Nutcracker Fire Warning", "1.0.6")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "aueser.lethalcompany.nutcrackerfirewarning"; public const string PluginName = "Nutcracker Fire Warning"; public const string PluginVersion = "1.0.6"; private Harmony harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; NutcrackerShotConfig.Bind(((BaseUnityPlugin)this).Config); CreateMonitor(); harmony = new Harmony("aueser.lethalcompany.nutcrackerfirewarning"); harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. enabled={2}, UI fire window={3}, model outline={4}, debug={5}.", "Nutcracker Fire Warning", "1.0.6", NutcrackerShotConfig.EnableMod.Value, NutcrackerShotConfig.EnableUiFireWindow.Value, NutcrackerShotConfig.EnableModelOutlineFireWindow.Value, NutcrackerShotConfig.EnableDebugLogs.Value)); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } private static void CreateMonitor() { //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) new GameObject("NutcrackerShotUIMonitor") { hideFlags = (HideFlags)61 }.AddComponent(); } } }