using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppTMPro;
using Kp0hyc.Labyrinthine.Shared;
using MelonLoader;
using MelonLoader.Preferences;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(DeathBodyOutline), "Death Body Outline", "0.2.0", "kp0hyc", null)]
[assembly: MelonGame("Valko Game Studios", "Labyrinthine")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("DeathBodyOutline")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("DeathBodyOutline")]
[assembly: AssemblyTitle("DeathBodyOutline")]
[assembly: AssemblyVersion("1.0.0.0")]
public class DeathBodyOutline : MelonMod
{
private sealed class BodyCloneResult
{
public GameObject Body { get; init; }
public GameObject Nameplate { get; init; }
public string Source { get; init; }
public string LocalHeadCompletion { get; init; }
}
private readonly struct RendererMaterialCopyResult
{
public readonly int SourceRenderers;
public readonly int TargetRenderers;
public readonly int RenderersCopied;
public readonly int MaterialsCopied;
public RendererMaterialCopyResult(int sourceRenderers, int targetRenderers, int renderersCopied, int materialsCopied)
{
SourceRenderers = sourceRenderers;
TargetRenderers = targetRenderers;
RenderersCopied = renderersCopied;
MaterialsCopied = materialsCopied;
}
public override string ToString()
{
return "sourceRenderers=" + SourceRenderers.ToString(CultureInfo.InvariantCulture) + " targetRenderers=" + TargetRenderers.ToString(CultureInfo.InvariantCulture) + " renderersCopied=" + RenderersCopied.ToString(CultureInfo.InvariantCulture) + " materialsCopied=" + MaterialsCopied.ToString(CultureInfo.InvariantCulture);
}
}
private const string HarmonyId = "kp0hyc.deathbodyoutline";
private const float DuplicateGuardSeconds = 2f;
private const int VisualModeDisabled = 0;
private const int VisualModeFinalDeathOnly = 1;
private const int VisualModeEveryDeath = 2;
private const string DeadNameSuffix = " ☠";
private const float NameplateHeightAboveBodyRoot = 1.08f;
private const float NameplateHeightAboveHead = 0.42f;
private const float MaxNameplateBoundsHorizontalOffset = 1.35f;
private static readonly Color DeadNameColor = new Color(1f, 0.18f, 0.14f, 1f);
private static readonly Dictionary<string, float> s_lastOutlineTimeByKey = new Dictionary<string, float>(StringComparer.Ordinal);
private static readonly List<GameObject> s_outlines = new List<GameObject>();
private static MelonPreferences_Entry<bool> s_verboseLogs;
private static MelonPreferences_Entry<int> s_visualMode;
private static Harmony s_harmony;
private static GameObject s_root;
private static void CreateBodyOutline(string label, object player, GameObject sourceBody, string sourceContext)
{
BodyCloneResult bodyCloneResult = CreateFrozenBodyClone(label, player, sourceBody);
if (!((Object)(object)bodyCloneResult.Body == (Object)null))
{
if ((Object)(object)bodyCloneResult.Nameplate != (Object)null)
{
s_outlines.Add(bodyCloneResult.Nameplate);
}
s_outlines.Add(bodyCloneResult.Body);
LogVerbose("body clone spawned label=" + label + " sourceContext=" + sourceContext + " source=" + bodyCloneResult.Source + " body=" + DescribeGameObject(bodyCloneResult.Body) + " nameplate=" + DescribeGameObject(bodyCloneResult.Nameplate));
}
}
private static BodyCloneResult CreateFrozenBodyClone(string label, object player, GameObject sourceBody)
{
//IL_000d: 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)
GameObject root = GetRoot();
GameObject val = Object.Instantiate<GameObject>(sourceBody, sourceBody.transform.position, sourceBody.transform.rotation);
((Object)val).name = "DeathBodyOutline_Body_" + SanitizeName(label) + "_" + s_outlines.Count.ToString(CultureInfo.InvariantCulture);
val.transform.SetParent(root.transform, true);
val.SetActive(true);
int num = FreezeBodyClone(val);
string text = TryCompleteLocalHeadlessBody(label, player, sourceBody, val);
GameObject val2 = TryCloneNameplate(player, label, val);
LogVerbose("body clone created label=" + label + " stripped=" + num.ToString(CultureInfo.InvariantCulture) + " localHeadCompletion=" + text + " source=" + DescribeGameObject(sourceBody) + " clone=" + DescribeGameObject(val) + " nameplate=" + DescribeGameObject(val2));
return new BodyCloneResult
{
Body = val,
Nameplate = val2,
Source = DescribeGameObject(sourceBody),
LocalHeadCompletion = text
};
}
private static void SetLayerRecursive(GameObject gameObject, int layer)
{
if ((Object)(object)gameObject == (Object)null)
{
return;
}
foreach (Transform componentsInChild in gameObject.GetComponentsInChildren<Transform>(true))
{
if ((Object)(object)componentsInChild != (Object)null)
{
((Component)componentsInChild).gameObject.layer = layer;
}
}
}
private static int FreezeBodyClone(GameObject clone)
{
int num = 0;
foreach (Animator componentsInChild in clone.GetComponentsInChildren<Animator>(true))
{
if (!((Object)(object)componentsInChild == (Object)null))
{
componentsInChild.keepAnimatorStateOnDisable = true;
componentsInChild.speed = 0f;
((Behaviour)componentsInChild).enabled = false;
num++;
}
}
foreach (Renderer componentsInChild2 in clone.GetComponentsInChildren<Renderer>(true))
{
if ((Object)(object)componentsInChild2 != (Object)null)
{
componentsInChild2.enabled = true;
}
}
foreach (Collider componentsInChild3 in clone.GetComponentsInChildren<Collider>(true))
{
if (!((Object)(object)componentsInChild3 == (Object)null))
{
componentsInChild3.enabled = false;
num++;
}
}
foreach (Rigidbody componentsInChild4 in clone.GetComponentsInChildren<Rigidbody>(true))
{
if (!((Object)(object)componentsInChild4 == (Object)null))
{
componentsInChild4.isKinematic = true;
componentsInChild4.detectCollisions = false;
num++;
}
}
foreach (Light componentsInChild5 in clone.GetComponentsInChildren<Light>(true))
{
if (!((Object)(object)componentsInChild5 == (Object)null))
{
((Behaviour)componentsInChild5).enabled = false;
num++;
}
}
foreach (Behaviour componentsInChild6 in clone.GetComponentsInChildren<Behaviour>(true))
{
if ((Object)(object)componentsInChild6 == (Object)null || componentsInChild6 is Animator || componentsInChild6 is Light)
{
continue;
}
string text = ((object)componentsInChild6).GetType().FullName ?? ((object)componentsInChild6).GetType().Name;
if (text.IndexOf("Renderer", StringComparison.OrdinalIgnoreCase) < 0)
{
try
{
componentsInChild6.enabled = false;
num++;
}
catch (Exception ex)
{
LogVerbose("body behaviour disable failed type=" + text + " error=" + ex.GetType().Name);
}
}
}
return num;
}
private static GameObject ResolveCurrentVisualBodyObject(object player, string context)
{
object value = GetFieldOrProp(player, "CurrentBody") ?? GetFieldOrProp(player, "_CurrentBody_k__BackingField");
bool? boolValue = GetBoolValue(GetFieldOrProp(player, "IsCurrentBodyHeadless") ?? GetFieldOrProp(player, "_IsCurrentBodyHeadless_k__BackingField"));
object obj = GetFieldOrProp(player, "AnimHandler") ?? GetFieldOrProp(player, "animHandler");
object fieldOrProp = GetFieldOrProp(obj, "VisualObj");
GameObject val = (GameObject)(((fieldOrProp is GameObject) ? fieldOrProp : null) ?? ((object)/*isinst with value type is only supported in some contexts*/));
object value2 = GetFieldOrProp(obj, "CurrentState") ?? GetFieldOrProp(obj, "_currentState");
bool flag = HasRenderer(val);
LogVerbose("body source context=" + context + " source=AnimationHandler.VisualObj currentBody=" + FormatObjectValue(value) + " isCurrentHeadless=" + FormatObjectValue(boolValue) + " animState=" + FormatObjectValue(value2) + " hasRenderer=" + flag.ToString(CultureInfo.InvariantCulture) + " visual=" + DescribeGameObject(val));
if (!flag)
{
return null;
}
return val;
}
private static bool LooksHeadless(GameObject gameObject)
{
if ((Object)(object)gameObject == (Object)null)
{
return false;
}
Transform val = gameObject.transform;
while ((Object)(object)val != (Object)null)
{
if (((Object)val).name.IndexOf("Headless", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
val = val.parent;
}
return false;
}
private static Vector3 ResolveHeadCenter(GameObject gameObject)
{
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00d2: 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_009e: 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_00ad: Unknown result type (might be due to invalid IL or missing references)
//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)gameObject == (Object)null)
{
return Vector3.zero;
}
foreach (Transform componentsInChild in gameObject.GetComponentsInChildren<Transform>(true))
{
if (!((Object)(object)componentsInChild == (Object)null))
{
string text = ((Object)componentsInChild).name.ToLowerInvariant();
if (!text.Contains("headless") && (text == "head" || text.EndsWith(":head", StringComparison.Ordinal) || text.EndsWith("_head", StringComparison.Ordinal)))
{
return componentsInChild.position;
}
}
}
if (!TryComputeRendererBounds(gameObject, out var bounds))
{
return gameObject.transform.position + Vector3.up * 1.6f;
}
return new Vector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).max.y, ((Bounds)(ref bounds)).center.z);
}
public override void OnInitializeMelon()
{
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Expected O, but got Unknown
MelonPreferences_Category obj = MelonPreferences.CreateCategory("DeathBodyOutline");
s_visualMode = obj.CreateEntry<int>("VisualMode", 1, "Visual Mode", (string)null, false, false, (ValueValidator)null, (string)null);
s_verboseLogs = obj.CreateEntry<bool>("VerboseLogs", false, "Verbose Logs", (string)null, false, false, (ValueValidator)null, (string)null);
s_harmony = new Harmony("kp0hyc.deathbodyoutline");
PatchFinalDeathRpc();
((MelonBase)this).LoggerInstance.Msg("DeathBodyOutline initialized.");
}
public override void OnSceneWasLoaded(int buildIndex, string sceneName)
{
ClearOutlines("scene loaded " + sceneName);
}
private void PatchFinalDeathRpc()
{
//IL_0098: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Expected O, but got Unknown
Type type = FindType("Death") ?? FindType("ValkoGames.Labyrinthine.Players.Death");
if (type == null)
{
((MelonBase)this).LoggerInstance.Warning("Death type was not found; outlines will not spawn.");
return;
}
MethodInfo methodInfo = AccessTools.Method(type, "UserCode_RpcOnBleedOutTimeout__SByte__Boolean", new Type[2]
{
typeof(sbyte),
typeof(bool)
}, (Type[])null);
if (methodInfo == null)
{
((MelonBase)this).LoggerInstance.Warning("Final-death RPC method was not found; outlines will not spawn.");
return;
}
MethodInfo methodInfo2 = AccessTools.Method(typeof(DeathBodyOutline), "OnFinalDeathRpcPrefix", (Type[])null, (Type[])null);
s_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
LogVerbose("patched " + type.FullName + "." + methodInfo.Name);
}
private static void OnFinalDeathRpcPrefix(object __instance, sbyte livesLeft, bool respawn)
{
try
{
SpawnOutlineForDeath(__instance, livesLeft, respawn);
}
catch (Exception ex)
{
MelonLogger.Warning("[Death Body Outline] final death outline failed: " + ex.GetType().Name + " " + ex.Message);
}
}
private static void SpawnOutlineForDeath(object deathInstance, sbyte livesLeft, bool respawn)
{
int visualMode = GetVisualMode();
object fieldOrProp = GetFieldOrProp(deathInstance, "playerNetworkSync");
if (fieldOrProp == null)
{
LogVerbose("final death ignored: playerNetworkSync missing");
return;
}
string playerKey = GetPlayerKey(fieldOrProp);
switch (visualMode)
{
case 0:
LogVerbose("final death ignored: visual mode disabled key=" + playerKey + " livesLeft=" + livesLeft + " respawn=" + respawn);
return;
case 1:
if (!IsOutOfLivesDeath(livesLeft, respawn))
{
LogVerbose("final death ignored by visual mode key=" + playerKey + " mode=" + visualMode.ToString(CultureInfo.InvariantCulture) + " livesLeft=" + livesLeft + " respawn=" + respawn);
return;
}
break;
}
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (s_lastOutlineTimeByKey.TryGetValue(playerKey, out var value) && realtimeSinceStartup - value < 2f)
{
LogVerbose("duplicate final death ignored key=" + playerKey + " age=" + (realtimeSinceStartup - value).ToString("0.00", CultureInfo.InvariantCulture));
return;
}
s_lastOutlineTimeByKey[playerKey] = realtimeSinceStartup;
string playerLabel = GetPlayerLabel(fieldOrProp);
LogVerbose("final death source decision start key=" + playerKey + " label=" + playerLabel + " mode=" + visualMode.ToString(CultureInfo.InvariantCulture) + " livesLeft=" + livesLeft + " respawn=" + respawn);
GameObject val = ResolveCurrentVisualBodyObject(fieldOrProp, "final-time");
if ((Object)(object)val != (Object)null)
{
LogVerbose("final death source selected key=" + playerKey + " label=" + playerLabel + " sourceMode=final-time body=" + DescribeGameObject(val));
CreateBodyOutline(playerLabel, fieldOrProp, val, "final-time");
LogVerbose("outline spawned key=" + playerKey + " label=" + playerLabel + " livesLeft=" + livesLeft + " respawn=" + respawn + " source=" + DescribeGameObject(val));
}
else
{
LogVerbose("final death source selected key=" + playerKey + " label=" + playerLabel + " sourceMode=none player=" + DescribeObject(fieldOrProp));
}
}
private static bool IsOutOfLivesDeath(sbyte livesLeft, bool respawn)
{
if (livesLeft > 0)
{
return !respawn;
}
return true;
}
private static int GetVisualMode()
{
int num = ((s_visualMode == null) ? 1 : s_visualMode.Value);
if (num < 0)
{
return 0;
}
if (num > 2)
{
return 2;
}
return num;
}
private static string TryCompleteLocalHeadlessBody(string label, object player, GameObject sourceBody, GameObject clone)
{
if (!LooksHeadless(sourceBody))
{
return "source-headed";
}
if (!IsLocalPlayer(player))
{
LogVerbose("local head completion skipped: non-local selected source is headless label=" + label + " player=" + DescribeObject(player) + " source=" + DescribeGameObject(sourceBody) + " clone=" + DescribeGameObject(clone));
return "source-headless local=False";
}
GameObject val = ResolveLocalHeadSource(player, sourceBody);
if ((Object)(object)val == (Object)null)
{
LogVerbose("local head completion skipped: explicit head source missing label=" + label + " player=" + DescribeObject(player) + " source=" + DescribeGameObject(sourceBody) + " clone=" + DescribeGameObject(clone));
return "source-headless local=True headSource=none";
}
int num = CompleteHeadlessBodyRendererFromSource(val, clone);
int num2 = CopyLocalHairChildrenFromSource(val, clone);
return "source-headless local=True headSource=" + ((Object)val).name + " completedBody=" + num.ToString(CultureInfo.InvariantCulture) + " copiedHair=" + num2.ToString(CultureInfo.InvariantCulture) + " label=" + label;
}
private static bool IsLocalPlayer(object player)
{
if (TryConvertBool(GetFieldOrProp(player, "isOwned") ?? GetFieldOrProp(player, "IsOwned") ?? GetFieldOrProp(player, "_isOwned_k__BackingField") ?? GetFieldOrProp(player, "_IsOwned_k__BackingField"), out var result))
{
return result;
}
Type type = FindType("PlayerNetworkSync");
object obj = GetStaticFieldOrProp(type, "instance") ?? GetStaticFieldOrProp(type, "Instance");
object fieldOrProp = GetFieldOrProp(player, "gameObject");
GameObject val = (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null);
object fieldOrProp2 = GetFieldOrProp(obj, "gameObject");
GameObject val2 = (GameObject)((fieldOrProp2 is GameObject) ? fieldOrProp2 : null);
bool result2 = obj == player || ((Object)(object)val != (Object)null && val == val2);
LogVerbose("local player ownership field missing; instance match=" + result2.ToString(CultureInfo.InvariantCulture) + " player=" + DescribeObject(player) + " instance=" + DescribeObject(obj));
return result2;
}
private static GameObject ResolveLocalHeadSource(object player, GameObject sourceBody)
{
object value = GetFieldOrProp(player, "CurrentBody") ?? GetFieldOrProp(player, "_CurrentBody_k__BackingField");
if (!TryConvertLong(value, out var result) || result < 0 || result > int.MaxValue)
{
LogVerbose("local head source missing: currentBody invalid value=" + FormatObjectValue(value));
return null;
}
object indexedObject = GetIndexedObject(GetFieldOrProp(player, "playerBodies"), (int)result);
object fieldOrProp = GetFieldOrProp(indexedObject, "remote");
GameObject val = (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null);
LogVerbose("local head source lookup currentBody=" + result.ToString(CultureInfo.InvariantCulture) + " container=" + DescribeObject(indexedObject) + " remote=" + DescribeGameObject(val));
if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)sourceBody || LooksHeadless(val) || !HasRenderer(val))
{
return null;
}
return val;
}
private static object GetIndexedObject(object values, int index)
{
if (values == null || index < 0)
{
return null;
}
if (values is Array array && index < array.Length)
{
return array.GetValue(index);
}
if (!TryConvertLong(GetFieldOrProp(values, "Count"), out var result))
{
TryConvertLong(GetFieldOrProp(values, "Length"), out result);
}
if (index >= result)
{
return null;
}
MethodInfo method = values.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null)
{
return null;
}
try
{
return method.Invoke(values, new object[1] { index });
}
catch
{
return null;
}
}
private static int CompleteHeadlessBodyRendererFromSource(GameObject headSource, GameObject clone)
{
//IL_0128: 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_0156: Unknown result type (might be due to invalid IL or missing references)
int num = 0;
foreach (Renderer item in ((IEnumerable<Renderer>)headSource.GetComponentsInChildren<Renderer>(true)).OrderBy((Renderer renderer) => GetDepth(((Component)renderer).transform)))
{
if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).transform == (Object)null || (Object)(object)((Component)item).transform == (Object)(object)headSource.transform || IsHairTransform(headSource.transform, ((Component)item).transform))
{
continue;
}
string relativePath = GetRelativePath(headSource.transform, ((Component)item).transform);
Transform val = FindChildByPath(clone.transform, relativePath);
if ((Object)(object)val == (Object)null || !HasRenderer(((Component)val).gameObject))
{
continue;
}
Transform val2 = val.parent ?? clone.transform;
GameObject val3 = Object.Instantiate<GameObject>(((Component)((Component)item).transform).gameObject);
((Object)val3).name = ((Object)((Component)((Component)item).transform).gameObject).name + "_DeathCompletedBody";
val3.transform.SetParent(val2, false);
val3.transform.localPosition = ((Component)item).transform.localPosition;
val3.transform.localRotation = ((Component)item).transform.localRotation;
val3.transform.localScale = ((Component)item).transform.localScale;
SetLayerRecursive(val3, clone.layer);
string text = RemapSkinnedMeshBones(val3, headSource.transform, clone.transform);
RendererMaterialCopyResult rendererMaterialCopyResult = CopyRendererMaterialsFromClone(((Component)val).gameObject, val3);
if (rendererMaterialCopyResult.MaterialsCopied <= 0)
{
Object.Destroy((Object)(object)val3);
LogVerbose("local body completion skipped path=" + relativePath + " reason=no-live-materials source=" + DescribeGameObject(headSource) + " cloneTarget=" + DescribeGameObject(((Component)val).gameObject));
continue;
}
foreach (Renderer componentsInChild in val3.GetComponentsInChildren<Renderer>(true))
{
if ((Object)(object)componentsInChild != (Object)null)
{
componentsInChild.enabled = true;
}
}
foreach (Renderer componentsInChild2 in ((Component)val).gameObject.GetComponentsInChildren<Renderer>(true))
{
if ((Object)(object)componentsInChild2 != (Object)null)
{
componentsInChild2.enabled = false;
}
}
num++;
LogVerbose("local body completion copied path=" + relativePath + " disabledClone=" + GetRelativePath(clone.transform, val) + " parent=" + GetRelativePath(clone.transform, val2) + " materials=" + rendererMaterialCopyResult.ToString() + " " + text);
}
LogVerbose("local body completion summary source=" + DescribeGameObject(headSource) + " clone=" + DescribeGameObject(clone) + " completed=" + num.ToString(CultureInfo.InvariantCulture));
return num;
}
private static int CopyLocalHairChildrenFromSource(GameObject headSource, GameObject clone)
{
//IL_017d: Unknown result type (might be due to invalid IL or missing references)
//IL_018f: 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)
List<Transform> list = new List<Transform>();
foreach (Renderer item in ((IEnumerable<Renderer>)headSource.GetComponentsInChildren<Renderer>(true)).OrderBy((Renderer renderer) => GetDepth(((Component)renderer).transform)))
{
Transform transform = (((Object)(object)item == (Object)null) ? null : ((Component)item).transform);
if (!((Object)(object)transform == (Object)null) && !((Object)(object)transform == (Object)(object)headSource.transform) && IsHairTransform(headSource.transform, transform) && !list.Any((Transform existing) => IsChildOf(transform, existing)))
{
list.Add(transform);
}
}
int num = 0;
foreach (Transform item2 in list)
{
Transform val = FindMatchingCloneParent(headSource.transform, clone.transform, item2.parent) ?? FindHeadAttachParent(clone.transform);
if ((Object)(object)val == (Object)null)
{
LogVerbose("local hair copy skipped: target parent missing source=" + GetRelativePath(headSource.transform, item2));
continue;
}
GameObject obj = Object.Instantiate<GameObject>(((Component)item2).gameObject);
((Object)obj).name = ((Object)((Component)item2).gameObject).name + "_DeathHair";
obj.transform.SetParent(val, false);
obj.transform.localPosition = item2.localPosition;
obj.transform.localRotation = item2.localRotation;
obj.transform.localScale = item2.localScale;
SetLayerRecursive(obj, clone.layer);
string text = RemapSkinnedMeshBones(obj, headSource.transform, clone.transform);
foreach (Renderer componentsInChild in obj.GetComponentsInChildren<Renderer>(true))
{
if ((Object)(object)componentsInChild != (Object)null)
{
componentsInChild.enabled = true;
}
}
num++;
LogVerbose("local hair copied path=" + GetRelativePath(headSource.transform, item2) + " parent=" + GetRelativePath(clone.transform, val) + " " + text);
}
LogVerbose("local hair copy summary source=" + DescribeGameObject(headSource) + " clone=" + DescribeGameObject(clone) + " selected=" + list.Count.ToString(CultureInfo.InvariantCulture) + " copied=" + num.ToString(CultureInfo.InvariantCulture));
return num;
}
private static RendererMaterialCopyResult CopyRendererMaterialsFromClone(GameObject source, GameObject target)
{
Il2CppArrayBase<Renderer> componentsInChildren = source.GetComponentsInChildren<Renderer>(true);
Il2CppArrayBase<Renderer> componentsInChildren2 = target.GetComponentsInChildren<Renderer>(true);
int num = 0;
int num2 = 0;
int num3 = Math.Min(componentsInChildren.Length, componentsInChildren2.Length);
for (int i = 0; i < num3; i++)
{
Renderer val = componentsInChildren[i];
Renderer val2 = componentsInChildren2[i];
if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null))
{
Il2CppReferenceArray<Material> sharedMaterials = val.sharedMaterials;
if (sharedMaterials != null && ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length != 0)
{
val2.sharedMaterials = sharedMaterials;
num++;
num2 += ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length;
}
}
}
return new RendererMaterialCopyResult(componentsInChildren.Length, componentsInChildren2.Length, num, num2);
}
private static bool IsHairTransform(Transform root, Transform transform)
{
if ((Object)(object)transform == (Object)null || IsUnderLimbTransform(transform))
{
return false;
}
if (!GetRelativePath(root, transform).ToLowerInvariant().Contains("hair"))
{
return false;
}
return true;
}
private static bool IsUnderLimbTransform(Transform transform)
{
Transform val = transform;
while ((Object)(object)val != (Object)null)
{
string text = ((Object)val).name.ToLowerInvariant();
if (text.Contains("hand") || text.Contains("forearm") || text.Contains("leftarm") || text.Contains("rightarm"))
{
return true;
}
val = val.parent;
}
return false;
}
private static GameObject TryCloneNameplate(object player, string label, GameObject bodyClone)
{
//IL_0044: 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)
object fieldOrProp = GetFieldOrProp(player, "nameplateCanvas");
object canvas = ((fieldOrProp is Canvas) ? fieldOrProp : null);
object fieldOrProp2 = GetFieldOrProp(player, "namePlate");
Component namePlate = (Component)((fieldOrProp2 is Component) ? fieldOrProp2 : null);
GameObject val = ResolveNameplateSource((Canvas)canvas, namePlate);
if ((Object)(object)val == (Object)null)
{
LogVerbose("nameplate clone skipped: source missing");
return null;
}
GameObject val2 = Object.Instantiate<GameObject>(val, val.transform.position, val.transform.rotation);
((Object)val2).name = "DeathBodyOutline_Nameplate_" + SanitizeName(label);
val2.transform.SetParent(bodyClone.transform, true);
val2.SetActive(true);
int num = PrepareNameplateClone(val2, label);
RepositionNameplateClone(val2, bodyClone);
LogVerbose("nameplate cloned changed=" + num.ToString(CultureInfo.InvariantCulture) + " source=" + DescribeGameObject(val) + " clone=" + DescribeGameObject(val2));
return val2;
}
private static GameObject ResolveNameplateSource(Canvas canvas, Component namePlate)
{
if ((Object)(object)namePlate != (Object)null)
{
Canvas componentInParent = namePlate.GetComponentInParent<Canvas>(true);
if ((Object)(object)componentInParent != (Object)null && HasTmpText(((Component)componentInParent).gameObject))
{
return ((Component)componentInParent).gameObject;
}
if (HasTmpText(namePlate.gameObject))
{
return namePlate.gameObject;
}
}
if ((Object)(object)canvas != (Object)null && HasTmpText(((Component)canvas).gameObject))
{
return ((Component)canvas).gameObject;
}
if (!((Object)(object)namePlate != (Object)null))
{
if (!((Object)(object)canvas != (Object)null))
{
return null;
}
return ((Component)canvas).gameObject;
}
return namePlate.gameObject;
}
private static void RepositionNameplateClone(GameObject nameplate, GameObject bodyClone)
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: 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)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: 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_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_0127: 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_006b: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_0078: 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_008e: 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_00c3: 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)
//IL_00fe: 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_0110: Unknown result type (might be due to invalid IL or missing references)
//IL_0150: Unknown result type (might be due to invalid IL or missing references)
//IL_015f: Unknown result type (might be due to invalid IL or missing references)
//IL_0170: Unknown result type (might be due to invalid IL or missing references)
//IL_0175: Unknown result type (might be due to invalid IL or missing references)
//IL_017d: Unknown result type (might be due to invalid IL or missing references)
//IL_0195: Unknown result type (might be due to invalid IL or missing references)
//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)nameplate == (Object)null || (Object)(object)bodyClone == (Object)null)
{
return;
}
Vector3 position = bodyClone.transform.position;
Vector3 val = ResolveHeadCenter(bodyClone);
bool flag = IsSaneNameplateHeadAnchor(bodyClone, val);
bool flag2 = false;
string text = "{none}";
Bounds bounds;
if (flag)
{
((Vector3)(ref position))..ctor(val.x, position.y, val.z);
}
else if (TryComputeRendererBounds(bodyClone, out bounds))
{
float num = Vector2.Distance(new Vector2(position.x, position.z), new Vector2(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).center.z));
text = "center=" + FormatVector(((Bounds)(ref bounds)).center) + " size=" + FormatVector(((Bounds)(ref bounds)).size) + " horizontalOffset=" + num.ToString("0.00", CultureInfo.InvariantCulture);
if (num <= 1.35f)
{
((Vector3)(ref position))..ctor(((Bounds)(ref bounds)).center.x, position.y, ((Bounds)(ref bounds)).center.z);
flag2 = true;
}
}
float num2 = bodyClone.transform.position.y + 1.08f;
float val2 = (flag ? (val.y + 0.42f) : num2);
Vector3 val3 = default(Vector3);
((Vector3)(ref val3))..ctor(position.x, Math.Max(num2, val2), position.z);
Vector3 position2 = nameplate.transform.position;
nameplate.transform.position = val3;
LogVerbose("nameplate reposition before=" + FormatVector(position2) + " after=" + FormatVector(val3) + " root=" + FormatVector(bodyClone.transform.position) + " head=" + FormatVector(val) + " usedHeadCenter=" + flag + " usedBoundsCenter=" + flag2 + " heightAboveRoot=" + 1.08f.ToString("0.00", CultureInfo.InvariantCulture) + " heightAboveHead=" + 0.42f.ToString("0.00", CultureInfo.InvariantCulture) + " bounds=" + text);
}
private static bool IsSaneNameplateHeadAnchor(GameObject bodyClone, Vector3 headCenter)
{
//IL_0009: 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_001e: 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_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)bodyClone == (Object)null || headCenter == Vector3.zero)
{
return false;
}
return Vector2.Distance(new Vector2(bodyClone.transform.position.x, bodyClone.transform.position.z), new Vector2(headCenter.x, headCenter.z)) <= 1.35f;
}
private static int PrepareNameplateClone(GameObject clone, string label)
{
int num = 0;
foreach (Transform componentsInChild in clone.GetComponentsInChildren<Transform>(true))
{
if ((Object)(object)componentsInChild != (Object)null)
{
((Component)componentsInChild).gameObject.SetActive(true);
}
}
foreach (Canvas componentsInChild2 in clone.GetComponentsInChildren<Canvas>(true))
{
if (!((Object)(object)componentsInChild2 == (Object)null))
{
((Behaviour)componentsInChild2).enabled = true;
num++;
}
}
foreach (CanvasGroup componentsInChild3 in clone.GetComponentsInChildren<CanvasGroup>(true))
{
if (!((Object)(object)componentsInChild3 == (Object)null))
{
componentsInChild3.alpha = 1f;
componentsInChild3.interactable = false;
componentsInChild3.blocksRaycasts = false;
num++;
}
}
foreach (TMP_Text componentsInChild4 in clone.GetComponentsInChildren<TMP_Text>(true))
{
if (!((Object)(object)componentsInChild4 == (Object)null))
{
try
{
ApplyDeadNameText(componentsInChild4, label);
((Component)componentsInChild4).gameObject.SetActive(true);
componentsInChild4.ForceMeshUpdate(false, false);
num++;
}
catch (Exception ex)
{
LogVerbose("nameplate TMP update failed type=" + ((object)componentsInChild4).GetType().FullName + " error=" + ex.GetType().Name);
}
}
}
int num2 = 0;
foreach (Graphic componentsInChild5 in clone.GetComponentsInChildren<Graphic>(true))
{
if (!((Object)(object)componentsInChild5 == (Object)null) && !((Object)(object)((Component)componentsInChild5).GetComponent<TMP_Text>() != (Object)null))
{
((Behaviour)componentsInChild5).enabled = false;
((Component)componentsInChild5).gameObject.SetActive(false);
num2++;
}
}
LogVerbose("nameplate prepare label=" + label + " canvases=" + clone.GetComponentsInChildren<Canvas>(true).Length.ToString(CultureInfo.InvariantCulture) + " canvasGroups=" + clone.GetComponentsInChildren<CanvasGroup>(true).Length.ToString(CultureInfo.InvariantCulture) + " tmpTexts=" + clone.GetComponentsInChildren<TMP_Text>(true).Length.ToString(CultureInfo.InvariantCulture) + " disabledGraphics=" + num2.ToString(CultureInfo.InvariantCulture) + " runtimeBehavioursKept=True");
return num;
}
private static void UpdateNameplateText(GameObject nameplate, string label)
{
if ((Object)(object)nameplate == (Object)null)
{
return;
}
foreach (TMP_Text componentsInChild in nameplate.GetComponentsInChildren<TMP_Text>(true))
{
if (!((Object)(object)componentsInChild == (Object)null))
{
try
{
ApplyDeadNameText(componentsInChild, label);
((Component)componentsInChild).gameObject.SetActive(true);
componentsInChild.ForceMeshUpdate(false, false);
}
catch (Exception ex)
{
LogVerbose("nameplate text reveal update failed type=" + ((object)componentsInChild).GetType().FullName + " error=" + ex.GetType().Name);
}
}
}
}
private static bool HasTmpText(GameObject gameObject)
{
if ((Object)(object)gameObject != (Object)null)
{
return ((IEnumerable<TMP_Text>)gameObject.GetComponentsInChildren<TMP_Text>(true)).Any((TMP_Text text) => (Object)(object)text != (Object)null);
}
return false;
}
private static void ApplyDeadNameText(TMP_Text text, string label)
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
text.text = label + " ☠";
((Graphic)text).color = DeadNameColor;
}
private static void EnableVisibleRenderers(GameObject gameObject, bool enabled)
{
if ((Object)(object)gameObject == (Object)null)
{
return;
}
foreach (Renderer componentsInChild in gameObject.GetComponentsInChildren<Renderer>(true))
{
if ((Object)(object)componentsInChild != (Object)null)
{
componentsInChild.enabled = enabled;
}
}
}
private static bool HasRenderer(GameObject gameObject)
{
if ((Object)(object)gameObject != (Object)null)
{
return ((IEnumerable<Renderer>)gameObject.GetComponentsInChildren<Renderer>(true)).Any((Renderer renderer) => (Object)(object)renderer != (Object)null);
}
return false;
}
private static bool TryComputeRendererBounds(GameObject gameObject, out Bounds bounds)
{
return TryComputeRendererBounds(gameObject, out bounds, includeDeathAppliedAssets: false);
}
private static bool TryComputeRendererBounds(GameObject gameObject, out Bounds bounds, bool includeDeathAppliedAssets)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
bounds = default(Bounds);
if ((Object)(object)gameObject == (Object)null)
{
return false;
}
bool flag = false;
foreach (Renderer componentsInChild in gameObject.GetComponentsInChildren<Renderer>(true))
{
if (!((Object)(object)componentsInChild == (Object)null) && (includeDeathAppliedAssets || !IsDeathAppliedAssetRenderer(componentsInChild)))
{
if (!flag)
{
bounds = componentsInChild.bounds;
flag = true;
}
else
{
((Bounds)(ref bounds)).Encapsulate(componentsInChild.bounds);
}
}
}
return flag;
}
private static bool IsDeathAppliedAssetRenderer(Renderer renderer)
{
Transform val = (((Object)(object)renderer == (Object)null) ? null : ((Component)renderer).transform);
while ((Object)(object)val != (Object)null)
{
if (!string.IsNullOrWhiteSpace(((Object)val).name) && ((Object)val).name.IndexOf("_DeathAppliedAsset", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
val = val.parent;
}
return false;
}
private static GameObject GetRoot()
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Expected O, but got Unknown
if ((Object)(object)s_root != (Object)null)
{
return s_root;
}
s_root = new GameObject("DeathBodyOutline_Root");
return s_root;
}
private static void ClearOutlines(string reason)
{
foreach (GameObject s_outline in s_outlines)
{
if ((Object)(object)s_outline != (Object)null)
{
Object.Destroy((Object)(object)s_outline);
}
}
s_outlines.Clear();
s_lastOutlineTimeByKey.Clear();
if ((Object)(object)s_root != (Object)null)
{
Object.Destroy((Object)(object)s_root);
s_root = null;
}
LogVerbose("outlines cleared: " + reason);
}
private static Type FindType(string fullName)
{
return Il2CppReflection.FindType(fullName);
}
private static string GetPlayerKey(object player)
{
if (TryConvertLong(GetFieldOrProp(player, "netId"), out var result) && result > 0)
{
return "net:" + result.ToString(CultureInfo.InvariantCulture);
}
object clientData = GetClientData(player);
if (TryConvertLong(GetFieldOrProp(clientData, "SteamID") ?? GetFieldOrProp(clientData, "SteamId") ?? GetFieldOrProp(clientData, "steamID") ?? GetFieldOrProp(clientData, "ID"), out var result2) && result2 > 0)
{
return "steam:" + result2.ToString(CultureInfo.InvariantCulture);
}
return "obj:" + RuntimeHelpersHash(player).ToString(CultureInfo.InvariantCulture);
}
private static string GetPlayerLabel(object player)
{
object clientData = GetClientData(player);
string text = GetStringValue(GetFieldOrProp(clientData, "Name")) ?? GetStringValue(GetFieldOrProp(clientData, "_Name_k__BackingField")) ?? GetStringValue(GetFieldOrProp(GetFieldOrProp(player, "namePlate"), "text")) ?? GetStringValue(GetFieldOrProp(player, "name")) ?? GetStringValue(GetFieldOrProp(GetFieldOrProp(player, "gameObject"), "name"));
if (!string.IsNullOrWhiteSpace(text))
{
return text.Trim();
}
return "player";
}
private static object GetClientData(object player)
{
return GetFieldOrProp(player, "ClientData") ?? GetFieldOrProp(player, "NetworkclientData") ?? GetFieldOrProp(player, "clientData") ?? GetFieldOrProp(player, "_ClientData_k__BackingField") ?? GetFieldOrProp(player, "_NetworkclientData_k__BackingField");
}
private static object GetFieldOrProp(object obj, string name)
{
return Il2CppReflection.GetFieldOrProp(obj, name);
}
private static object GetStaticFieldOrProp(Type type, string name)
{
return Il2CppReflection.GetStaticFieldOrProp(type, name);
}
private static object FindFirstObjectOfType(Type type)
{
return Il2CppReflection.FindFirstObjectOfType(type);
}
private static string GetStringValue(object value)
{
return ValueConversion.GetStringValue(value);
}
private static bool? GetBoolValue(object value)
{
return ValueConversion.GetBoolValue(value);
}
private static bool TryConvertBool(object value, out bool result)
{
return ValueConversion.TryConvertBool(value, out result);
}
private static string FormatObjectValue(object value)
{
if (value == null)
{
return "{none}";
}
if (value is bool)
{
if (!(bool)value)
{
return "False";
}
return "True";
}
return value.ToString();
}
private static bool TryConvertLong(object value, out long result)
{
return ValueConversion.TryConvertLong(value, out result);
}
private static int RuntimeHelpersHash(object obj)
{
if (obj != null)
{
return RuntimeHelpers.GetHashCode(obj);
}
return 0;
}
private static string SanitizeName(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "player";
}
return new string(value.Select((char ch) => (!char.IsLetterOrDigit(ch)) ? '_' : ch).ToArray()).Trim('_');
}
private static string FormatVector(Vector3 vector)
{
return vector.x.ToString("0.00", CultureInfo.InvariantCulture) + "," + vector.y.ToString("0.00", CultureInfo.InvariantCulture) + "," + vector.z.ToString("0.00", CultureInfo.InvariantCulture);
}
private static string DescribeObject(object obj)
{
if (obj == null)
{
return "{none}";
}
GameObject val = (GameObject)((obj is GameObject) ? obj : null);
if (val != null)
{
return DescribeGameObject(val);
}
Component val2 = (Component)((obj is Component) ? obj : null);
if (val2 != null)
{
return ((object)val2).GetType().FullName + " go=" + DescribeGameObject(val2.gameObject);
}
return obj.GetType().FullName + "#" + RuntimeHelpersHash(obj).ToString(CultureInfo.InvariantCulture);
}
private static string DescribeGameObject(GameObject gameObject)
{
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)gameObject == (Object)null)
{
return "{none}";
}
return ((Object)gameObject).name + " activeSelf=" + gameObject.activeSelf + " activeHierarchy=" + gameObject.activeInHierarchy + " pos=" + FormatVector(gameObject.transform.position) + " renderers=" + gameObject.GetComponentsInChildren<Renderer>(true).Length.ToString(CultureInfo.InvariantCulture);
}
private static void LogVerbose(string message)
{
if (s_verboseLogs != null && s_verboseLogs.Value)
{
MelonLogger.Msg("[Death Body Outline] [Debug] " + message);
}
}
private static string RemapSkinnedMeshBones(GameObject copy, Transform donorRoot, Transform cloneRoot)
{
int num = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
int num5 = 0;
int num6 = 0;
foreach (SkinnedMeshRenderer componentsInChild in copy.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
if ((Object)(object)componentsInChild == (Object)null)
{
continue;
}
num++;
Il2CppReferenceArray<Transform> bones = componentsInChild.bones;
if (bones != null && ((Il2CppArrayBase<Transform>)(object)bones).Length > 0)
{
Transform[] array = (Transform[])(object)new Transform[((Il2CppArrayBase<Transform>)(object)bones).Length];
for (int i = 0; i < ((Il2CppArrayBase<Transform>)(object)bones).Length; i++)
{
Transform val = ((Il2CppArrayBase<Transform>)(object)bones)[i];
if ((Object)(object)val == (Object)null)
{
array[i] = copy.transform;
num4++;
continue;
}
if (IsChildOfOrSelf(val, copy.transform))
{
array[i] = val;
num3++;
continue;
}
Transform val2 = MapDonorTransformToClone(donorRoot, cloneRoot, val);
if ((Object)(object)val2 != (Object)null)
{
array[i] = val2;
num2++;
}
else
{
array[i] = copy.transform;
num4++;
}
}
componentsInChild.bones = Il2CppReferenceArray<Transform>.op_Implicit(array);
}
Transform rootBone = componentsInChild.rootBone;
if (!((Object)(object)rootBone == (Object)null) && !IsChildOfOrSelf(rootBone, copy.transform))
{
Transform val3 = MapDonorTransformToClone(donorRoot, cloneRoot, rootBone);
if ((Object)(object)val3 != (Object)null)
{
componentsInChild.rootBone = val3;
num5++;
}
else if (IsChildOfOrSelf(rootBone, donorRoot))
{
componentsInChild.rootBone = cloneRoot;
num6++;
}
}
}
return "skinnedRenderers=" + num.ToString(CultureInfo.InvariantCulture) + " mappedBones=" + num2.ToString(CultureInfo.InvariantCulture) + " keptLocalBones=" + num3.ToString(CultureInfo.InvariantCulture) + " detachedBones=" + num4.ToString(CultureInfo.InvariantCulture) + " mappedRootBones=" + num5.ToString(CultureInfo.InvariantCulture) + " detachedRootBones=" + num6.ToString(CultureInfo.InvariantCulture);
}
private static int GetDepth(Transform transform)
{
int num = 0;
Transform val = transform;
while ((Object)(object)val != (Object)null)
{
num++;
val = val.parent;
}
return num;
}
private static bool IsChildOf(Transform child, Transform possibleParent)
{
Transform val = (((Object)(object)child == (Object)null) ? null : child.parent);
while ((Object)(object)val != (Object)null)
{
if ((Object)(object)val == (Object)(object)possibleParent)
{
return true;
}
val = val.parent;
}
return false;
}
private static bool IsChildOfOrSelf(Transform child, Transform possibleParent)
{
Transform val = child;
while ((Object)(object)val != (Object)null)
{
if ((Object)(object)val == (Object)(object)possibleParent)
{
return true;
}
val = val.parent;
}
return false;
}
private static Transform MapDonorTransformToClone(Transform donorRoot, Transform cloneRoot, Transform donorTransform)
{
if ((Object)(object)donorRoot == (Object)null || (Object)(object)cloneRoot == (Object)null || (Object)(object)donorTransform == (Object)null)
{
return null;
}
if ((Object)(object)donorTransform == (Object)(object)donorRoot)
{
return cloneRoot;
}
if (!IsChildOfOrSelf(donorTransform, donorRoot))
{
return null;
}
Transform val = FindChildByPath(cloneRoot, GetRelativePath(donorRoot, donorTransform));
if ((Object)(object)val != (Object)null)
{
return val;
}
return FindDescendantByName(cloneRoot, ((Object)donorTransform).name);
}
private static Transform FindMatchingCloneParent(Transform donorRoot, Transform cloneRoot, Transform donorParent)
{
if ((Object)(object)donorRoot == (Object)null || (Object)(object)cloneRoot == (Object)null || (Object)(object)donorParent == (Object)null || (Object)(object)donorParent == (Object)(object)donorRoot)
{
return cloneRoot;
}
Transform val = FindChildByPath(cloneRoot, GetRelativePath(donorRoot, donorParent));
if ((Object)(object)val != (Object)null)
{
return val;
}
return FindDescendantByName(cloneRoot, ((Object)donorParent).name);
}
private static Transform FindHeadAttachParent(Transform cloneRoot)
{
if ((Object)(object)cloneRoot == (Object)null)
{
return null;
}
foreach (Transform componentsInChild in ((Component)cloneRoot).GetComponentsInChildren<Transform>(true))
{
if (!((Object)(object)componentsInChild == (Object)null))
{
string text = ((Object)componentsInChild).name.ToLowerInvariant();
if (text == "head" || text.EndsWith(":head", StringComparison.Ordinal) || text.EndsWith("_head", StringComparison.Ordinal))
{
return componentsInChild;
}
}
}
return cloneRoot;
}
private static Transform FindChildByPath(Transform root, string path)
{
if ((Object)(object)root == (Object)null)
{
return null;
}
if (string.IsNullOrWhiteSpace(path))
{
return root;
}
Transform val = root;
string[] array = path.Split('/');
foreach (string text in array)
{
if (!string.IsNullOrWhiteSpace(text))
{
val = FindDirectChildByName(val, text);
if ((Object)(object)val == (Object)null)
{
return null;
}
}
}
return val;
}
private static Transform FindDirectChildByName(Transform root, string name)
{
if ((Object)(object)root == (Object)null)
{
return null;
}
for (int i = 0; i < root.childCount; i++)
{
Transform child = root.GetChild(i);
if ((Object)(object)child != (Object)null && string.Equals(((Object)child).name, name, StringComparison.Ordinal))
{
return child;
}
}
return null;
}
private static Transform FindDescendantByName(Transform root, string name)
{
if ((Object)(object)root == (Object)null)
{
return null;
}
foreach (Transform componentsInChild in ((Component)root).GetComponentsInChildren<Transform>(true))
{
if ((Object)(object)componentsInChild != (Object)null && string.Equals(((Object)componentsInChild).name, name, StringComparison.Ordinal))
{
return componentsInChild;
}
}
return null;
}
private static string GetRelativePath(Transform root, Transform target)
{
if ((Object)(object)root == (Object)null || (Object)(object)target == (Object)null)
{
return "{none}";
}
if ((Object)(object)root == (Object)(object)target)
{
return string.Empty;
}
Stack<string> stack = new Stack<string>();
Transform val = target;
while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
{
stack.Push(((Object)val).name);
val = val.parent;
}
return string.Join("/", stack);
}
}
namespace Kp0hyc.Labyrinthine.Shared;
internal static class Il2CppReflection
{
public static Type FindType(string fullName)
{
if (string.IsNullOrWhiteSpace(fullName))
{
return null;
}
string il2cppName = (fullName.StartsWith("Il2Cpp.", StringComparison.Ordinal) ? fullName : ("Il2Cpp." + fullName));
string il2cppNamespaceName = (fullName.StartsWith("Il2Cpp", StringComparison.Ordinal) ? fullName : ("Il2Cpp" + fullName));
string shortName = (fullName.Contains(".") ? fullName.Substring(fullName.LastIndexOf('.') + 1) : fullName);
Type type = Type.GetType(fullName) ?? Type.GetType(il2cppName) ?? Type.GetType(il2cppNamespaceName);
if (type != null)
{
return type;
}
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
Type type2 = assembly.GetType(fullName, throwOnError: false) ?? assembly.GetType(il2cppName, throwOnError: false) ?? assembly.GetType(il2cppNamespaceName, throwOnError: false);
if (type2 != null)
{
return type2;
}
}
assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly2 in assemblies)
{
Type[] source;
try
{
source = assembly2.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
source = ex.Types.Where((Type t) => t != null).ToArray();
}
catch
{
continue;
}
Type type3 = source.FirstOrDefault((Type t) => t.Name == shortName || t.FullName == fullName || t.FullName == il2cppName || t.FullName == il2cppNamespaceName);
if (type3 != null)
{
return type3;
}
}
return null;
}
public static object GetFieldOrProp(object obj, string name)
{
if (obj == null || string.IsNullOrWhiteSpace(name))
{
return null;
}
Type type = obj.GetType();
while (type != null)
{
PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.GetIndexParameters().Length == 0)
{
try
{
return property.GetValue(obj);
}
catch
{
}
}
FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
try
{
return field.GetValue(obj);
}
catch
{
}
}
type = type.BaseType;
}
return null;
}
public static object GetStaticFieldOrProp(Type type, string name)
{
if (type == null || string.IsNullOrWhiteSpace(name))
{
return null;
}
Type type2 = type;
while (type2 != null)
{
PropertyInfo property = type2.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.GetIndexParameters().Length == 0)
{
try
{
return property.GetValue(null);
}
catch
{
}
}
FieldInfo field = type2.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
try
{
return field.GetValue(null);
}
catch
{
}
}
type2 = type2.BaseType;
}
return null;
}
public static bool SetFieldOrProp(object obj, string name, object value)
{
if (obj == null || string.IsNullOrWhiteSpace(name))
{
return false;
}
Type type = obj.GetType();
while (type != null)
{
PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.GetIndexParameters().Length == 0 && property.CanWrite)
{
try
{
property.SetValue(obj, value);
return true;
}
catch
{
}
}
FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
try
{
field.SetValue(obj, value);
return true;
}
catch
{
}
}
type = type.BaseType;
}
return false;
}
public static object FindFirstObjectOfType(Type type)
{
if (type == null)
{
return null;
}
Array array = InvokeFindObjectsOfType(typeof(Object), "FindObjectsOfType", type) ?? InvokeFindObjectsOfType(typeof(Resources), "FindObjectsOfTypeAll", type);
if (array == null || array.Length <= 0)
{
return null;
}
return array.GetValue(0);
}
public static void ForEachListItem(object list, Action<object> action)
{
if (list == null || action == null)
{
return;
}
if (list is Array array)
{
{
foreach (object item in array)
{
action(item);
}
return;
}
}
if (list is IEnumerable enumerable)
{
{
foreach (object item2 in enumerable)
{
action(item2);
}
return;
}
}
TryGetCollectionCount(list, out var count);
if (count <= 0)
{
return;
}
MethodInfo method = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null)
{
return;
}
for (int i = 0; i < count; i++)
{
try
{
action(method.Invoke(list, new object[1] { i }));
}
catch
{
}
}
}
public static int GetCollectionCount(object list)
{
if (list == null)
{
return 0;
}
if (list is Array array)
{
return array.Length;
}
if (TryGetCollectionCount(list, out var count))
{
return Math.Max(0, count);
}
int num = 0;
if (list is IEnumerable enumerable)
{
foreach (object item in enumerable)
{
_ = item;
num++;
}
}
return num;
}
public static bool TryGetCollectionCount(object list, out int count)
{
count = 0;
if (list == null)
{
return false;
}
if (list is Array array)
{
count = array.Length;
return true;
}
Type type = list.GetType();
if (ValueConversion.TryConvertInt(GetFieldOrProp(list, "Count"), out count))
{
return true;
}
if (ValueConversion.TryConvertInt(GetFieldOrProp(list, "Length"), out count))
{
return true;
}
MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "get_Count" && m.GetParameters().Length == 0);
if (methodInfo != null)
{
try
{
if (ValueConversion.TryConvertInt(methodInfo.Invoke(list, Array.Empty<object>()), out count))
{
return true;
}
}
catch
{
}
}
MethodInfo methodInfo2 = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "get_Length" && m.GetParameters().Length == 0);
if (methodInfo2 != null)
{
try
{
return ValueConversion.TryConvertInt(methodInfo2.Invoke(list, Array.Empty<object>()), out count);
}
catch
{
}
}
return false;
}
public static string DescribeCollectionAccessForLog(object list, bool enabled)
{
if (!enabled)
{
return "disabled";
}
if (list == null)
{
return "null";
}
try
{
Type type = list.GetType();
object fieldOrProp = GetFieldOrProp(list, "Count");
object fieldOrProp2 = GetFieldOrProp(list, "Length");
MethodInfo method = type.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
bool flag = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any((MethodInfo m) => m.Name == "get_Count" && m.GetParameters().Length == 0);
string empty = string.Empty;
try
{
empty = string.Join(",", (from i in type.GetInterfaces()
select i.FullName ?? i.Name into n
where n.IndexOf("IEnumerable", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("IReadOnlyList", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("IList", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("ICollection", StringComparison.OrdinalIgnoreCase) >= 0
select n).Take(6));
}
catch
{
empty = "unavailable";
}
return "type=" + SafeLogToken(type.FullName ?? type.Name) + " array=" + (list is Array) + " enumerable=" + (list is IEnumerable) + " count=" + FormatObjectForLog(fieldOrProp) + " length=" + FormatObjectForLog(fieldOrProp2) + " getCount=" + flag + " getItem=" + (method != null) + " helperCount=" + GetCollectionCount(list) + " interfaces=[" + empty + "]";
}
catch (Exception ex)
{
return "describe-failed:" + ex.GetType().Name + ":" + SafeLogToken(ex.Message);
}
}
public static bool TryGetIndexedItem(object list, object index, out object item)
{
item = null;
if (list == null)
{
return false;
}
if (list is Array array && index is int num)
{
if (num < 0 || num >= array.Length)
{
return false;
}
item = array.GetValue(num);
return true;
}
try
{
MethodInfo method = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method != null)
{
item = method.Invoke(list, new object[1] { index });
return true;
}
}
catch
{
}
if (index is int num2)
{
int num3 = 0;
if (list is IEnumerable enumerable)
{
foreach (object item2 in enumerable)
{
if (num3 == num2)
{
item = item2;
return true;
}
num3++;
}
}
}
return false;
}
private static Array InvokeFindObjectsOfType(Type ownerType, string methodName, Type searchedType)
{
try
{
return ownerType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo candidate)
{
ParameterInfo[] parameters = candidate.GetParameters();
return candidate.Name == methodName && parameters.Length == 1 && parameters[0].ParameterType == typeof(Type);
})?.Invoke(null, new object[1] { searchedType }) as Array;
}
catch
{
return null;
}
}
private static string FormatObjectForLog(object value)
{
if (value == null)
{
return "null";
}
try
{
return SafeLogToken(value.ToString());
}
catch
{
return "toString-failed";
}
}
private static string SafeLogToken(string value)
{
if (!string.IsNullOrWhiteSpace(value))
{
return value.Replace("|", "/").Replace("\r", " ").Replace("\n", " ")
.Trim();
}
return string.Empty;
}
}
internal static class LabelUtilities
{
public static bool TryReadVectorXZ(object vector, out float x, out float z)
{
x = 0f;
z = 0f;
if (vector != null && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "x"), out x))
{
return ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "z"), out z);
}
return false;
}
public static bool TryReadVectorXY(object vector, out float x, out float y)
{
x = 0f;
y = 0f;
if (vector != null && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "x"), out x))
{
return ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "y"), out y);
}
return false;
}
public static bool IsActiveInHierarchy(object obj)
{
bool result;
return !ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject") ?? obj, "activeInHierarchy"), out result) || result;
}
public static string GetObjectName(object obj)
{
return (Il2CppReflection.GetFieldOrProp(obj, "name") as string) ?? (Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject"), "name") as string);
}
public static string CleanObjectLabel(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return name;
}
return name.Replace("(Clone)", string.Empty).Trim();
}
public static object GetPlayerClientData(object player)
{
string source;
return GetPlayerClientData(player, out source);
}
public static object GetPlayerClientData(object player, out string source)
{
source = "none";
if (player == null)
{
return null;
}
object fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "ClientData");
if (fieldOrProp != null)
{
source = "ClientData";
return fieldOrProp;
}
fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "NetworkclientData");
if (fieldOrProp != null)
{
source = "NetworkclientData";
return fieldOrProp;
}
fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "clientData");
if (fieldOrProp != null)
{
source = "clientData";
return fieldOrProp;
}
fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "_ClientData_k__BackingField");
if (fieldOrProp != null)
{
source = "_ClientData_k__BackingField";
return fieldOrProp;
}
fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "_NetworkclientData_k__BackingField");
if (fieldOrProp != null)
{
source = "_NetworkclientData_k__BackingField";
return fieldOrProp;
}
return null;
}
public static string GetClientDataName(object clientData)
{
string source;
return GetClientDataName(clientData, out source);
}
public static string GetClientDataName(object clientData, out string source)
{
source = "none";
if (clientData == null)
{
return null;
}
string text = Il2CppReflection.GetFieldOrProp(clientData, "Name") as string;
if (!string.IsNullOrWhiteSpace(text))
{
source = "Name";
return text;
}
text = Il2CppReflection.GetFieldOrProp(clientData, "_Name_k__BackingField") as string;
if (!string.IsNullOrWhiteSpace(text))
{
source = "_Name_k__BackingField";
return text;
}
return null;
}
public static string GetEndMapPlayerName(object ui, int index)
{
if (ui == null || index < 0)
{
return null;
}
if (!Il2CppReflection.TryGetIndexedItem(Il2CppReflection.GetFieldOrProp(ui, "playerNameUIs"), index, out var item) || item == null)
{
return null;
}
return GetTextValue(Il2CppReflection.GetFieldOrProp(item, "playerNameText"));
}
public static string GetTextValue(object textObject)
{
string text = Il2CppReflection.GetFieldOrProp(textObject, "text") as string;
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}
text = Il2CppReflection.GetFieldOrProp(textObject, "Text") as string;
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}
return null;
}
public static bool IsUsefulPlayerLabel(string label)
{
if (string.IsNullOrWhiteSpace(label))
{
return false;
}
string text = label.Trim();
if (!string.Equals(text, "Player", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "Player(Clone)", StringComparison.OrdinalIgnoreCase))
{
return !text.StartsWith("PlayerNetworkSync", StringComparison.OrdinalIgnoreCase);
}
return false;
}
public static string DescribePlayerNameSources(object player, object ui, int index)
{
string source;
string source2;
string clientDataName = GetClientDataName(GetPlayerClientData(player, out source), out source2);
string textValue = GetTextValue(Il2CppReflection.GetFieldOrProp(player, "namePlate"));
string endMapPlayerName = GetEndMapPlayerName(ui, index);
string text = Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(player, "gameObject") ?? player, "name") as string;
return "player=" + DescribePlayerObject(player) + " clientData=" + source + " clientNameSource=" + source2 + " clientName='" + clientDataName + "' namePlate='" + textValue + "' endMap='" + endMapPlayerName + "' gameObject='" + text + "'";
}
public static string DescribePlayerObject(object player)
{
if (player == null)
{
return "null";
}
string text = player.GetType().FullName ?? player.GetType().Name;
if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(player, "netId"), out var result) && result > 0)
{
return text + "#net:" + result;
}
if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(player, "ID"), out var result2) && result2 > 0)
{
return text + "#id:" + result2;
}
return text + "#obj:" + RuntimeHelpers.GetHashCode(player);
}
}
internal static class ValueConversion
{
public static string GetStringValue(object value)
{
return value as string;
}
public static bool? GetBoolValue(object value)
{
if (value == null)
{
return null;
}
if (value is bool)
{
return (bool)value;
}
if (!bool.TryParse(value.ToString(), out var result))
{
return null;
}
return result;
}
public static bool TryConvertBool(object value, out bool result)
{
result = false;
if (value == null)
{
return false;
}
if (value is bool flag)
{
result = flag;
return true;
}
if (value is string text)
{
return bool.TryParse(text.Trim(), out result);
}
try
{
result = Convert.ToBoolean(value, CultureInfo.InvariantCulture);
return true;
}
catch
{
return bool.TryParse(value.ToString(), out result);
}
}
public static bool TryConvertInt(object value, out int result)
{
result = 0;
if (value == null)
{
return false;
}
if (value is Enum value2)
{
result = Convert.ToInt32(value2, CultureInfo.InvariantCulture);
return true;
}
try
{
result = Convert.ToInt32(value, CultureInfo.InvariantCulture);
return true;
}
catch
{
return int.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
}
}
public static bool TryConvertLong(object value, out long result)
{
result = 0L;
if (value == null)
{
return false;
}
if (value is Enum value2)
{
result = Convert.ToInt64(value2, CultureInfo.InvariantCulture);
return true;
}
if (!(value is long num))
{
if (!(value is int num2))
{
if (!(value is uint num3))
{
if (!(value is ulong num4))
{
if (value is short num5)
{
result = num5;
return true;
}
if (value is ushort num6)
{
result = num6;
return true;
}
if (value is byte b)
{
result = b;
return true;
}
if (value is sbyte b2)
{
result = b2;
return true;
}
if (value is string text)
{
return long.TryParse(text.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
}
}
else if (num4 <= long.MaxValue)
{
result = (long)num4;
return true;
}
try
{
result = Convert.ToInt64(value, CultureInfo.InvariantCulture);
return true;
}
catch
{
}
string[] array = new string[3] { "m_value", "value", "Value" };
foreach (string name in array)
{
object fieldOrProp = Il2CppReflection.GetFieldOrProp(value, name);
if (fieldOrProp != null && fieldOrProp != value && TryConvertLong(fieldOrProp, out result))
{
return true;
}
}
return long.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
}
result = num3;
return true;
}
result = num2;
return true;
}
result = num;
return true;
}
public static bool TryConvertFloat(object value, out float result)
{
result = 0f;
if (value == null)
{
return false;
}
try
{
result = Convert.ToSingle(value, CultureInfo.InvariantCulture);
return true;
}
catch
{
return float.TryParse(value.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out result);
}
}
public static bool TryConvertByte(object value, out byte result)
{
result = 0;
if (value == null)
{
return false;
}
try
{
result = Convert.ToByte(value, CultureInfo.InvariantCulture);
return true;
}
catch
{
return byte.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
}
}
public static int FloorToInt(float value)
{
return (int)Math.Floor(value);
}
public static float Clamp(float value, float min, float max)
{
if (value < min)
{
return min;
}
if (!(value > max))
{
return value;
}
return max;
}
public static int Clamp(int value, int min, int max)
{
if (value < min)
{
return min;
}
if (value <= max)
{
return value;
}
return max;
}
public static float Clamp01(float value)
{
return Clamp(value, 0f, 1f);
}
}