using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("BetterCombatText")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Contorno/halo suave e legibilidade para o texto de combate (nomes de inimigos, rotulos de buff/debuff e o texto do dado nos eventos). Sem alteracao de gameplay.")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("BetterCombatText")]
[assembly: AssemblyTitle("BetterCombatText")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace BetterCombatText;
internal sealed class Configuracao
{
public readonly ConfigEntry<bool> Ativar;
public readonly ConfigEntry<bool> LogDetalhado;
public readonly ConfigEntry<bool> DiagnosticoArranque;
public readonly EstiloTmpCfg Nomes;
public readonly EstiloTextoLegadoCfg Rotulos;
public readonly EstiloTmpCfg EventosDado;
public readonly ConfigEntry<bool> EventosAtivar;
public readonly EstiloTmpCfg NumeroDeVida;
public readonly ConfigEntry<bool> AplicarNumeroDeVida;
public readonly EstiloTmpCfg TooltipStatus;
public readonly ConfigEntry<bool> TooltipAtivar;
public Configuracao(ConfigFile cfg)
{
Ativar = cfg.Bind<bool>("1. Geral", "Ativar", true, "CHAVE MESTRA. Coloque false para o mod nao alterar NADA (nem material, nem fonte, nem contorno). E a unica linha que voce precisa mudar para desligar tudo.");
LogDetalhado = cfg.Bind<bool>("1. Geral", "LogDetalhado", false, "true = escreve uma linha no LogOutput.log a cada superficie tratada (util para diagnostico). false = so o resumo de arranque e os avisos.");
DiagnosticoArranque = cfg.Bind<bool>("1. Geral", "DiagnosticoNoArranque", true, "true (padrao) = UMA vez no arranque o mod procura os componentes alvo em cena e escreve no log a fonte, o material compartilhado e o shader de cada um — 100% leitura, nada e alterado. E por esse bloco que se sabe o que o jogo usa de verdade. Custo: uma varredura de cena por passo (5s) ate achar um alvo ou ate 240s. false = nenhuma varredura de cena acontece (custo zero) e o log so registra que o diagnostico esta desligado. Ligue para testar, desligue para jogar sem ele.");
string sec = "2. Nomes de inimigos (combate)";
Nomes = new EstiloTmpCfg(cfg, sec, haloAtivo: true, 0.1f, 0.6f, "000000", 0.1f, sombraAtiva: true, 0.1f, -0.1f, 0.1f, 0.5f, 0.35f, 0f);
string sec2 = "3. Rotulos de buff/debuff (combate)";
Rotulos = new EstiloTextoLegadoCfg(cfg, sec2, haloAtivo: true, 1f, "000000", 0.1f, sombraAtiva: true, 1f, -1f, 0.5f, negrito: true, 0f);
string text = "4. Eventos (texto do dado)";
EventosAtivar = cfg.Bind<bool>(text, "Ativar", true, "Liga/desliga SO o realce do texto do dado nos eventos de rolagem. Independente do resto: desligar aqui nao mexe no combate.");
EventosDado = new EstiloTmpCfg(cfg, text, haloAtivo: true, 0.12f, 0.6f, "000000", 0.35f, sombraAtiva: true, 0.1f, -0.1f, 0.1f, 0.5f, 0.4f, 0f);
string text2 = "5. Extra";
AplicarNumeroDeVida = cfg.Bind<bool>(text2, "AplicarNoNumeroDeVida", false, "Opcional: aplica o mesmo halo no numero de vida que flutua sobre as unidades em combate (l.210529 Healthbar.healthbarText). Desligado por padrao — fora do pedido original.");
NumeroDeVida = new EstiloTmpCfg(cfg, text2 + " (numero de vida)", haloAtivo: true, 0.1f, 0.6f, "000000", 0.1f, sombraAtiva: false, 0.1f, -0.1f, 0.1f, 0.5f, 0.3f, 0f);
string text3 = "6. Tooltip de status (nome do buff/debuff)";
TooltipAtivar = cfg.Bind<bool>(text3, "Ativar", false, "DESLIGADO por padrao. O nome do buff/debuff nao tem rotulo proprio em combate: ele so aparece no tooltip generico do jogo, e o componente que o desenha (Tooltip.Title/Description) e o MESMO dos tooltips de skill/item/powerup. Ligando, o halo tambem aparece nesses tooltips. Com false, nada e tocado.");
TooltipStatus = new EstiloTmpCfg(cfg, text3, haloAtivo: true, 0.1f, 0.6f, "000000", 0.1f, sombraAtiva: true, 0.1f, -0.1f, 0.1f, 0.5f, 0.35f, 0f);
}
internal static Color HexComAlfa(string hex, float alfa)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
Color result = Color.black;
try
{
string text = (hex ?? "").Trim().TrimStart('#');
if (text.Length == 6)
{
int num = Convert.ToInt32(text.Substring(0, 2), 16);
int num2 = Convert.ToInt32(text.Substring(2, 2), 16);
int num3 = Convert.ToInt32(text.Substring(4, 2), 16);
result = new Color((float)num / 255f, (float)num2 / 255f, (float)num3 / 255f, 1f);
}
}
catch
{
result = Color.black;
}
result.a = Mathf.Clamp01(alfa);
return result;
}
}
internal sealed class EstiloTmpCfg
{
public readonly ConfigEntry<bool> HaloAtivo;
public readonly ConfigEntry<float> LarguraContorno;
public readonly ConfigEntry<float> SuavidadeContorno;
public readonly ConfigEntry<string> CorContornoHex;
public readonly ConfigEntry<float> AlfaContorno;
public readonly ConfigEntry<bool> SombraAtiva;
public readonly ConfigEntry<float> SombraOffsetX;
public readonly ConfigEntry<float> SombraOffsetY;
public readonly ConfigEntry<float> SombraDilate;
public readonly ConfigEntry<float> SombraSuavidade;
public readonly ConfigEntry<float> AlfaSombra;
public readonly ConfigEntry<float> TamanhoFonteExtra;
public Color CorContorno => Configuracao.HexComAlfa(CorContornoHex.Value, AlfaContorno.Value);
public Color CorSombra => Configuracao.HexComAlfa(CorContornoHex.Value, AlfaSombra.Value);
public EstiloTmpCfg(ConfigFile cfg, string sec, bool haloAtivo, float largura, float suavidade, string corHex, float alfa, bool sombraAtiva, float sombraOffX, float sombraOffY, float sombraDilate, float sombraSuavidade, float alfaSombra, float tamanhoExtra)
{
HaloAtivo = cfg.Bind<bool>(sec, "Halo", haloAtivo, "Contorno/halo em volta das letras (material do TextMeshPro). Desligue para nao tocar no contorno.");
LarguraContorno = cfg.Bind<float>(sec, "LarguraContorno", largura, "Espessura do contorno, em unidades de fonte SDF (_OutlineWidth). Faixa util: 0 a 0.5. 0.10 = fino; 0.25 = grosso.");
SuavidadeContorno = cfg.Bind<float>(sec, "SuavidadeContorno", suavidade, "Borramento do contorno (_OutlineSoftness). ALTO = halo suave; baixo (0) = contorno duro. 0.5-0.8 da o efeito de 'sombreamento radial' pedido.");
CorContornoHex = cfg.Bind<string>(sec, "CorContorno", corHex, "Cor do contorno no formato RRGGBB (sem #). Padrao 000000 = preto.");
AlfaContorno = cfg.Bind<float>(sec, "AlfaContorno", alfa, "Opacidade do contorno, 0 a 1. PADRAO 0.10 = os 10% pedidos pelo dono.");
SombraAtiva = cfg.Bind<bool>(sec, "Sombra", sombraAtiva, "Sombra difusa (_Underlay, o 'drop shadow' do TextMeshPro). Ligada por padrao para reforcar a leitura.");
SombraOffsetX = cfg.Bind<float>(sec, "SombraOffsetX", sombraOffX, "Deslocamento X da sombra (-1 a 1).");
SombraOffsetY = cfg.Bind<float>(sec, "SombraOffsetY", sombraOffY, "Deslocamento Y da sombra (-1 a 1).");
SombraDilate = cfg.Bind<float>(sec, "SombraDilate", sombraDilate, "Engrossamento da sombra (_UnderlayDilate).");
SombraSuavidade = cfg.Bind<float>(sec, "SombraSuavidade", sombraSuavidade, "Borramento da sombra (_UnderlaySoftness).");
AlfaSombra = cfg.Bind<float>(sec, "AlfaSombra", alfaSombra, "Opacidade da sombra, 0 a 1.");
TamanhoFonteExtra = cfg.Bind<float>(sec, "TamanhoFonteExtra", tamanhoExtra, "Soma no tamanho da fonte. 0 = NAO MEXE no tamanho (padrao). Valores tipicos: +0.5 a +2.");
}
}
internal sealed class EstiloTextoLegadoCfg
{
public readonly ConfigEntry<bool> HaloAtivo;
public readonly ConfigEntry<float> RaioContorno;
public readonly ConfigEntry<string> CorContornoHex;
public readonly ConfigEntry<float> AlfaContorno;
public readonly ConfigEntry<bool> SombraAtiva;
public readonly ConfigEntry<float> SombraOffsetX;
public readonly ConfigEntry<float> SombraOffsetY;
public readonly ConfigEntry<float> AlfaSombra;
public readonly ConfigEntry<bool> Negrito;
public readonly ConfigEntry<float> TamanhoExtra;
public Color CorContorno => Configuracao.HexComAlfa(CorContornoHex.Value, AlfaContorno.Value);
public Color CorSombra => Configuracao.HexComAlfa(CorContornoHex.Value, AlfaSombra.Value);
public EstiloTextoLegadoCfg(ConfigFile cfg, string sec, bool haloAtivo, float raio, string corHex, float alfa, bool sombraAtiva, float sombraOffX, float sombraOffY, float alfaSombra, bool negrito, float tamanhoExtra)
{
HaloAtivo = cfg.Bind<bool>(sec, "Halo", haloAtivo, "Contorno duro (componente Outline do Unity) nos rotulos 'x3' e no contador de turnos. ATENCAO: este texto e UI.Text legado, NAO tem fonte distance field — o contorno aqui e duro (4 copias), nunca suave. Para halo suave nestes rotulos seria preciso trocar os componentes por TextMeshPro (mudanca grande, fora do escopo deste mod).");
RaioContorno = cfg.Bind<float>(sec, "RaioContorno", raio, "Distancia do contorno duro, em pixels da UI. 1.0 e discreto; 2.0 e bem visivel.");
CorContornoHex = cfg.Bind<string>(sec, "CorContorno", corHex, "Cor do contorno no formato RRGGBB (sem #). Padrao 000000 = preto.");
AlfaContorno = cfg.Bind<float>(sec, "AlfaContorno", alfa, "Opacidade de CADA uma das 4 copias do contorno duro (0 a 1). PADRAO 0.10 = os 10% pedidos. Como o contorno duro nao borra, se ficar fraco demais suba para 0.25-0.40.");
SombraAtiva = cfg.Bind<bool>(sec, "Sombra", sombraAtiva, "Sombra dura atras do rotulo (componente Shadow). E o que mais ajuda a ler em fundo claro.");
SombraOffsetX = cfg.Bind<float>(sec, "SombraOffsetX", sombraOffX, "Deslocamento X da sombra, em pixels.");
SombraOffsetY = cfg.Bind<float>(sec, "SombraOffsetY", sombraOffY, "Deslocamento Y da sombra, em pixels.");
AlfaSombra = cfg.Bind<float>(sec, "AlfaSombra", alfaSombra, "Opacidade da sombra, 0 a 1.");
Negrito = cfg.Bind<bool>(sec, "Negrito", negrito, "Deixa os rotulos em negrito (FontStyle.Bold). Ganho de leitura grande, custo zero.");
TamanhoExtra = cfg.Bind<float>(sec, "TamanhoFonteExtra", tamanhoExtra, "Soma no tamanho da fonte do rotulo. 0 = NAO MEXE no tamanho (padrao).");
}
}
internal static class DiagnosticoRotina
{
private static bool _feito;
private static bool _falhou;
private static bool _vidaLogada;
private static float _proximaChecagem;
private const float EsperaMinima = 20f;
private const float IntervaloChecagem = 5f;
private const float DesistirEm = 240f;
internal static void Tentar(string origem)
{
if (_feito || _falhou || !Plugin.Ativo || Plugin.Cfg == null)
{
return;
}
try
{
if (!_vidaLogada)
{
_vidaLogada = true;
Plugin.Log.LogInfo((object)("Better Combat Text: gatilho '" + origem + "' vivo (primeira chamada) — o patch esta rodando."));
}
if (!Plugin.Cfg.DiagnosticoArranque.Value)
{
_feito = true;
Plugin.Log.LogInfo((object)"Better Combat Text: diagnostico de arranque DESLIGADO no config ('1. Geral' > DiagnosticoNoArranque = false) — nenhuma varredura de cena sera feita.");
return;
}
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (!(realtimeSinceStartup < 20f) && !(realtimeSinceStartup < _proximaChecagem))
{
_proximaChecagem = realtimeSinceStartup + 5f;
bool flag = TemAlvoEmCena();
if (flag || !(realtimeSinceStartup < 240f))
{
_feito = true;
Plugin.Log.LogInfo((object)(flag ? "Better Combat Text: alvo encontrado em cena — diagnostico encerrado (nada mais e varrido)." : ($"Better Combat Text: nenhum componente alvo apareceu em cena ate {240f:0}s — " + "rodando o diagnostico uma unica vez (o inventario de cena ainda informa) e parando.")));
Rodar();
}
}
}
catch (Exception ex)
{
_falhou = true;
Plugin.Log.LogWarning((object)("Better Combat Text: diagnostico de arranque falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
private static bool TemAlvoEmCena()
{
try
{
return Resources.FindObjectsOfTypeAll<PlayerInfoWindow>().Length != 0 || Resources.FindObjectsOfTypeAll<BossHealthbar>().Length != 0 || Resources.FindObjectsOfTypeAll<StatusIcon>().Length != 0 || Resources.FindObjectsOfTypeAll<DiceRoller>().Length != 0 || Resources.FindObjectsOfTypeAll<DiceVisualSetup>().Length != 0;
}
catch
{
return true;
}
}
private static void Rodar()
{
Plugin.Log.LogInfo((object)"Better Combat Text: --- diagnostico de arranque (somente leitura, nada alterado) ---");
ReportarTmp<PlayerInfoWindow>("PlayerInfoWindow.playerName (NOME DO INIMIGO no hover em combate)", Resources.FindObjectsOfTypeAll<PlayerInfoWindow>(), (Func<PlayerInfoWindow, TMP_Text>)((PlayerInfoWindow w) => (TMP_Text)(object)((!((Object)(object)w != (Object)null)) ? null : w.playerName)));
ReportarTmp<BossHealthbar>("BossHealthbar.BossName (NOME DO INIMIGO na barra de chefe)", Resources.FindObjectsOfTypeAll<BossHealthbar>(), (Func<BossHealthbar, TMP_Text>)((BossHealthbar b) => (TMP_Text)(object)((!((Object)(object)b != (Object)null)) ? null : b.BossName)));
ReportarTmp<Tooltip>("Tooltip.Title (nome do buff/debuff no tooltip, default DESLIGADO no config)", Resources.FindObjectsOfTypeAll<Tooltip>(), (Func<Tooltip, TMP_Text>)((Tooltip t) => (TMP_Text)(object)((!((Object)(object)t != (Object)null)) ? null : t.Title)));
ReportarTmp<DiceRoller>("DiceRoller.ResultText (resultado do dado nos eventos)", Resources.FindObjectsOfTypeAll<DiceRoller>(), (Func<DiceRoller, TMP_Text>)((DiceRoller d) => (TMP_Text)(object)((!((Object)(object)d != (Object)null)) ? null : d.ResultText)));
DiceVisualSetup[] array = Resources.FindObjectsOfTypeAll<DiceVisualSetup>();
int num = 0;
TMP_Text val = null;
for (int num2 = 0; num2 < array.Length; num2++)
{
TextMeshPro[] array2 = (((Object)(object)array[num2] != (Object)null) ? array[num2].DiceNumbers : null);
if (array2 != null)
{
num += array2.Length;
if ((Object)(object)val == (Object)null && array2.Length != 0)
{
val = (TMP_Text)(object)array2[0];
}
}
}
if ((Object)(object)val != (Object)null)
{
Descrever("DiceVisualSetup.DiceNumbers (NUMERO NA FACE DO DADO)", val);
}
Plugin.Log.LogInfo((object)$"Better Combat Text: faces de dado (TextMeshPro) encontradas: {num} em {array.Length} DiceVisualSetup.");
StatusIcon[] array3 = Resources.FindObjectsOfTypeAll<StatusIcon>();
int num3 = 0;
string text = "(nenhum componente de stack encontrado em cena)";
foreach (StatusIcon val2 in array3)
{
if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.stackCount == (Object)null))
{
num3++;
text = ((object)val2.stackCount).GetType().FullName;
}
}
Plugin.Log.LogInfo((object)$"Better Combat Text: StatusIcon em cena: {array3.Length} ({num3} com stackCount).");
Plugin.Log.LogInfo((object)("Better Combat Text: tipo REAL do rotulo de stack = '" + text + "' — " + (text.Contains("UnityEngine.UI.Text") ? "UI.Text LEGADO: halo SUAVE e impossivel ali; aplicado contorno duro + sombra + negrito." : "TextMeshPro: halo suave viavel.")));
TextMesh[] array4 = Resources.FindObjectsOfTypeAll<TextMesh>();
Plugin.Log.LogInfo((object)("Better Combat Text: TextMesh legado (3D) em cena: " + array4.Length + " — esse tipo (usado por DieSideAwareTextDie) NAO e tocado pelo mod; se a face do dado do seu build usar este tipo, o numero do dado fica sem efeito (nada quebra)."));
TMP_Text[] array5 = Resources.FindObjectsOfTypeAll<TMP_Text>();
Plugin.Log.LogInfo((object)$"Better Combat Text: total de TMP_Text em cena: {array5.Length} (so os alvos sao tocados).");
Plugin.Log.LogInfo((object)"Better Combat Text: --- fim do diagnostico ---");
}
private static void ReportarTmp<T>(string rotulo, T[] donos, Func<T, TMP_Text> seletor) where T : Object
{
try
{
if (donos == null || donos.Length == 0)
{
Plugin.Log.LogInfo((object)("Better Combat Text: " + rotulo + ": nenhuma instancia em cena agora (normal antes de abrir a tela correspondente)."));
return;
}
TMP_Text val = null;
for (int i = 0; i < donos.Length; i++)
{
if (!((Object)(object)val == (Object)null))
{
break;
}
if (!((Object)(object)donos[i] == (Object)null))
{
try
{
val = seletor(donos[i]);
}
catch
{
}
}
}
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogInfo((object)("Better Combat Text: " + rotulo + ": componente ainda nao preenchido pelo jogo."));
}
else
{
Descrever(rotulo, val);
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: diagnostico de '" + rotulo + "' falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
private static void Descrever(string rotulo, TMP_Text alvo)
{
Material fontSharedMaterial = alvo.fontSharedMaterial;
bool flag = (Object)(object)fontSharedMaterial != (Object)null && fontSharedMaterial.HasProperty("_OutlineWidth");
Plugin.Log.LogInfo((object)("Better Combat Text: " + rotulo + " -> objeto='" + ((Object)((Component)alvo).gameObject).name + "', tipo=" + ((object)alvo).GetType().Name + ", fonte='" + (((Object)(object)alvo.font != (Object)null) ? ((Object)alvo.font).name : "(nula)") + "', material compartilhado='" + (((Object)(object)fontSharedMaterial != (Object)null) ? ((Object)fontSharedMaterial).name : "(nulo)") + "', shader='" + (((Object)(object)fontSharedMaterial != (Object)null && (Object)(object)fontSharedMaterial.shader != (Object)null) ? ((Object)fontSharedMaterial.shader).name : "(nulo)") + "', " + $"distance field (tem _OutlineWidth)={flag}"));
}
}
[HarmonyPatch(typeof(GUIManager), "Update", new Type[] { })]
internal static class GatilhoDiagnosticoPorFrame
{
private static void Postfix()
{
DiagnosticoRotina.Tentar("GUIManager.Update");
}
}
[HarmonyPatch(typeof(OptionsManager), "Localize")]
internal static class GatilhoDiagnosticoPorLocalize
{
private static void Postfix()
{
DiagnosticoRotina.Tentar("OptionsManager.Localize");
}
}
[HarmonyPatch(typeof(BossHealthbar), "set_BossCharacter")]
internal static class PatchNomeDoChefe
{
private static void Postfix(BossHealthbar __instance)
{
if (!Plugin.Ativo)
{
return;
}
try
{
TextStyler.AplicarTmp((TMP_Text)(object)__instance.BossName, Plugin.Cfg.Nomes, "Nomes (combate)");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: nome do chefe falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
}
[HarmonyPatch(typeof(PlayerInfoWindow), "ShowPlayerInfoWindow")]
internal static class PatchHoverEmCombate
{
private static void Postfix(PlayerInfoWindow __instance)
{
if (!Plugin.Ativo)
{
return;
}
try
{
TextStyler.AplicarTmp((TMP_Text)(object)__instance.playerName, Plugin.Cfg.Nomes, "Nomes (combate)");
AplicarStatusIcons(__instance.statusIcons);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: hover de combate falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
internal static void AplicarStatusIcons(StatusIcon[] icones)
{
if (icones != null)
{
for (int i = 0; i < icones.Length; i++)
{
PatchRotulosDeStatus.Aplicar(icones[i]);
}
}
}
}
[HarmonyPatch(typeof(StatusIcon), "UpdateStatusIcon", new Type[] { })]
internal static class PatchRotulosDeStatus
{
private static void Postfix(StatusIcon __instance)
{
Aplicar(__instance);
}
internal static void Aplicar(StatusIcon icone)
{
if (!Plugin.Ativo || (Object)(object)icone == (Object)null)
{
return;
}
try
{
EstiloTextoLegadoCfg rotulos = Plugin.Cfg.Rotulos;
TextStyler.AplicarTextoLegado(icone.stackCount, rotulos, "Rotulos (combate)");
TextStyler.AplicarTextoLegado(icone.turnCount, rotulos, "Rotulos (combate)");
TextStyler.ReportarResumoRotulos();
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: rotulo de status falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
}
[HarmonyPatch(typeof(DiceRollingManager), "RollDice", new Type[]
{
typeof(int),
typeof(int)
})]
internal static class PatchDadoNaFace
{
private static void Postfix(DiceRollingManager __instance, TextMeshPro __result)
{
if (!Plugin.Ativo || !Plugin.Cfg.EventosAtivar.Value)
{
return;
}
try
{
TextStyler.AplicarTmp((TMP_Text)(object)__result, Plugin.Cfg.EventosDado, "Eventos (texto do dado)");
DiceVisualSetup[] diceSetups = __instance.DiceSetups;
if (diceSetups == null)
{
return;
}
for (int i = 0; i < diceSetups.Length; i++)
{
TextMeshPro[] array = (((Object)(object)diceSetups[i] != (Object)null) ? diceSetups[i].DiceNumbers : null);
if (array != null)
{
for (int j = 0; j < array.Length; j++)
{
TextStyler.AplicarTmp((TMP_Text)(object)array[j], Plugin.Cfg.EventosDado, "Eventos (texto do dado)");
}
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: texto do dado falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
}
[HarmonyPatch(typeof(DiceRoller), "StartRollVisuals")]
internal static class PatchResultadoDoDado
{
private static void Postfix(DiceRoller __instance)
{
if (!Plugin.Ativo || !Plugin.Cfg.EventosAtivar.Value)
{
return;
}
try
{
TextStyler.AplicarTmp((TMP_Text)(object)__instance.TargetText, Plugin.Cfg.EventosDado, "Eventos (texto do dado)");
TextStyler.AplicarTmp((TMP_Text)(object)__instance.ResultText, Plugin.Cfg.EventosDado, "Eventos (texto do dado)");
TextStyler.AplicarTmp((TMP_Text)(object)__instance.ModifierValueText, Plugin.Cfg.EventosDado, "Eventos (texto do dado)");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: resultado do dado falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
}
[HarmonyPatch(typeof(Tooltip), "ShowActionStatusTooltip", new Type[] { typeof(StatusIcon) })]
internal static class PatchTooltipDeStatus
{
private static void Postfix(Tooltip __instance)
{
if (!Plugin.Ativo || !Plugin.Cfg.TooltipAtivar.Value)
{
return;
}
try
{
TextStyler.AplicarTmp((TMP_Text)(object)__instance.Title, Plugin.Cfg.TooltipStatus, "Tooltip de status");
TextStyler.AplicarTmp((TMP_Text)(object)__instance.Description, Plugin.Cfg.TooltipStatus, "Tooltip de status");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: tooltip de status falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
}
[HarmonyPatch(typeof(Healthbar), "Update", new Type[] { })]
internal static class PatchNumeroDeVida
{
private static void Postfix(Healthbar __instance)
{
if (!Plugin.Ativo || !Plugin.Cfg.AplicarNumeroDeVida.Value)
{
return;
}
try
{
TextStyler.AplicarTmp((TMP_Text)(object)__instance.healthbarText, Plugin.Cfg.NumeroDeVida, "Numero de vida (extra)");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: numero de vida falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
}
[BepInPlugin("com.gumatos.bettercombattext", "Better Combat Text", "0.1.0")]
public class Plugin : BaseUnityPlugin
{
public const string Guid = "com.gumatos.bettercombattext";
public const string Versao = "0.1.0";
internal static bool Ativo;
internal static ManualLogSource Log { get; private set; }
internal static Configuracao Cfg { get; private set; }
private void Awake()
{
Log = ((BaseUnityPlugin)this).Logger;
Log.LogInfo((object)"Better Combat Text 0.1.0 carregado (GUID com.gumatos.bettercombattext).");
try
{
Cfg = new Configuracao(((BaseUnityPlugin)this).Config);
}
catch (Exception ex)
{
Log.LogError((object)("Better Combat Text: falha ao ler o config — o mod NAO vai alterar nada. " + ex.GetType().Name + ": " + ex.Message));
return;
}
if (!Cfg.Ativar.Value)
{
Ativo = false;
Log.LogInfo((object)"Better Combat Text: DESLIGADO no config (secao '1. Geral', chave 'Ativar' = false). Nenhum patch foi aplicado — o jogo roda 100% original.");
return;
}
Ativo = true;
if (AplicarPatches() == 0)
{
Ativo = false;
Log.LogError((object)"Better Combat Text: NENHUM gancho foi aplicado — o mod fica inerte nesta sessao (o jogo roda original; o motivo esta nas linhas acima).");
return;
}
try
{
Log.LogInfo((object)("Better Combat Text: alvos dos ganchos (por TIPO, sem parametro posicional): BossHealthbar.set_BossCharacter e PlayerInfoWindow.ShowPlayerInfoWindow (nomes de inimigo), StatusIcon.UpdateStatusIcon (rotulos/stacks de buff e debuff), DiceRollingManager.RollDice e DiceRoller.StartRollVisuals (texto do dado nos eventos)" + (Cfg.AplicarNumeroDeVida.Value ? " + Healthbar.Update (numero de vida, opcional ligado)" : "") + ". Os efeitos aparecem quando cada superficie e escrita pelo jogo — abra um combate/evento para ver."));
Log.LogInfo((object)("Better Combat Text: contorno cor=" + Cfg.Nomes.CorContornoHex.Value + " " + $"alfa={Cfg.Nomes.AlfaContorno.Value:0.###} largura={Cfg.Nomes.LarguraContorno.Value:0.###} " + $"softness={Cfg.Nomes.SuavidadeContorno.Value:0.###} sombra={Cfg.Nomes.SombraAtiva.Value}; " + $"eventos(dado) ativo={Cfg.EventosAtivar.Value}; numero de vida={Cfg.AplicarNumeroDeVida.Value}; " + $"diagnostico no arranque={Cfg.DiagnosticoArranque.Value}."));
Log.LogInfo((object)"Better Combat Text: config em BepInEx/config/com.gumatos.bettercombattext.cfg — para desligar TUDO basta 'Ativar = false' na secao '1. Geral'.");
}
catch (Exception ex2)
{
Log.LogWarning((object)("Better Combat Text: falha ao escrever o resumo de config no log: " + ex2.GetType().Name + ": " + ex2.Message));
}
}
private static int 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.bettercombattext");
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)("Better Combat Text: nao deu para listar os tipos do mod — nenhum gancho aplicado: " + ex));
return 0;
}
foreach (Type type in types)
{
if (EhClasseDeGancho(type))
{
num++;
try
{
int num4 = val.CreateClassProcessor(type).Patch()?.Count ?? 0;
num3 += num4;
num2++;
Log.LogInfo((object)("Better Combat Text: gancho aplicado — " + type.Name + " (" + num4 + " metodo(s) do jogo)."));
}
catch (Exception ex2)
{
list.Add(type.Name);
Log.LogError((object)("Better Combat Text: FALHA ao aplicar o gancho " + type.Name + " — " + ex2.Message));
}
}
}
string text = "Better Combat Text: patches Harmony aplicados (" + num2 + "/" + num + " ganchos, " + num3 + " metodos do jogo).";
if (list.Count == 0)
{
Log.LogInfo((object)text);
return num2;
}
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."));
return num2;
}
private static bool EhClasseDeGancho(Type tipo)
{
try
{
if (tipo == null || !tipo.IsClass)
{
return false;
}
return tipo.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0;
}
catch (Exception)
{
return false;
}
}
}
internal static class TextStyler
{
private static readonly Dictionary<int, Material> MateriaisInstanciados = new Dictionary<int, Material>();
private static readonly Dictionary<string, string> AvisosDados = new Dictionary<string, string>();
private static readonly HashSet<int> TextosLegadosTratados = new HashSet<int>();
private static readonly Dictionary<int, float> TamanhoOriginal = new Dictionary<int, float>();
private static readonly Dictionary<int, float> TamanhoOriginalLegado = new Dictionary<int, float>();
private static bool _resumoAplicadoSobreNomes;
private static bool _resumoAplicadoSobreDado;
private static readonly HashSet<string> DetalhesDados = new HashSet<string>();
private static bool _inventarioFeito;
private static bool _resumoRotulosFeito;
internal static void AplicarTmp(TMP_Text tmp, EstiloTmpCfg cfg, string superficie)
{
//IL_0147: Unknown result type (might be due to invalid IL or missing references)
//IL_0207: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)tmp == (Object)null || (!cfg.HaloAtivo.Value && !cfg.SombraAtiva.Value && cfg.TamanhoFonteExtra.Value == 0f))
{
return;
}
try
{
Material fontSharedMaterial = tmp.fontSharedMaterial;
if ((Object)(object)fontSharedMaterial == (Object)null)
{
AvisoUma(superficie + ":sem-material", "'" + Nome(tmp) + "' nao tem fontSharedMaterial; ignorado.");
return;
}
int instanceID = ((Object)tmp).GetInstanceID();
if (MateriaisInstanciados.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null && (Object)(object)value == (Object)(object)fontSharedMaterial)
{
return;
}
Material fontMaterial = tmp.fontMaterial;
if ((Object)(object)fontMaterial == (Object)null)
{
AvisoUma(superficie + ":instancia-nula", "'" + Nome(tmp) + "': fontMaterial devolveu null; ignorado.");
return;
}
MateriaisInstanciados[instanceID] = fontMaterial;
Shader shader = fontMaterial.shader;
string text = (((Object)(object)shader != (Object)null) ? ((Object)shader).name : "(sem shader)");
bool flag = fontMaterial.HasProperty("_OutlineWidth");
bool flag2 = fontMaterial.HasProperty("_UnderlaySoftness");
if (cfg.HaloAtivo.Value)
{
if (flag)
{
fontMaterial.EnableKeyword("OUTLINE_ON");
fontMaterial.SetColor("_OutlineColor", cfg.CorContorno);
fontMaterial.SetFloat("_OutlineWidth", Mathf.Clamp(cfg.LarguraContorno.Value, 0f, 1f));
fontMaterial.SetFloat("_OutlineSoftness", Mathf.Clamp(cfg.SuavidadeContorno.Value, 0f, 1f));
}
else
{
AvisoUma(superficie + ":bitmap", "'" + Nome(tmp) + "' usa o shader '" + text + "', que NAO tem _OutlineWidth (fonte bitmap, nao distance field). Halo suave IMPOSSIVEL nesta fonte; nada foi alterado nela.");
}
}
if (cfg.SombraAtiva.Value)
{
if (flag2)
{
fontMaterial.EnableKeyword("UNDERLAY_ON");
fontMaterial.SetColor("_UnderlayColor", cfg.CorSombra);
fontMaterial.SetFloat("_UnderlayOffsetX", Mathf.Clamp(cfg.SombraOffsetX.Value, -2f, 2f));
fontMaterial.SetFloat("_UnderlayOffsetY", Mathf.Clamp(cfg.SombraOffsetY.Value, -2f, 2f));
fontMaterial.SetFloat("_UnderlayDilate", Mathf.Clamp(cfg.SombraDilate.Value, -1f, 1f));
fontMaterial.SetFloat("_UnderlaySoftness", Mathf.Clamp(cfg.SombraSuavidade.Value, 0f, 1f));
}
else
{
AvisoUma(superficie + ":sem-underlay", "'" + Nome(tmp) + "' / shader '" + text + "' nao expoe _UnderlaySoftness; sombra ignorada.");
}
}
AplicarTamanho(tmp, cfg.TamanhoFonteExtra.Value, instanceID);
tmp.UpdateMeshPadding();
((Graphic)tmp).SetVerticesDirty();
((Graphic)tmp).SetMaterialDirty();
if (Plugin.Cfg.LogDetalhado.Value)
{
Plugin.Log.LogInfo((object)("[" + superficie + "] '" + Nome(tmp) + "' tratado: shader='" + text + "' " + string.Format("df={0} halo={1} ", flag, cfg.HaloAtivo.Value ? Part(cfg.LarguraContorno) : "off") + "softness=" + Part(cfg.SuavidadeContorno) + " alfa=" + Part(cfg.AlfaContorno) + " sombra=" + (cfg.SombraAtiva.Value ? Part(cfg.AlfaSombra) : "off")));
}
ReportarResumo(superficie, text, cfg, flag);
DetalharAlvo(superficie, tmp, fontSharedMaterial, text, flag);
}
catch (Exception ex)
{
AvisoUma(superficie + ":erro", "falha ao tratar '" + Nome(tmp) + "': " + ex.GetType().Name + ": " + ex.Message);
}
}
private static void ReportarResumo(string superficie, string nomeShader, EstiloTmpCfg cfg, bool df)
{
if (!(superficie.Contains("dado") ? _resumoAplicadoSobreDado : _resumoAplicadoSobreNomes))
{
if (superficie.Contains("dado"))
{
_resumoAplicadoSobreDado = true;
}
else
{
_resumoAplicadoSobreNomes = true;
}
Plugin.Log.LogInfo((object)("[" + superficie + "] APLICADO em '" + superficie + "' (material instanciado POR COMPONENTE): " + $"shader='{nomeShader}', distance field={df}, contorno={Part(cfg.LarguraContorno)} " + "largura / " + Part(cfg.SuavidadeContorno) + " softness / cor " + cfg.CorContornoHex.Value + " alfa " + Part(cfg.AlfaContorno) + ", sombra=" + (cfg.SombraAtiva.Value ? "on" : "off") + " tamanho=" + ((cfg.TamanhoFonteExtra.Value == 0f) ? "intocado" : Part(cfg.TamanhoFonteExtra)) + "."));
}
}
private static void AplicarTamanho(TMP_Text tmp, float extra, int id)
{
if (extra != 0f)
{
if (!TamanhoOriginal.TryGetValue(id, out var value))
{
value = tmp.fontSize;
TamanhoOriginal[id] = value;
}
tmp.fontSize = value + extra;
}
}
internal static void AplicarTextoLegado(Text txt, EstiloTextoLegadoCfg cfg, string superficie)
{
//IL_018d: Unknown result type (might be due to invalid IL or missing references)
//IL_0193: 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_016c: Unknown result type (might be due to invalid IL or missing references)
//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)txt == (Object)null || (!cfg.HaloAtivo.Value && !cfg.SombraAtiva.Value && !cfg.Negrito.Value && cfg.TamanhoExtra.Value == 0f))
{
return;
}
int instanceID = ((Object)txt).GetInstanceID();
try
{
if (TextosLegadosTratados.Contains(instanceID))
{
return;
}
TextosLegadosTratados.Add(instanceID);
Graphic[] components = ((Component)txt).gameObject.GetComponents<Graphic>();
bool flag = components == null || components.Length == 0 || (Object)(object)components[0] == (Object)(object)txt;
if (cfg.HaloAtivo.Value)
{
if (flag)
{
Outline val = ((Component)txt).gameObject.GetComponent<Outline>();
if ((Object)(object)val == (Object)null)
{
val = ((Component)txt).gameObject.AddComponent<Outline>();
}
((Shadow)val).effectColor = cfg.CorContorno;
((Shadow)val).effectDistance = new Vector2(cfg.RaioContorno.Value, cfg.RaioContorno.Value);
((Shadow)val).useGraphicAlpha = true;
}
else
{
AvisoUma("rotulos:graphic-antes", "'" + ((Object)((Component)txt).gameObject).name + "': ha outro Graphic antes do Text no mesmo objeto; contorno NAO adicionado (mexeria no elemento errado).");
}
}
if (cfg.SombraAtiva.Value && flag)
{
Shadow val2 = ((Component)txt).gameObject.GetComponent<Shadow>();
if ((Object)(object)val2 == (Object)null)
{
val2 = ((Component)txt).gameObject.AddComponent<Shadow>();
}
val2.effectColor = cfg.CorSombra;
val2.effectDistance = new Vector2(cfg.SombraOffsetX.Value, cfg.SombraOffsetY.Value);
val2.useGraphicAlpha = true;
}
if (cfg.Negrito.Value)
{
txt.fontStyle = (FontStyle)(txt.fontStyle | 1);
}
if (cfg.TamanhoExtra.Value != 0f)
{
if (!TamanhoOriginalLegado.TryGetValue(instanceID, out var value))
{
value = txt.fontSize;
TamanhoOriginalLegado[instanceID] = value;
}
txt.fontSize = Mathf.Max(1, Mathf.RoundToInt(value + cfg.TamanhoExtra.Value));
}
if (Plugin.Cfg.LogDetalhado.Value)
{
Plugin.Log.LogInfo((object)("[" + superficie + "] '" + ((Object)((Component)txt).gameObject).name + "' tratado (UI.Text legado): contorno=" + (cfg.HaloAtivo.Value ? Part(cfg.RaioContorno) : "off") + "px alfa=" + Part(cfg.AlfaContorno) + ", " + string.Format("sombra={0}, negrito={1}", cfg.SombraAtiva.Value ? Part(cfg.AlfaSombra) : "off", cfg.Negrito.Value)));
}
}
catch (Exception ex)
{
AvisoUma(superficie + ":erro", "falha ao tratar '" + ((Object)((Component)txt).gameObject).name + "': " + ex.GetType().Name + ": " + ex.Message);
}
}
internal static void ReportarResumoRotulos()
{
if (!_resumoRotulosFeito)
{
_resumoRotulosFeito = true;
EstiloTextoLegadoCfg rotulos = Plugin.Cfg.Rotulos;
Plugin.Log.LogInfo((object)("[Rotulos de buff/debuff em combate] APLICADO nos rotulos 'xN' e contador de turnos dos icones de status (props 100% locais do objeto, nada compartilhado): contorno duro " + (rotulos.HaloAtivo.Value ? (Part(rotulos.RaioContorno) + "px") : "off") + " cor " + rotulos.CorContornoHex.Value + " alfa " + Part(rotulos.AlfaContorno) + " (4 copias), sombra=" + (rotulos.SombraAtiva.Value ? "on" : "off") + ", " + string.Format("negrito={0}, tamanho={1}. ", rotulos.Negrito.Value, (rotulos.TamanhoExtra.Value == 0f) ? "intocado" : Part(rotulos.TamanhoExtra)) + "Obs.: este texto e UI.Text legado (sem distance field) — halo SUAVE nao se aplica aqui."));
}
}
private static void DetalharAlvo(string superficie, TMP_Text tmp, Material compartilhadoAntes, string nomeShader, bool df)
{
if (DetalhesDados.Add(superficie))
{
try
{
string text = (((Object)(object)tmp.font != (Object)null) ? ((Object)tmp.font).name : "(nula)");
string text2 = (((Object)(object)compartilhadoAntes != (Object)null) ? ((Object)compartilhadoAntes).name : "(nulo)");
bool flag = text2.Contains("(Instance)");
Plugin.Log.LogInfo((object)("[" + superficie + "] alvo: objeto='" + Nome(tmp) + "', componente=" + ((object)tmp).GetType().Name + ", fonte TMP='" + text + "', material compartilhado antes de clonar='" + text2 + "'" + (flag ? " (ja era instancia de outro mod — clonamos por cima)" : " (compartilhado do prefab)") + ", " + $"shader='{nomeShader}', tem _OutlineWidth (distance field/SDF)={df}"));
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: log de detalhe falhou: " + ex.GetType().Name + ": " + ex.Message));
}
InventarioDeCena();
}
}
private static void InventarioDeCena()
{
if (_inventarioFeito)
{
return;
}
_inventarioFeito = true;
try
{
TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
TextMesh[] array2 = Resources.FindObjectsOfTypeAll<TextMesh>();
Plugin.Log.LogInfo((object)($"Better Combat Text: inventario de cena — {array.Length} TMP_Text (TextMeshPro/UGUI, " + $"elegiveis ao halo suave) e {array2.Length} TextMesh legado (3D, usado por " + "DieSideAwareTextDie — NAO e tocado pelo mod)."));
Dictionary<string, int> dictionary = new Dictionary<string, int>();
foreach (TMP_Text val in array)
{
if (!((Object)(object)val == (Object)null))
{
Material val2 = null;
try
{
val2 = val.fontSharedMaterial;
}
catch
{
}
string key = (((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "(sem material)");
dictionary.TryGetValue(key, out var value);
dictionary[key] = value + 1;
}
}
int num = 0;
foreach (KeyValuePair<string, int> item in dictionary)
{
if (num++ >= 10)
{
Plugin.Log.LogInfo((object)$"Better Combat Text: ... e mais {dictionary.Count - 10} materiais distintos.");
break;
}
Plugin.Log.LogInfo((object)$"Better Combat Text: material de fonte em uso: '{item.Key}' x{item.Value}");
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Better Combat Text: inventario de cena falhou: " + ex.GetType().Name + ": " + ex.Message));
}
}
private static string Nome(TMP_Text t)
{
try
{
return ((Object)(object)t != (Object)null && (Object)(object)((Component)t).gameObject != (Object)null) ? ((Object)((Component)t).gameObject).name : "(?)";
}
catch
{
return "(?)";
}
}
private static string Part(float valor)
{
return valor.ToString("0.###");
}
private static string Part(ConfigEntry<float> e)
{
return e.Value.ToString("0.###");
}
private static void AvisoUma(string chave, string mensagem)
{
if (!AvisosDados.ContainsKey(chave))
{
AvisosDados[chave] = mensagem;
Plugin.Log.LogWarning((object)("Better Combat Text: " + mensagem));
}
}
}