Decompiled source of RoguelikeSkillTreeVisualizer v0.1.0

plugins/RoguelikeSkillTreeVisualizer/RoguelikeSkillTreeVisualizer.dll

Decompiled 3 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Burst2Flame;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("RoguelikeSkillTreeVisualizer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Botao na tela Select Party do Roguelike que abre a Skill Tree nativa em modo somente leitura.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("RoguelikeSkillTreeVisualizer")]
[assembly: AssemblyTitle("RoguelikeSkillTreeVisualizer")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace RoguelikeSkillTreeVisualizer;

internal static class PartyTargets
{
	private static readonly List<Character> AdditionOrder = new List<Character>();

	internal static bool IsLocal(Character character)
	{
		try
		{
			return character != null && character.Owned;
		}
		catch (Exception)
		{
			return false;
		}
	}

	internal static void NoteAddition(Character character)
	{
		if (IsLocal(character))
		{
			AdditionOrder.RemoveAll((Character c) => c == null || c == character || !IsLocal(c));
			AdditionOrder.Add(character);
		}
	}

	internal static void NoteRemoval(Character character)
	{
		if (character != null)
		{
			AdditionOrder.Remove(character);
		}
	}

	internal static int LocalPartyCount()
	{
		try
		{
			return NetworkingManager.Instance.MyPartyCharactersUnaccepted?.Count ?? 0;
		}
		catch (Exception)
		{
			return 0;
		}
	}

	internal static Character Resolve()
	{
		try
		{
			List<Character> myPartyCharactersUnaccepted = NetworkingManager.Instance.MyPartyCharactersUnaccepted;
			if (myPartyCharactersUnaccepted == null || myPartyCharactersUnaccepted.Count == 0)
			{
				return null;
			}
			for (int num = AdditionOrder.Count - 1; num >= 0; num--)
			{
				Character val = AdditionOrder[num];
				if (val != null && myPartyCharactersUnaccepted.Contains(val))
				{
					return val;
				}
			}
			return myPartyCharactersUnaccepted[myPartyCharactersUnaccepted.Count - 1];
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha ao resolver o personagem da party: " + ex.Message));
			return null;
		}
	}
}
[HarmonyPatch(typeof(CharacterChoiceManager), "OpenCharacterChoiceManager", new Type[] { })]
internal static class CharacterChoiceOpenPatch
{
	private static bool _firstCallLogged;

	[HarmonyPostfix]
	private static void Postfix(CharacterChoiceManager __instance)
	{
		try
		{
			if (!_firstCallLogged)
			{
				_firstCallLogged = true;
				Plugin.Log.LogInfo((object)"RSTV-2: gancho da tela Select Party ATIVO (OpenCharacterChoiceManager).");
			}
			RstvHost.Ensure();
			SelectPartyButton.Ensure(__instance);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha no postfix de OpenCharacterChoiceManager: " + ex));
		}
	}
}
[HarmonyPatch(typeof(CharacterChoiceManager), "ToggleSelectedCharacter", new Type[] { typeof(int) })]
internal static class CharacterChoiceTogglePatch
{
	private static Character _subject;

	private static bool _firstCallLogged;

	[HarmonyPrefix]
	private static void Prefix(CharacterChoiceManager __instance)
	{
		try
		{
			_subject = (((Object)(object)__instance != (Object)null && (Object)(object)__instance.selectedCharacterChoiceItem != (Object)null) ? __instance.selectedCharacterChoiceItem.Character : null);
		}
		catch (Exception)
		{
			_subject = null;
		}
	}

	[HarmonyPostfix]
	private static void Postfix(CharacterChoiceManager __instance, int controllerPlayerId)
	{
		try
		{
			if (!_firstCallLogged)
			{
				_firstCallLogged = true;
				Plugin.Log.LogInfo((object)("RSTV-2: gancho de adicao/remocao da party ATIVO (ToggleSelectedCharacter, controllerPlayerId=" + controllerPlayerId + ")."));
			}
			Character subject = _subject;
			_subject = null;
			if (subject != null && PartyTargets.IsLocal(subject))
			{
				if (subject.SelectedForBattle)
				{
					PartyTargets.NoteAddition(subject);
					Plugin.Log.LogInfo((object)("RSTV-2: '" + subject.CharacterName + "' entrou na MINHA party — novo alvo do botao."));
				}
				else
				{
					PartyTargets.NoteRemoval(subject);
					Character val = PartyTargets.Resolve();
					Plugin.Log.LogInfo((object)("RSTV-2: '" + subject.CharacterName + "' saiu da minha party — alvo agora: " + ((val != null) ? val.CharacterName : "(nenhum)")));
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha no postfix de ToggleSelectedCharacter: " + ex));
		}
	}
}
[HarmonyPatch(typeof(SkillTreeManager), "AcceptSkillChanges", new Type[] { typeof(bool) })]
internal static class SkillTreeManagerAcceptSkillChangesPatch
{
	[HarmonyPrefix]
	private static bool Prefix(SkillTreeManager __instance, bool closeMenu)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Invalid comparison between Unknown and I4
		try
		{
			if (!ReadOnlySession.Active || (Object)(object)__instance == (Object)null)
			{
				return true;
			}
			if ((Object)(object)GUIManager.instance != (Object)null && (int)GUIManager.instance.CurrentGuiState == 1)
			{
				return true;
			}
			Character target = ReadOnlySession.Target;
			__instance.SkillsToAdd.Clear();
			__instance.SkillsRemoved.Clear();
			__instance.SkillsOnEnter.Clear();
			HashSet<SkillInfo> hashSet = new HashSet<SkillInfo>();
			if (target != null)
			{
				hashSet.UnionWith(target.SkillsFromPoints);
			}
			Character val = (((Object)(object)GameLogic.instance != (Object)null) ? GameLogic.instance.CurrentlySelectedCharacter : null);
			if (val != null && val != target)
			{
				hashSet.UnionWith(val.SkillsFromPoints);
			}
			__instance.SkillsOnEnter.AddRange(hashSet);
			Plugin.Log.LogInfo((object)("RSTV-2: commit higienizado (read-only, closeMenu=" + closeMenu + ") — nenhuma escrita no personagem (" + hashSet.Count + " skills no snapshot)."));
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha ao higienizar AcceptSkillChanges — o original NAO vai rodar (zero escrita); fechando a janela para nao prender a UI: " + ex));
			try
			{
				ReadOnlySession.Close();
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogError((object)("RSTV: e o fechamento de emergencia tambem falhou: " + ex2.Message));
			}
			return false;
		}
	}
}
[HarmonyPatch(typeof(SkillTreeManager), "Initialize", new Type[]
{
	typeof(List<SkillInfo>),
	typeof(int),
	typeof(bool)
})]
internal static class SkillTreeManagerInitializePatch
{
	[HarmonyPostfix]
	private static void Postfix(SkillTreeManager __instance, bool creating)
	{
		try
		{
			if (!ReadOnlySession.Active || (Object)(object)__instance == (Object)null || creating)
			{
				return;
			}
			Character target = ReadOnlySession.Target;
			if (target != null)
			{
				bool num = __instance.startingUnspentPoints != 0 || __instance.SkillsToAdd.Count > 0 || __instance.SkillsRemoved.Count > 0 || __instance.SkillsOnEnter.Count != target.SkillsFromPoints.Count;
				__instance.SkillsToAdd.Clear();
				__instance.SkillsRemoved.Clear();
				__instance.SkillsOnEnter.Clear();
				__instance.SkillsOnEnter.AddRange(target.SkillsFromPoints);
				__instance.startingUnspentPoints = 0;
				__instance.RefreshRespecFooter();
				if (num)
				{
					__instance.UpdateCurrentTree();
					Plugin.Log.LogInfo((object)"RSTV-2: estado interno reafirmado (read-only) depois de um Initialize do jogo.");
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha ao reafirmar o modo somente leitura — fechando a arvore para nao deixar uma janela interativa em estado desconhecido: " + ex));
			try
			{
				ReadOnlySession.Close();
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogError((object)("RSTV: e o fechamento de emergencia tambem falhou: " + ex2.Message));
			}
		}
	}
}
[HarmonyPatch(typeof(SkillTreeManager), "RefreshRespecFooter", new Type[] { })]
internal static class SkillTreeManagerRespecFooterPatch
{
	[HarmonyPostfix]
	private static void Postfix(SkillTreeManager __instance)
	{
		try
		{
			if (ReadOnlySession.Active && (Object)(object)__instance != (Object)null && (Object)(object)__instance.RespecButton != (Object)null && __instance.RespecButton.activeSelf)
			{
				__instance.RespecButton.SetActive(false);
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha ao esconder o RespecButton: " + ex));
		}
	}
}
[HarmonyPatch(typeof(SkillTreeItem), "ToggleAddToSkillToAddList", new Type[] { })]
internal static class SkillTreeItemTogglePatch
{
	private static bool _firstBlockLogged;

	[HarmonyPrefix]
	private static bool Prefix(SkillTreeItem __instance)
	{
		bool active;
		try
		{
			active = ReadOnlySession.Active;
		}
		catch (Exception)
		{
			return true;
		}
		if (!active)
		{
			return true;
		}
		try
		{
			if (!_firstBlockLogged)
			{
				_firstBlockLogged = true;
				string text = (((Object)(object)__instance != (Object)null && (Object)(object)__instance.SkillInfo != (Object)null) ? __instance.SkillInfo.SkillName : "?");
				Plugin.Log.LogInfo((object)("RSTV-2: clique na arvore BLOQUEADO (read-only) — primeira skill clicada: " + text + "."));
			}
		}
		catch (Exception ex2)
		{
			Plugin.Log.LogWarning((object)("RSTV: nao deu para logar o bloqueio do clique (o clique continua bloqueado): " + ex2.Message));
		}
		return false;
	}
}
[HarmonyPatch(typeof(SkillTreeManager), "ResetSkillPoints", new Type[] { })]
internal static class SkillTreeManagerResetSkillPointsPatch
{
	private static bool _firstBlockLogged;

	[HarmonyPrefix]
	private static bool Prefix()
	{
		if (!ReadOnlySession.Active)
		{
			return true;
		}
		if (!_firstBlockLogged)
		{
			_firstBlockLogged = true;
			Plugin.Log.LogInfo((object)"RSTV-2: ResetSkillPoints BLOQUEADO (read-only) — nada foi zerado no personagem.");
		}
		return false;
	}
}
[HarmonyPatch(typeof(SkillTreeManager), "OnDisable", new Type[] { })]
internal static class SkillTreeManagerOnDisablePatch
{
	[HarmonyPostfix]
	private static void Postfix(SkillTreeManager __instance)
	{
		try
		{
			if (ReadOnlySession.Active)
			{
				ReadOnlySession.End();
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha no postfix de OnDisable: " + ex));
		}
	}
}
[BepInPlugin("com.gumatos.roguelikeskilltreevisualizer", "Roguelike Skill Tree Visualizer", "0.1.0")]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "com.gumatos.roguelikeskilltreevisualizer";

	public const string Version = "0.1.0";

	internal static ManualLogSource Log { get; private set; }

	internal static ConfigEntry<bool> AtivarBotao { get; private set; }

	internal static bool BotaoLigado
	{
		get
		{
			if (AtivarBotao != null)
			{
				return AtivarBotao.Value;
			}
			return true;
		}
	}

	private void Awake()
	{
		Log = ((BaseUnityPlugin)this).Logger;
		try
		{
			AtivarBotao = ((BaseUnityPlugin)this).Config.Bind<bool>("Geral", "AtivarBotao", true, "Injeta o botao 'Skills' na tela Select Party do modo Roguelike. Padrao: true. Com false o mod carrega e registra os ganchos, mas nao cria botao nenhum na tela.");
		}
		catch (Exception ex)
		{
			Log.LogError((object)("RSTV: falha ao ler/criar o arquivo de config — seguindo com o padrao (botao LIGADO): " + ex.Message));
		}
		Log.LogInfo((object)"Roguelike Skill Tree Visualizer 0.1.0 carregado (RSTV-2: botao ao lado do Choose Powerups + skill tree read-only).");
		AplicarPatches();
		Log.LogInfo((object)("RSTV DIAG: botao 'Skills' " + (BotaoLigado ? "HABILITADO" : "DESABILITADO") + " nesta sessao (AtivarBotao=" + BotaoLigado + " no config)."));
	}

	private static void AplicarPatches()
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		Harmony val = new Harmony("com.gumatos.roguelikeskilltreevisualizer");
		List<string> list = new List<string>();
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		Type[] types;
		try
		{
			types = typeof(Plugin).Assembly.GetTypes();
		}
		catch (Exception ex)
		{
			Log.LogError((object)("RSTV: nao deu para listar os tipos do mod — nenhum gancho aplicado: " + ex));
			return;
		}
		foreach (Type type in types)
		{
			if (EhClasseDeGancho(type))
			{
				num++;
				try
				{
					int num4 = val.CreateClassProcessor(type).Patch()?.Count ?? 0;
					num3 += num4;
					num2++;
					Log.LogInfo((object)("RSTV: gancho aplicado — " + type.Name + " (" + num4 + " metodo(s) do jogo)."));
				}
				catch (Exception ex2)
				{
					list.Add(type.Name);
					Log.LogError((object)("RSTV: FALHA ao aplicar o gancho " + type.Name + " — " + ex2.Message));
				}
			}
		}
		string text = "RSTV: patches Harmony aplicados (" + num2 + "/" + num + " ganchos, " + num3 + " metodos do jogo).";
		if (list.Count == 0)
		{
			Log.LogInfo((object)text);
		}
		else
		{
			Log.LogError((object)(text + " GANCHOS QUE FALHARAM: " + string.Join(", ", list.ToArray()) + ". O mod continua de pe, mas o recurso que dependia deles nao existe nesta sessao."));
		}
	}

	private static bool EhClasseDeGancho(Type tipo)
	{
		try
		{
			if (tipo == null || !tipo.IsClass)
			{
				return false;
			}
			if (tipo.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length == 0)
			{
				return false;
			}
			MethodInfo[] methods = tipo.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.GetCustomAttributes(typeof(HarmonyPrefix), inherit: false).Length != 0 || methodInfo.GetCustomAttributes(typeof(HarmonyPostfix), inherit: false).Length != 0)
				{
					return true;
				}
			}
			return false;
		}
		catch (Exception)
		{
			return false;
		}
	}
}
internal class RstvHost : MonoBehaviour
{
	private static RstvHost _instance;

	private float _nextMirror;

	private float _lastErrorLog;

	internal static RstvHost Ensure()
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Expected O, but got Unknown
		if ((Object)(object)_instance != (Object)null)
		{
			return _instance;
		}
		GameObject val = new GameObject("RstvHost");
		Object.DontDestroyOnLoad((Object)val);
		_instance = val.AddComponent<RstvHost>();
		Plugin.Log.LogInfo((object)"RSTV: host criado (cena viva) — Update() ativo.");
		return _instance;
	}

	private void Update()
	{
		try
		{
			SkillTreeReadOnly.Tick();
			if (!(Time.realtimeSinceStartup < _nextMirror))
			{
				_nextMirror = Time.realtimeSinceStartup + 0.2f;
				SelectPartyButton.Mirror();
			}
		}
		catch (Exception ex)
		{
			if (!(Time.realtimeSinceStartup - _lastErrorLog < 5f))
			{
				_lastErrorLog = Time.realtimeSinceStartup;
				Plugin.Log.LogError((object)("RSTV: falha no Update do host: " + ex));
			}
		}
	}
}
internal static class SelectPartyButton
{
	internal const string ButtonName = "RstvSkillTreeButton";

	internal const string Label = "Skills";

	private static CharacterChoiceManager _owner;

	private static GameObject _button;

	private static string _reason;

	private static string _reasonLogged;

	private static CharacterChoiceManager _diagnosedFor;

	internal static void Ensure(CharacterChoiceManager manager)
	{
		//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f9: Expected O, but got Unknown
		try
		{
			if ((Object)(object)manager == (Object)null)
			{
				Fail("a instancia da tela Select Party chegou nula", aviso: false);
				return;
			}
			if (!Plugin.BotaoLigado)
			{
				Fail("AtivarBotao=false no arquivo de config", aviso: false);
				return;
			}
			Button roguelikePowerupButton = manager.roguelikePowerupButton;
			if ((Object)(object)roguelikePowerupButton == (Object)null)
			{
				Fail("o botao nativo do qual o clone nasce (roguelikePowerupButton / 'Choose Powerups') nao existe nesta tela", aviso: true);
			}
			else if ((Object)(object)((Component)roguelikePowerupButton).transform == (Object)null || (Object)(object)((Component)roguelikePowerupButton).transform.parent == (Object)null)
			{
				Fail("o botao nativo 'Choose Powerups' esta sem Transform ou sem pai na hierarquia", aviso: true);
			}
			else
			{
				if ((Object)(object)_owner == (Object)(object)manager && (Object)(object)_button != (Object)null)
				{
					return;
				}
				Transform parent = ((Component)roguelikePowerupButton).transform.parent;
				RectTransform component = ((Component)roguelikePowerupButton).GetComponent<RectTransform>();
				if ((Object)(object)component == (Object)null)
				{
					Fail("o botao nativo 'Choose Powerups' nao tem RectTransform (sem isso nao da para medir/repor a largura da linha)", aviso: true);
					return;
				}
				float num = RowWidth(parent);
				float num2 = WorldWidth(component);
				if (num2 <= 1f)
				{
					num2 = component.sizeDelta.x;
				}
				float side = SquareSide(roguelikePowerupButton, component);
				Transform val = parent.Find("RstvSkillTreeButton");
				GameObject val2;
				if ((Object)(object)val != (Object)null)
				{
					val2 = ((Component)val).gameObject;
				}
				else
				{
					val2 = Object.Instantiate<GameObject>(((Component)roguelikePowerupButton).gameObject, parent);
					((Object)val2).name = "RstvSkillTreeButton";
				}
				val2.transform.SetSiblingIndex(((Component)roguelikePowerupButton).transform.GetSiblingIndex() + 1);
				RectTransform component2 = val2.GetComponent<RectTransform>();
				if ((Object)(object)component2 == (Object)null)
				{
					Fail("o clone do botao ficou sem RectTransform", aviso: true);
					return;
				}
				TextMeshProUGUI componentInChildren = val2.GetComponentInChildren<TextMeshProUGUI>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					((TMP_Text)componentInChildren).text = OptionsManager.Localize("Skills", (LanguageGender)0, (Gender)2);
				}
				DisableLocalizers(val2);
				val2.SetActive(((Component)roguelikePowerupButton).gameObject.activeSelf);
				ApplySquareLayout(roguelikePowerupButton, component, val2, component2, side, num2, parent);
				Button component3 = val2.GetComponent<Button>();
				if ((Object)(object)component3 != (Object)null)
				{
					((UnityEventBase)component3.onClick).RemoveAllListeners();
					((UnityEvent)component3.onClick).AddListener(new UnityAction(OnClick));
					((Selectable)component3).interactable = PartyTargets.Resolve() != null;
				}
				_owner = manager;
				_button = val2;
				_reason = null;
				_reasonLogged = null;
				_diagnosedFor = manager;
				Plugin.Log.LogInfo((object)("RSTV-2: botao 'Skills' injetado (" + side.ToString("0.#") + "x" + side.ToString("0.#") + " px) a direita do Choose Powerups — largura da linha " + num.ToString("0.#") + " -> " + RowWidth(parent).ToString("0.#") + " px; pai=" + ((Object)parent).name + "; grupo=" + DescribeGroup(parent)));
			}
		}
		catch (Exception ex)
		{
			Fail("excecao durante a injecao (" + ex.GetType().Name + "): " + ex.Message, aviso: true);
			Plugin.Log.LogError((object)("RSTV: falha ao injetar o botao: " + ex));
		}
	}

	internal static void Mirror()
	{
		CharacterChoiceManager instance = CharacterChoiceManager.Instance;
		if ((Object)(object)instance == (Object)null || !Plugin.BotaoLigado)
		{
			return;
		}
		if ((Object)(object)_button == (Object)null)
		{
			if ((Object)(object)_diagnosedFor != (Object)(object)instance && ((Component)instance).gameObject.activeInHierarchy)
			{
				_diagnosedFor = instance;
				DumpDiagnosis(instance);
			}
		}
		else
		{
			if ((Object)(object)instance != (Object)(object)_owner)
			{
				return;
			}
			Button roguelikePowerupButton = instance.roguelikePowerupButton;
			if ((Object)(object)roguelikePowerupButton == (Object)null)
			{
				return;
			}
			bool activeSelf = ((Component)roguelikePowerupButton).gameObject.activeSelf;
			if (_button.activeSelf != activeSelf)
			{
				_button.SetActive(activeSelf);
			}
			Button component = _button.GetComponent<Button>();
			if ((Object)(object)component != (Object)null)
			{
				bool flag = PartyTargets.Resolve() != null;
				if (((Selectable)component).interactable != flag)
				{
					((Selectable)component).interactable = flag;
				}
			}
		}
	}

	private static void Fail(string motivo, bool aviso)
	{
		_reason = motivo;
		if (!(_reasonLogged == motivo))
		{
			_reasonLogged = motivo;
			if (aviso)
			{
				Plugin.Log.LogWarning((object)("RSTV DIAG: botao 'Skills' NAO injetado — " + motivo + "."));
			}
			else
			{
				Plugin.Log.LogInfo((object)("RSTV DIAG: botao 'Skills' desativado — " + motivo + "."));
			}
		}
	}

	private static void DumpDiagnosis(CharacterChoiceManager manager)
	{
		string text = (((Object)(object)manager != (Object)null && (Object)(object)((Component)manager).gameObject != (Object)null) ? ((Object)((Component)manager).gameObject).name : "AUSENTE");
		bool flag = (Object)(object)manager != (Object)null && (Object)(object)((Component)manager).gameObject != (Object)null && ((Component)manager).gameObject.activeInHierarchy;
		Button val = (((Object)(object)manager != (Object)null) ? manager.roguelikePowerupButton : null);
		Transform val2 = (((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform != (Object)null) ? ((Component)val).transform.parent : null);
		Plugin.Log.LogWarning((object)("RSTV DIAG: a tela Select Party esta aberta e o botao 'Skills' NAO foi injetado. motivo=" + ((_reason != null) ? _reason : "desconhecido") + "; tela=" + text + "; ativaNaHierarquia=" + flag + "; botao nativo do clone=" + (((Object)(object)val != (Object)null) ? "presente" : "AUSENTE") + "; paiDoBotao=" + (((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "AUSENTE") + "; grupo=" + DescribeGroup(val2) + "; party local=" + PartyTargets.LocalPartyCount() + " personagem(ns)."));
	}

	private static void OnClick()
	{
		try
		{
			Character val = PartyTargets.Resolve();
			if (val == null)
			{
				Plugin.Log.LogWarning((object)"RSTV: clique sem personagem local na party — nada a abrir.");
				return;
			}
			Plugin.Log.LogInfo((object)("RSTV-2: clique no botao 'Skills' -> '" + val.CharacterName + "' (nivel " + val.Level + ")."));
			SkillTreeReadOnly.Open(val);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha no clique do botao: " + ex));
		}
	}

	private static float SquareSide(Button original, RectTransform rect)
	{
		//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_0018: Unknown result type (might be due to invalid IL or missing references)
		Rect rect2 = rect.rect;
		float num = ((Rect)(ref rect2)).height;
		if (num <= 1f)
		{
			num = rect.sizeDelta.y;
		}
		if (num <= 1f)
		{
			LayoutElement component = ((Component)original).GetComponent<LayoutElement>();
			if ((Object)(object)component != (Object)null && component.preferredHeight > 0f)
			{
				num = component.preferredHeight;
			}
		}
		if (num <= 1f)
		{
			num = 44f;
		}
		return num;
	}

	private static void ApplySquareLayout(Button original, RectTransform srcRt, GameObject clone, RectTransform newRt, float side, float widthBefore, 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_001b: 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_01d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0137: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0154: 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_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		Rect rect = srcRt.rect;
		float num;
		if (!(((Rect)(ref rect)).height > 1f))
		{
			num = side;
		}
		else
		{
			rect = srcRt.rect;
			num = ((Rect)(ref rect)).height;
		}
		float num2 = num;
		LayoutElement val = EnsureLayoutElement(((Component)original).gameObject);
		LayoutElement obj = EnsureLayoutElement(clone);
		obj.preferredWidth = side;
		obj.minWidth = side;
		obj.flexibleWidth = 0f;
		obj.preferredHeight = num2;
		obj.minHeight = num2;
		HorizontalOrVerticalLayoutGroup val2 = FindRowGroup(parent, ((Component)original).transform);
		bool flag = (Object)(object)val2 != (Object)null && val2.childControlWidth && !val2.childForceExpandWidth;
		float num3 = (((Object)(object)val2 != (Object)null) ? val2.spacing : 0f);
		if ((Object)(object)val2 != (Object)null && val2.childForceExpandWidth)
		{
			Plugin.Log.LogInfo((object)"RSTV: linha usa LayoutGroup com childForceExpandWidth — o proprio grupo acomoda o filho novo (sem compensacao manual de largura).");
		}
		else if (flag)
		{
			float num4 = ((val.preferredWidth > 0f) ? val.preferredWidth : widthBefore);
			float num5 = side + num3;
			float num6 = Mathf.Max(1f, num4 - num5);
			val.minWidth = Mathf.Min((val.minWidth > 0f) ? val.minWidth : num6, num6);
			val.preferredWidth = num6;
			srcRt.sizeDelta = new Vector2(Mathf.Max(0f, srcRt.sizeDelta.x - num5), srcRt.sizeDelta.y);
			newRt.sizeDelta = new Vector2(Mathf.Max(0f, newRt.sizeDelta.x - (widthBefore - side)), newRt.sizeDelta.y);
			LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)(object)((parent is RectTransform) ? parent : null));
		}
		else
		{
			float num7 = ((num3 > 0f) ? num3 : 4f);
			float num8 = side + num7;
			ShrinkKeepingLeftEdge(srcRt, Mathf.Max(1f, widthBefore - num8));
			newRt.sizeDelta = new Vector2(Mathf.Max(0f, newRt.sizeDelta.x - (widthBefore - side)), newRt.sizeDelta.y);
			Canvas.ForceUpdateCanvases();
			PlaceToTheRight(srcRt, newRt, num7);
		}
		Plugin.Log.LogInfo((object)("[RSTV-2] geometria: original=" + WorldWidth(srcRt).ToString("0.#") + " clone=" + WorldWidth(newRt).ToString("0.#") + " linha=" + (((Object)(object)((parent is RectTransform) ? parent : null) != (Object)null) ? RowWidth(parent).ToString("0.#") : "?")));
	}

	private static float WorldWidth(RectTransform rect)
	{
		if ((Object)(object)rect == (Object)null)
		{
			return 0f;
		}
		Vector3[] array = (Vector3[])(object)new Vector3[4];
		rect.GetWorldCorners(array);
		return array[3].x - array[0].x;
	}

	private static float RowWidth(Transform parent)
	{
		RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
		if ((Object)(object)val == (Object)null || ((Transform)val).childCount == 0)
		{
			return 0f;
		}
		Vector3[] array = (Vector3[])(object)new Vector3[4];
		float num = float.MaxValue;
		float num2 = float.MinValue;
		for (int i = 0; i < ((Transform)val).childCount; i++)
		{
			Transform child = ((Transform)val).GetChild(i);
			RectTransform val2 = (RectTransform)(object)((child is RectTransform) ? child : null);
			if (!((Object)(object)val2 == (Object)null) && ((Component)val2).gameObject.activeSelf)
			{
				val2.GetWorldCorners(array);
				num = Mathf.Min(num, array[0].x);
				num2 = Mathf.Max(num2, array[3].x);
			}
		}
		if (!(num2 > num))
		{
			return 0f;
		}
		return num2 - num;
	}

	private static void ShrinkKeepingLeftEdge(RectTransform rect, float newWidth)
	{
		//IL_0040: 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_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: 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)
		Vector3[] array = (Vector3[])(object)new Vector3[4];
		rect.GetWorldCorners(array);
		float x = array[0].x;
		float num = array[3].x - array[0].x;
		if (!(num <= 1f))
		{
			rect.sizeDelta = new Vector2(rect.sizeDelta.x - (num - newWidth), rect.sizeDelta.y);
			Canvas.ForceUpdateCanvases();
			Vector3[] array2 = (Vector3[])(object)new Vector3[4];
			rect.GetWorldCorners(array2);
			float num2 = x - array2[0].x;
			if (Mathf.Abs(num2) > 0.01f)
			{
				((Transform)rect).position = ((Transform)rect).position + new Vector3(num2, 0f, 0f);
			}
		}
	}

	private static void PlaceToTheRight(RectTransform left, RectTransform right, float gap)
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		Vector3[] array = (Vector3[])(object)new Vector3[4];
		Vector3[] array2 = (Vector3[])(object)new Vector3[4];
		left.GetWorldCorners(array);
		right.GetWorldCorners(array2);
		float num = array[3].x + gap - array2[0].x;
		((Transform)right).position = ((Transform)right).position + new Vector3(num, 0f, 0f);
	}

	private static HorizontalOrVerticalLayoutGroup FindRowGroup(Transform parent, Transform source)
	{
		if ((Object)(object)parent == (Object)null)
		{
			return null;
		}
		HorizontalOrVerticalLayoutGroup component = ((Component)parent).GetComponent<HorizontalOrVerticalLayoutGroup>();
		if ((Object)(object)component != (Object)null && (Object)(object)source.parent == (Object)(object)((Component)component).transform)
		{
			return component;
		}
		Transform parent2 = parent.parent;
		if ((Object)(object)parent2 != (Object)null)
		{
			HorizontalOrVerticalLayoutGroup component2 = ((Component)parent2).GetComponent<HorizontalOrVerticalLayoutGroup>();
			if ((Object)(object)component2 != (Object)null && (Object)(object)source.parent == (Object)(object)((Component)component2).transform)
			{
				return component2;
			}
		}
		return null;
	}

	private static string DescribeGroup(Transform parent)
	{
		if ((Object)(object)parent == (Object)null)
		{
			return "nenhum";
		}
		LayoutGroup component = ((Component)parent).GetComponent<LayoutGroup>();
		if (!((Object)(object)component == (Object)null))
		{
			return ((object)component).GetType().Name;
		}
		return "nenhum";
	}

	private static LayoutElement EnsureLayoutElement(GameObject go)
	{
		LayoutElement component = go.GetComponent<LayoutElement>();
		if (!((Object)(object)component != (Object)null))
		{
			return go.AddComponent<LayoutElement>();
		}
		return component;
	}

	private static void DisableLocalizers(GameObject root)
	{
		Component[] componentsInChildren = root.GetComponentsInChildren<Component>(true);
		foreach (Component val in componentsInChildren)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			string name = ((object)val).GetType().Name;
			if (name.IndexOf("Localiz", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Localize", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null);
				if ((Object)(object)val2 != (Object)null)
				{
					val2.enabled = false;
					Plugin.Log.LogInfo((object)("RSTV: localizador '" + name + "' desativado no botao clonado."));
				}
			}
		}
	}
}
internal static class SkillTreeReadOnly
{
	private static Character _pendingTarget;

	private static float _pendingSince;

	private const float PendingTimeout = 15f;

	internal static void Open(Character target)
	{
		try
		{
			OpenInternal(target);
		}
		catch (Exception ex)
		{
			_pendingTarget = null;
			ReadOnlySession.End();
			Plugin.Log.LogError((object)("RSTV: falha ao abrir a skill tree read-only: " + ex));
		}
	}

	private static void OpenInternal(Character target)
	{
		if (target == null)
		{
			Plugin.Log.LogWarning((object)"RSTV: pedido de abrir a arvore sem personagem — nada a fazer.");
			return;
		}
		if ((Object)(object)GameLogic.instance == (Object)null)
		{
			Plugin.Log.LogError((object)"RSTV: GameLogic.instance ausente — sem ele a arvore mostraria o personagem errado; abertura cancelada.");
			return;
		}
		GameLogic.instance.CurrentlySelectedCharacter = target;
		SkillTreeManager instance = LoadableUIWindow<SkillTreeManager>.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			_pendingTarget = target;
			_pendingSince = Time.realtimeSinceStartup;
			ReferenceLoader instance2 = ReferenceLoader.Instance;
			if ((Object)(object)instance2 == (Object)null)
			{
				Plugin.Log.LogError((object)"RSTV: ReferenceLoader ausente — nao da para carregar a skill tree.");
				_pendingTarget = null;
			}
			else
			{
				Plugin.Log.LogInfo((object)"RSTV-2: SkillTreeManager ainda nao existia — carregando a instancia nativa.");
				LoadableUIWindow<SkillTreeManager>.LoadInstanceReference(((Component)instance2.SkillTreeManager).gameObject, (Transform)(object)instance2.SkillTreeManagerPlaceholder);
			}
		}
		else
		{
			Finish(target, instance);
		}
	}

	internal static void Tick()
	{
		if (ReadOnlySession.Active && !PartyScreenIsOpen())
		{
			Plugin.Log.LogInfo((object)"RSTV-2: a tela Select Party fechou com a arvore aberta — fechando a skill tree read-only (nada foi gravado no personagem).");
			ReadOnlySession.Close();
		}
		else if (_pendingTarget != null)
		{
			SkillTreeManager instance = LoadableUIWindow<SkillTreeManager>.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				Character pendingTarget = _pendingTarget;
				_pendingTarget = null;
				Finish(pendingTarget, instance);
			}
			else if (Time.realtimeSinceStartup - _pendingSince > 15f)
			{
				_pendingTarget = null;
				Plugin.Log.LogError((object)"RSTV: timeout esperando o SkillTreeManager carregar (15s).");
			}
		}
	}

	private static bool PartyScreenIsOpen()
	{
		CharacterChoiceManager instance = CharacterChoiceManager.Instance;
		if ((Object)(object)instance != (Object)null && (Object)(object)((Component)instance).gameObject != (Object)null)
		{
			return ((Component)instance).gameObject.activeSelf;
		}
		return false;
	}

	private static void Finish(Character target, SkillTreeManager instance)
	{
		try
		{
			if (target != null && !((Object)(object)instance == (Object)null))
			{
				ReadOnlySession.Begin(target);
				((Component)instance).gameObject.SetActive(true);
				instance.Initialize(target.SkillsFromPoints.ToList(), 0, false);
				ZOrder.EnsureAbove(((Component)instance).transform, ((Object)(object)CharacterChoiceManager.Instance != (Object)null) ? ((Component)CharacterChoiceManager.Instance).transform : null);
				ReadOnlySession.InstallCancelInterceptor();
				Plugin.Log.LogInfo((object)("RSTV-2: skill tree read-only aberta para '" + target.CharacterName + "' (nivel " + target.Level + ", " + target.SkillsFromPoints.Count + " skills, 0 pontos). ativaNaHierarquia=" + ((Component)instance).gameObject.activeInHierarchy));
			}
		}
		catch (Exception ex)
		{
			ReadOnlySession.End();
			try
			{
				if ((Object)(object)instance != (Object)null)
				{
					((Component)instance).gameObject.SetActive(false);
				}
			}
			catch (Exception)
			{
			}
			Plugin.Log.LogError((object)("RSTV: falha ao abrir a skill tree: " + ex));
		}
	}
}
internal static class ReadOnlySession
{
	private static Func<bool> _interceptor;

	internal static bool Active { get; private set; }

	internal static Character Target { get; private set; }

	internal static void Begin(Character target)
	{
		Active = true;
		Target = target;
		Plugin.Log.LogInfo((object)"RSTV-2: modo somente leitura ATIVO.");
	}

	internal static void End()
	{
		if (Active)
		{
			Active = false;
			Target = null;
			RemoveCancelInterceptor();
			ZOrder.Restore();
			Plugin.Log.LogInfo((object)"RSTV-2: modo somente leitura encerrado (nada foi gravado no personagem).");
		}
	}

	internal static void InstallCancelInterceptor()
	{
		_interceptor = OnCancelPressed;
		UIWindowManager.CancelInterceptor = _interceptor;
	}

	internal static void RemoveCancelInterceptor()
	{
		if (_interceptor != null && UIWindowManager.CancelInterceptor == _interceptor)
		{
			UIWindowManager.CancelInterceptor = null;
		}
		_interceptor = null;
	}

	internal static void Close()
	{
		SkillTreeManager instance = LoadableUIWindow<SkillTreeManager>.Instance;
		End();
		try
		{
			if ((Object)(object)instance != (Object)null)
			{
				instance.SkillsToAdd.Clear();
				instance.SkillsRemoved.Clear();
				((Component)instance).gameObject.SetActive(false);
			}
			if ((Object)(object)GUIManager.instance != (Object)null && (Object)(object)GUIManager.instance.tooltip != (Object)null)
			{
				GUIManager.instance.tooltip.HideTooltip();
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("RSTV: falha ao fechar a skill tree read-only: " + ex.Message));
		}
	}

	private static bool OnCancelPressed()
	{
		if (!Active)
		{
			return false;
		}
		Plugin.Log.LogInfo((object)"RSTV-2: Esc/B consumido — fechando a skill tree read-only.");
		Close();
		return true;
	}
}
internal static class ZOrder
{
	private static Transform _movedBranch;

	private static int _movedBranchOldIndex = -1;

	internal static void EnsureAbove(Transform above, Transform reference)
	{
		try
		{
			Restore();
			if ((Object)(object)above == (Object)null || (Object)(object)reference == (Object)null)
			{
				return;
			}
			Transform val = CommonAncestor(above, reference);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Transform val2 = BranchUnder(val, above);
			Transform val3 = BranchUnder(val, reference);
			if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val3 == (Object)null))
			{
				if (val2.GetSiblingIndex() < val3.GetSiblingIndex())
				{
					_movedBranch = val2;
					_movedBranchOldIndex = val2.GetSiblingIndex();
					val2.SetAsLastSibling();
					Plugin.Log.LogInfo((object)("RSTV-2: z-order — ramo '" + ((Object)val2).name + "' estava em " + _movedBranchOldIndex + " (atras de '" + ((Object)val3).name + "', " + val3.GetSiblingIndex() + "); movido para o fim."));
				}
				if ((Object)(object)above.parent == (Object)(object)reference.parent && above.GetSiblingIndex() < reference.GetSiblingIndex())
				{
					above.SetAsLastSibling();
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("RSTV: nao deu para garantir o z-order da arvore: " + ex.Message));
		}
	}

	internal static void Restore()
	{
		try
		{
			if ((Object)(object)_movedBranch != (Object)null && _movedBranchOldIndex >= 0)
			{
				_movedBranch.SetSiblingIndex(_movedBranchOldIndex);
			}
		}
		catch (Exception)
		{
		}
		finally
		{
			_movedBranch = null;
			_movedBranchOldIndex = -1;
		}
	}

	private static Transform CommonAncestor(Transform a, Transform b)
	{
		HashSet<Transform> hashSet = new HashSet<Transform>();
		Transform val = a;
		while ((Object)(object)val != (Object)null)
		{
			hashSet.Add(val);
			val = val.parent;
		}
		Transform val2 = b;
		while ((Object)(object)val2 != (Object)null)
		{
			if (hashSet.Contains(val2))
			{
				return val2;
			}
			val2 = val2.parent;
		}
		return null;
	}

	private static Transform BranchUnder(Transform ancestor, Transform node)
	{
		Transform val = node;
		while ((Object)(object)val != (Object)null && (Object)(object)val.parent != (Object)(object)ancestor)
		{
			val = val.parent;
		}
		return val;
	}
}