You are viewing a potentially older version of this package. View all versions.
DefRuivo_StolenRealmMods-RoguelikeSkillTreeVisualizer-0.2.0 icon

RoguelikeSkillTreeVisualizer

Botao 'Skills' na tela Select Party e no HUD da run (ao lado do Ping Button) que abre a Skill Tree nativa em modo somente leitura, com o contexto real do personagem. Nenhum ponto e gasto.

Date uploaded a day ago
Version 0.2.0
Download link DefRuivo_StolenRealmMods-RoguelikeSkillTreeVisualizer-0.2.0.zip
Downloads 4
Dependency string DefRuivo_StolenRealmMods-RoguelikeSkillTreeVisualizer-0.2.0

This mod requires the following mods to function

BepInEx-BepInExPack-5.4.2305 icon
BepInEx-BepInExPack

BepInEx pack for Mono Unity games. Preconfigured and ready to use.

Preferred version: 5.4.2305

README

RoguelikeSkillTreeVisualizer

Status: IMPLEMENTADO (RSTV-2 + RSTV-5 + fase de diagnóstico). O botão e a árvore somente leitura estão prontos e o mod ainda não foi conferido em jogo pelo autor.

  • GUID: com.gumatos.roguelikeskilltreevisualizer
  • Versão: 0.2.0
  • Compatível com: Stolen Realm v1.3.1 (versão mais recente do jogo em 30/09/2026).
  • Dependências: nenhuma além do BepInEx 5 (que o r2modman já instala no perfil).

Este mod já é um pacote Thunderstore: a pasta tem o manifest.json, o icon.png (256×256, que o Thunderstore exige), o README.md, o CHANGELOG.md e o código:

arquivo papel
Plugin.cs [BepInPlugin], config (AtivarBotao, AtivarBotaoNaRun, AjustarZOrder) e aplicação dos ganchos um a um, com contagem real no log (o Awake NÃO cria GameObject: isso roda no chainloader)
Patches.cs os 10 ganchos Harmony, cada um com a linha do decompilado no comentário
SelectPartyButton.cs RSTV-2a: clona o roguelikePowerupButton, encolhe a linha, trata o clique e diagnostica por que o botão não entrou
RunButton.cs RSTV-5: o mesmo botão no HUD da run, ancorado no Ping Button, com o portão 4
RunTargets.cs RSTV-5: alvo do clique na run e o portão de segurança (estado do jogo)
PartyTargets.cs RSTV-2b: ordem de adição local (o jogo não guarda essa ordem)
SkillTreeReadOnly.cs RSTV-2c/2d + RSTV-5: abre a Skill Tree nativa e mantém a sessão somente leitura
RstvHost.cs MonoBehaviour criado de forma preguiçosa (Update do mod)

Seguindo o padrão dos outros mods do repositório: uma pasta na raiz, com o .csproj, o Plugin.cs e o alvo DeployToBepInEx (não existe src/, decisão registrada na PKG-1).

Alvo de compatibilidade: Stolen Realm v1.3.1 (versão mais recente do jogo em 30/09/2026) — é a versão que o manifest.json deste pacote declara.

Para quem vai usar

O que o botão faz

  • Aparece como um botão quadrado Skills, colado à direita do Choose Powerups, na mesma linha (o Choose Powerups encolhe o necessário e a linha não cresce; o Accept Party não muda).
  • RSTV-5 — o mesmo botão existe DURANTE A RUN, colado à direita do Ping Button (o botão de apontar o hex) do HUD, na mesma linha. Ele só aparece quando é seguro abrir: com o portão 4 fechado (ver Portão do botão abaixo).
  • Abre a árvore de skills do personagem certo: na tela de party, o último que VOCÊ adicionou; na run, o personagem selecionado no momento (resolvido no clique — o nível e o equipamento mostrados são os de agora, sem cache).
  • Dentro da árvore dá para navegar, dar zoom, trocar de aba e ler os tooltips com os números do seu personagem. As skills já aprendidas aparecem acesas; o resto cinza/CannotObtain.
  • Na tela de party o botão fica desabilitado quando a sua party está vazia e só aparece em modo Roguelike (segue o Choose Powerups).

Portão do botão (RSTV-5 — só vale para o botão da RUN)

O botão da run some e recusa a abertura (com o motivo no log) enquanto qualquer uma destas condições valer — em dúvida, não abre:

condição por quê
HexCellManager.CurrentState == PlayerState.Action você está mirando uma skill; abrir a árvore cancelaria o apontar hex
GUIManager.PingModeActive modo de apontar o hex ligado (o clique do ping atravessaria a janela)
Root.IsPlayerTurnAndReady == false (em batalha) não é o seu turno
Root.AnyActingCharactersInBattle / AnyMovingCharactersInBattle alguém está executando ação/andando
Root.SpawnPlacementActive posicionamento inicial
RoguelikeManager ativo level-up/reroll pendente: o jogo troca o personagem selecionado sozinho
UIWindowManager.OpenedWindows não vazio já há uma janela aberta (mochila, opções...) — não empilhar
GUIState fora de InBattle/InWorldMap/InTown estados fora da run

As checagens de turno/agindo/movendo valem só em batalha: fora dela IsPlayerTurnAndReady fica com o último valor do combate (o jogo o zera no fim do turno, l.110300) e bloquearia o botão no mapa do mundo inteiro.

O que o botão NÃO faz

  • Não gasta ponto de skill — o rodapé mostra 0 points available.
  • Não aprende nem remove skill — clicar num nó é bloqueado (read-only) e o Respec Build fica escondido.
  • Não altera o Accept Party, a Current Party, os Powerups, o GameMode nem as regras do Roguelike. Nenhum arquivo do jogo é modificado.
  • Não mistura jogadores — cada jogador usa os seus próprios personagens; o que o outro adiciona à party não muda o que você vê.

O mod não reimplementa as Skill Trees: ele resolve o personagem certo e entrega a instância real para o sistema nativo. Não há compra de skill, gasto de ponto nem qualquer escrita.

Instalar

Pelo r2modman (recomendado): instale este pacote no perfil do Stolen Realm — o r2modman coloca a DLL em BepInEx\plugins\RoguelikeSkillTreeVisualizer\.

Manual:

  1. Instale o BepInEx 5 x64 no jogo (ou dê "Start modded" uma vez no r2modman, que cria a pasta).
  2. Copie a pasta RoguelikeSkillTreeVisualizer para:
    %APPDATA%\r2modmanPlus-local\StolenRealm\profiles\Default\BepInEx\plugins\
    
    Deve ficar: ...\plugins\RoguelikeSkillTreeVisualizer\RoguelikeSkillTreeVisualizer.dll
  3. Abra o jogo. No LogOutput.log deve aparecer:
    Roguelike Skill Tree Visualizer 0.1.0 carregado (RSTV-2: botao ao lado do Choose Powerups + skill tree read-only).
    RSTV: patches Harmony aplicados (8/8 ganchos, 8 metodos do jogo).
    
    Se vier 7/8 (ou menos), o log diz qual gancho não entrou — veja Diagnóstico abaixo.
  4. Vá em Roguelike → Select Party: o botão Skills fica colado à direita do Choose Powerups.

Só funciona no modo Roguelike e o botão só aparece na tela Select Party. Não é preciso entrar em partida.

Config (opcional)

BepInEx/config/com.gumatos.roguelikeskilltreevisualizer.cfg:

seção chave padrão o que faz
Geral AtivarBotao true false = o mod carrega, registra os ganchos, mas não cria botão nenhum na tela de party
Geral AtivarBotaoNaRun true false = não cria o botão no HUD da run (o da tela de party continua normal). O portão 4 vale mesmo com true
Geral AjustarZOrder true ajusta o índice de irmão da janela nativa para a árvore ficar por cima do HUD na run. Desligue se o tooltip do jogo aparecer atrás da árvore

O padrão é o comportamento de sempre: quem não abrir o arquivo de config vê os botões ligados (com todas as guardas).

Desinstalar

  1. Feche o jogo.
  2. Apague a pasta do mod em BepInEx/plugins/RoguelikeSkillTreeVisualizer/ (no perfil do r2modman ou na instalação manual). Nada é criado fora dela.
  3. Opcional: apague BepInEx/config/com.gumatos.roguelikeskilltreevisualizer.cfg.

Desinstalar não deixa resíduo no jogo e não afeta save.

Diagnóstico — como saber o que aconteceu

O mod não falha em silêncio: cada gancho é aplicado e logado individualmente e cada caminho de falha escreve o motivo. No LogOutput.log:

linha significa
Roguelike Skill Tree Visualizer 0.2.0 carregado ... o plugin carregou
RSTV: gancho aplicado — <Classe> (N metodo(s) do jogo). um gancho entrou (uma linha por gancho)
RSTV: patches Harmony aplicados (10/10 ganchos, 10 metodos do jogo). contagem real de ganchos aplicados; se vier 9/10, o mesmo erro termina com GANCHOS QUE FALHARAM: e os nomes
RSTV: FALHA ao aplicar o gancho <Classe> — <motivo> aquele gancho não entrou (assinatura que mudou de versão, por exemplo)
RSTV DIAG: botao 'Skills' da tela Select Party HABILITADO/DESABILITADO ... o config foi lido (as duas chaves de botão)
RSTV-2: botao 'Skills' injetado (NxN px) ... largura da linha A -> B px o botão foi criado; as duas larguras devem ser iguais
RSTV DIAG: botao 'Skills' NAO injetado — <motivo> por que o botão não apareceu (botão nativo ausente, sem RectTransform, exceção, config desligado)
RSTV DIAG: a tela Select Party esta aberta e o botao 'Skills' NAO foi injetado. motivo=... resumo com o estado da tela quando a injeção falha
RSTV-2: clique no botao 'Skills' -> '<nome>' (nivel N). o clique resolveu o personagem
RSTV-5: botao 'Skills' injetado no HUD (...) a direita do Ping Button o botão da run foi criado (com a geometria da linha)
RSTV DIAG: botao 'Skills' do HUD escondido — <motivo> por que o botão da run está escondido agora (portão 4, uma linha por motivo)
RSTV DIAG: botao 'Skills' do HUD VISIVEL e clicavel — alvo '<nome>' o portão reabriu
RSTV-5: clique RECUSADO — <motivo> o clique chegou com o portão fechado (reavaliado na hora)
RSTV-5: clique no botao 'Skills' do HUD -> '<nome>' (nivel N). o clique resolveu o personagem selecionado
RSTV: skill tree read-only aberta para '<nome>' (... contexto=Run) a árvore abriu (contexto de party ou de run)
RSTV-5: o jogo JA tem '<nome>' como personagem selecionado nenhuma escrita em GameLogic.CurrentlySelectedCharacter
RSTV-5: abertura RECUSADA — o jogo tem '<A>' selecionado e o alvo e '<B>' a árvore mostraria o contexto errado: não abriu
RSTV-5: nenhum personagem estava selecionado — definindo '<nome>' o mod escreveu a seleção (valor anterior guardado e devolvido no fim)
RSTV-5: personagem selecionado devolvido ao valor de antes da abertura a blindagem 2 desfez a escrita
RSTV-5: janela registrada em UIWindowManager.OpenedWindows camada 1 do bloqueio do clique do hex (o jogo passa a considerar "menu aberto")
RSTV-5: clique no hex CONSUMIDO enquanto a arvore read-only esta aberta camada 2 (prefixo no ProcessLeftMouseClick)
RSTV-5: Esc NAO consumido — a janela do topo e '<Classe>' o Esc foi deixado para quem está por cima (mochila/opções)
RSTV-5: fechando a skill tree read-only — <motivo> a guarda de ciclo de vida fechou a árvore (tela de party fechou, saiu de InBattle/InWorldMap/InTown, o jogo trocou o personagem)
RSTV-2: clique na arvore BLOQUEADO (read-only) o clique no nó foi barrado
RSTV-2: ResetSkillPoints BLOQUEADO (read-only) o clique num botão que zeraria as skills do personagem foi barrado
RSTV: modo somente leitura encerrado (nada foi gravado no personagem) fechou limpo

O que o mod faz (técnico)

Adiciona um visualizador de Skill Trees na tela Roguelike → Select Party: um botão quadrado à direita do Choose Powerups que abre a interface de Skill Trees nativa do jogo (a mesma do Campaign → Change Skills), em modo somente leitura, com o contexto real do personagem — Level, equipamento, atributos e estado das skills atuais.

Regras que já estão decididas

regra o que significa
Personagem o último que o jogador local adicionou à Current Party — nunca o último global
Multiplayer cada jogador usa os seus próprios personagens; a ação de outro jogador não muda o meu contexto
Remoção se o último for removido, cai para o último local ainda na party; party vazia = botão desabilitado
Contexto resolvido no clique, não quando a tela abre (Level/equipamento sempre atuais, sem cache)
Read-only navegar, zoom, tooltips e requisitos continuam funcionando; nada que persista muda
Intocáveis Accept Party, a Current Party, os Powerups, o GameMode e as regras do Roguelike
UI o botão é clonado de um botão nativo (mesmo sprite/estados); a largura do Choose Powerups encolhe o necessário e a linha não cresce
Falha nenhum caminho pode falhar calado: ou o log diz por que o botão/gancho não entrou, ou o estado duvidoso é fechado

Ícone

icon.png (256×256) é o ícone de pacote do Thunderstore — arte de verdade do autor (redimensionada de uma arte 1254×1254), não um ícone genérico gerado pelo script do empacotador. É PNG válido (RGBA) e passa na checagem de 256×256 do tools/pack-thunderstore.py.

Pendente: o arquivo original de 1254×1254 não está versionado neste repositório (dos outros mods os originais ficaram em scratch/icons-orig/, mas o do RSTV não está lá). Se precisar reexportar ou cortar outra versão, a arte-fonte tem de ser pedida de novo ao autor — nada de gerar um PNG falso no lugar.

Especificação e aceite

A especificação completa (77 itens: investigação obrigatória, estratégia de injeção, rastreio de personagem, testes por caso) e os critérios de aceite estão registrados no quadro interno do projeto (não versionado).

Roteiro de conferência em jogo (RSTV-2)

  1. Compilar e instalar: dotnet build RoguelikeSkillTreeVisualizer/RoguelikeSkillTreeVisualizer.csproj (sem a propriedade DeployToBepInEx=false ele copia a DLL para o perfil do r2modman).
  2. Abrir o jogo modded e ir em Roguelike → Select Party. No LogOutput.log devem aparecer RSTV: patches Harmony aplicados (10/10 ganchos, 10 metodos do jogo) e RSTV-2: botao 'Skills' injetado ... largura da linha N -> N px (as duas larguras devem bater). Se o botão não aparecer, o log já diz o motivo (RSTV DIAG: ..., com a tela, o botão nativo e a party local) — não precisa adivinhar.
  3. Adicionar 2 personagens meus à party (clique nos tiles) → o log diz qual virou o alvo; clicar num terceiro e removê-lo → o alvo volta para o último que ficou.
  4. Clicar no botão quadrado Skills (colado à direita do Choose Powerups). Esperado: RSTV-2: skill tree read-only aberta para '<nome>' (nivel N, M skills, 0 pontos) e a árvore nativa por cima da tela de party, com as skills aprendidas acesas e o resto cinza/CannotObtain.
  5. Na árvore: passar o mouse nos nós (tooltip com os números do personagem real), trocar de aba, tentar clicar num nó → RSTV-2: clique na arvore BLOQUEADO (read-only). O botão Respec Build não aparece; o rodapé mostra 0 points available.
  6. Fechar (X ou Esc) → RSTV-2: modo somente leitura encerrado (nada foi gravado no personagem). Conferir que Accept Party, a Current Party e os Powerups continuam intactos.
  7. Repetir abrindo/fechando 3× e depois aceitar a party — a árvore não pode aparecer presa atrás nem empurrar a UI.
  8. Opcional (config): com AtivarBotao = false no .cfg, a tela abre normal e sem botão nenhum; o log diz RSTV DIAG: botao 'Skills' DESABILITADO nesta sessao.

Roteiro de conferência em jogo (RSTV-5 — o botão na run)

Nada disto foi executado ainda: o mod não foi aberto em jogo. Cada passo diz o que prova.

  1. Boot: RSTV: patches Harmony aplicados (10/10 ganchos, 10 metodos do jogo) e RSTV-5: gancho do HUD da run ATIVO (CurrentCharacterUI.InitSingleton).
  2. Entrar numa run e olhar o HUD: em batalha, turno do jogador, sem mira o log imprime RSTV-5: botao 'Skills' injetado no HUD (NxN px) a direita do Ping Button — largura da linha A -> B. Prova: o botão existe, é quadrado e a linha não cresceu (A == B).
  3. Aparência: o botão deve ter o fundo do botão nativo e o texto Skills; o ícone do ping não pode aparecer. O log diz quantos gráficos herdados foram desligados e qual é o targetGraphic. Se o rótulo não existir (Ping Button é só ícone), o log diz que o rótulo foi montado a partir de qual botão-molde (ou que ficou sem rótulo — defeito a reportar).
  4. Clicar em Skills: RSTV-5: clique no botao 'Skills' do HUD -> '<nome>' (nivel N) + RSTV-5: o jogo JA tem '<nome>' como personagem selecionado (é o caso seguro, sem escrita) + RSTV: skill tree read-only aberta para '<nome>' (... contexto=Run) + RSTV-5: janela registrada em UIWindowManager.OpenedWindows. Provas: o contexto é do personagem selecionado; a árvore aparece por cima do HUD; o clique do hex passa a ser consumido.
  5. Com a árvore aberta, clicar num hex do chão e depois num inimigo. Esperado (log): RSTV-5: clique no hex CONSUMIDO ... e nada acontece no jogo — nenhum movimento, nenhuma ação, nenhum dano. Prova da blindagem 5. Se algo acontecer, é defeito grave: reportar com o log.
  6. Com a árvore aberta, apertar Esc. Esperado: RSTV-5: Esc/B consumido + RSTV: modo somente leitura encerrado. A câmera, o personagem selecionado e o turno continuam como estavam.
  7. Portão 4: selecionar uma skill (entrar em mira) → o botão Skills some e o log diz RSTV DIAG: botao 'Skills' do HUD escondido — mira de skill ativa .... Cancelar a mira (Esc/right click) → o botão volta: RSTV DIAG: botao 'Skills' do HUD VISIVEL e clicavel — alvo '<nome>'. Repetir com: turno do inimigo (nao e o turno do jogador), personagem andando/agindo, level-up pendente (RoguelikeManager ativo) e com a mochila aberta (ja existe uma janela de UI aberta). Prova: o botão só aparece quando é seguro.
  8. Trocar de personagem com a árvore aberta (tecla de trocar personagem, se houver, ou clique no personagem não selecionado): esperado RSTV-5: fechando a skill tree read-only — o jogo trocou o personagem selecionado ... e a árvore fecha. Prova da blindagem 6.
  9. Esc com a mochila aberta por cima da árvore: abrir a mochila com a árvore aberta e apertar Esc → o log diz RSTV-5: Esc NAO consumido — a janela do topo e 'InventoryManager' e a mochila fecha (a árvore continua atrás). Prova da blindagem 3.
  10. Tooltips: com a árvore aberta na run, passar o mouse nas skills → o tooltip tem de aparecer por cima da árvore. Se aparecer atrás, desligar AjustarZOrder no .cfg e conferir de novo.
  11. Voltar ao mapa do mundo / town com a árvore aberta: a árvore pode ficar aberta (está na whitelist); cutscene fecha. Prova da blindagem 1 + whitelist.
  12. Sair do turno / fim de batalha com a árvore aberta: o GUIState muda para InWorldMap e nada quebra (a janela continua, o input volta ao jogo quando ela fechar).
  13. Config: AtivarBotaoNaRun = false → o log diz RSTV DIAG: botao 'Skills' da run desativado — AtivarBotaoNaRun=false e o HUD fica sem o botão (o da tela de party continua normal).

Riscos conhecidos (não verificáveis sem abrir o jogo)

  • O sprite/âncora/LayoutGroup da linha vivem no prefab (resources.assets, 1,7 GB, não aberto): o mod mede em runtime, compensa a largura e loga o modo de layout usado. Se o log mostrar childForceExpandWidth, a linha não cresce mas os botões vizinhos se redistribuem.
  • Z-order: a Skill Tree é instanciada sob outro ramo do canvas. O mod compara os índices de irmão e, se a árvore estiver atrás da tela de party, move o ramo para o fim e restaura ao fechar (logado).
  • GameLogic.CurrentlySelectedCharacter é ajustado para o personagem alvo (é de onde SkillTreeManager e o Tooltip leem o contexto). Isso também marca aquele personagem como IsPartyLeader e libera o lock da câmera — o mesmo que o jogo faz ao selecionar um personagem no mundo.
  • Fechamento de emergência: se a higienização do commit falhar, o mod não deixa o original rodar e fecha a janela por conta própria (zero escrita) em vez de arriscar a gravação. Só acontece num caminho de exceção, mas vale conferir que a tela de party volta ao normal depois (o log diz RSTV: falha ao higienizar AcceptSkillChanges ...).
  • Segundo caminho de gravação (achado na auditoria estática): SkillTreeManager.ResetSkillPoints() (l.173274) é público, sem parâmetro e chama Character.ResetSkills(), que zera todas as skills do personagem e enfileira o save dele (l.37474) — sem passar pelo AcceptSkillChanges. O mod agora o bloqueia em read-only (gancho 8). O que fica para medir em jogo: qual botão do prefab chama esse método (o campo resetButton, l.172057, não está ligado em código) e se existe algum botão que chame CheckSkillDependancies(Character) (l.172328), que também grava (l.172344/172352) mas não tem nenhum chamador no assembly — esse não foi bloqueado de propósito (bloqueá-lo às cegas poderia atrapalhar um caminho legítimo do jogo).
  • Interceptor de Esc é um slot único (UIWindowManager.CancelInterceptor, l.215982). O InventoryManager.OnEnable (l.125301) também o ocupa. RSTV-5: o mod agora fotografa quem tinha o slot ANTES de abrir (CaptureInterceptorOwner, antes do SetActive(true) que dispara o OnEnable do SkillTreeManager, l.172890) e devolve esse delegate no fechamento — não deixa mais null no lugar de quem estava lá. Além disso, o Esc deixa de ser consumido quando a janela do topo (OpenedWindows) não é a nossa, a CharacterMenusManager ou a tela de party (blindagem 3). Fica para medir em jogo: se o delegate devolvido é o do SkillTreeManager (o caso esperado, porque o OnEnable dele roda no SetActive(true) uma linha antes), o OnDisable do próprio jogo (l.172899) faz a limpeza normal e o slot termina em null — igual ao vanilla.
  • Largura da linha com childForceExpandWidth: o mod hoje não compensa a largura nesse modo ("o próprio grupo acomoda o filho"). Isso só vale se a linha tiver largura fixa; se ela for dimensionada pelo conteúdo (um ContentSizeFitter acima), ela cresce com o clone e o log mostra largura da linha A -> B com A ≠ B. É exatamente o que a linha do log serve para flagrar no primeiro teste em jogo.

Correções da auditoria estática (contra o decompilado, sem rodar o jogo)

  • Gancho 8: SkillTreeManager.ResetSkillPoints bloqueado em read-only (segundo ponto de gravação).
  • A árvore agora fecha sozinha se a tela Select Party fechar por fora (aceitar a party muda o GUIState e o jogo fecha a tela em l.118416/l.133307/l.215520 sem fechar o SkillTreeManager): antes ela ficaria desenhada por cima do mapa, com o interceptor de Esc instalado.

Riscos do botão na run (RSTV-5 — estáticos, precisam de conferência em jogo)

  • Aparência do clone: o Ping Button é um botão de ícone e o resources.assets não foi aberto. O mod cloneia o botão nativo, desliga os gráficos herdados que não são o targetGraphic (o ícone do ping) e cria um rótulo Skills a partir de outro texto do mesmo HUD. Se o targetGraphic do Ping Button for o próprio ícone, o botão novo aparece com o ícone do ping — o log diz qual é o targetGraphic, então isso se resolve com uma linha a mais, sem adivinhação.
  • Janela registrada em OpenedWindows (camada 1 contra o clique atravessando a janela): enquanto a árvore está aberta, GUIManager.InMenus é true, o que também desliga o input de hex legítimo e limpa o CurrentlyHoveringHexCell. É o comportamento desejado (a janela cobre a tela), mas mexe no OpenedWindows do jogo — se o registro falhar, o log avisa e a camada 2 (prefixo no ProcessLeftMouseClick) continua valendo. Se a árvore for fechada por fora, a rede de segurança do host retira a entrada (uma vez por segundo) para o jogo não ficar com o input morto.
  • Z-order vs. tooltip: a árvore é instanciada em outro ramo do canvas; na run o mod a empurra para cima do HUD (AjustarZOrder). Se o tooltip do jogo passar a aparecer atrás da árvore, desligar AjustarZOrder no .cfg e conferir.
  • Escolha do alvo na run: se o jogo estiver com outro personagem selecionado, a abertura é recusada (a árvore mostraria o contexto de tooltip errado — Tooltip.TooltipCharacter, l.213124). Se o jogo estiver sem ninguém selecionado, o mod usa o primeiro personagem local da party (fallback documentado) e devolve a seleção ao valor anterior ao fechar. O caso de o jogo tolerar a seleção voltar a null no meio da run precisa de conferência em jogo (é o mesmo estado de antes da abertura, mas quem escreveu foi o mod).
  • Checagens de turno só em batalha: fora de batalha o IsPlayerTurnAndReady fica no último valor do combate, e usá-lo fora da batalha bloquearia o botão no mapa do mundo. Se em jogo aparecer alguma janela ruim fora de batalha, a whitelist de GUIState é o lugar de apertar.
  • CurrentCharacterUI.skillsBtn / chooseSkillsBtn: o jogo tem campos de botão de skills no HUD (skillsBtn, l.209436, e chooseSkillsBtn, l.209462 — este último nunca referenciado em código, logo tem onClick ligado só no prefab, provavelmente ButtonPressedSkillTree, l.209994). No modo Roguelike o caminho nativo de skills abre SkillTreeManagerRoguelike (l.173293 / GUIManager.ShowSkillTree, l.119313) — que é a árvore de level-up, com escrita. Fica para conferir em jogo se algum desses botões aparece na run e se convive com o nosso (o nosso é somente leitura e não os substitui).

CHANGELOG

Changelog — RoguelikeSkillTreeVisualizer

0.3.0 — aba "All Skill Trees" + botão/atalho + escala/posição nativa (RSTV-13..19)

RSTV-13 — o botao Skills do HUD volta a APARECER passeando no MAPA-MUNDO. O RunTargets.GateOk aplicava ao mapa portoes que descrevem UMA BATALHA: Root.SpawnPlacementActive (so existe no setup da batalha, l.112152 liga / l.112165 desliga) e HexCellManager.CurrentState == PlayerState.Action (a mira de skill) eram checados em QUALQUER estado — no mapa eles escondiam o botao justamente enquanto o jogador passeia (sintoma 5 do dono). O IsPlayerTurnAndReady ja estava dentro do ramo InBattle, mas a separacao estava implicita. Agora ela e explicita: os portoes de batalha (mira, posicionamento inicial, turno, AnyActingCharactersInBattle, AnyMovingCharactersInBattle) moram DENTRO do ramo if (estado == GUIState.InBattle); no mapa/cidade valem so os universais (modo de apontar o hex, UI aberta, alvo). A seguranca EM BATALHA nao mudou — cada portao continua bloqueando la.

RSTV-14 — o botao DENTRO da janela do level-up segue o CICLO DE VIDA (deixa de "voar"). O LevelUpWindowButton era criado SO no postfix de OpenSkillSelectWindow e nunca reavaliado: o jogo troca o estagio do level-up SEM fechar a janela (CurLevelUpStage, l.168697, liga SkillSection/ItemSectionInventory/AttributeSection), o painel Content muda de metragem e o clone — filho do SkillSelectWindow — continuava visivel no estagio de ATRIBUTOS, preso ao canto ANTIGO do painel (sintoma 3). Agora um LevelUpWindowButton.Refresh() (chamado a cada quadro pelo RstvHost.Update e tambem no fim do Ensure) mostra o clone SO com a janela ativa, da mesma instancia, no estagio LevelUpStage.Skills — nos outros estagios (Items/Attributes/Currency) ele some, e ao VOLTAR ao estagio de skills ele e re-ancorado no canto atual do Content. O Ensure recusa injetar com a janela fechada ("nao injetar fora de hora"). O sintoma (6) ("aperto Skills e o botao some") e o flapping do portao de "janela aberta", tratado na RSTV-5: a arvore read-only nao deixa a janela presa em UIWindowManager.OpenedWindows, entao o botao do HUD VOLTA ao fechar.

  • Testes: t_rstv13_gate_mapa.py (suite pura; modelo puro do portao por estado) e t_rstv14_botao_ciclo_levelup.py (suite pura; ciclo de vida + opcoes de estagio). Iscas: cp_rstv13_batalha_no_mapa.py, cp_rstv14_refresh_morto_no_host.py, cp_rstv14_refresh_sem_estagio.py. Prova de fogo FISICA: os tres defeitos foram plantados nos fontes REAIS (RunTargets.cs, RstvHost.cs, LevelUpWindowButton.cs), o teste saiu REPROVOU (exit 1) pelo motivo certo e os arquivos foram restaurados byte a byte (sha256 conferido). Log: tools/testes/rstv13-14-prova-reprovando.log. A RSTV-8 teve o modelo do portao ajustado (mira e spawn agora contam como portoes de batalha no cenario puro).

Nao publicado (RSTV-12) — precisa de conferencia em jogo

RSTV-12 — o clique dos botoes Skills passa a chegar ao mod (nao ao onClick do prefab). O log de 02/10 mostrou os dois sintomas: o botao do HUD pingava em vez de abrir a arvore, e o da janela do level-up disparava ConfirmLevelUpSelection (11 NullReferenceException). Causa raiz lida do IL de UnityEngine.CoreModule.dll deste build: o UnityEvent guarda DUAS listas — m_PersistentCalls (os listeners serializados no prefab, ex.: ButtonPressedPing/ConfirmLevelUpSelection) e m_RuntimeCalls (os de script) — e RemoveAllListeners() chama InvokableCallList.Clear(), que esvazia so a de runtime. Pior: PrepareInvoke() concatena as persistentes antes das de runtime, entao o listener do jogo roda primeiro e, se ele lancar, o Invoke() aborta e o nosso nem chega a rodar.

  • Conserto: novo helper SelectPartyButton.ClearClickListeners(Button) — troca a instancia do evento (button.onClick = new Button.ButtonClickedEvent()), descartando junto a lista persistente serializada; o listener do mod e instalado no evento limpo. Aplicado nos tres clones (HUD, janela do level-up e tela Select Party).
  • Nao era sobreposicao/raycast (o log mostra o NOSSO OpenForTarget sendo chamado no clone do HUD) nem re-bind de componente (UIButtonController, l.336107, so troca cores — nao mexe em onClick).
  • Testes: t_rstv12_onclick_roteamento.py (suite pura) + cp_rstv12_clique_no_original.py (isca). O modelo puro do UnityEvent reproduz o clique que cai no original (pingou no HUD / levelup-nre no level-up) e aprova o conserto (abriu). Prova de fogo fisica: o defeito foi plantado no RunButton.cs e no LevelUpWindowButton.cs REAIS, o teste saiu REPROVOU (exit 1) e os fontes foram restaurados byte a byte (sha256 conferido). Log: tools/testes/rstv12-prova-reprovando.log.

Nao publicado (RSTV-11b) — precisa de conferencia em jogo

RSTV-11b — o botao Skills passa a existir DENTRO da janela do level-up. O re-parent da RSTV-9 foi aposentado: o clone do HUD nao muda mais de casa (a entrada da RSTV-9 abaixo fica como historico). No lugar dele, o postfix de RoguelikeManager.OpenSkillSelectWindow clona o AcceptButton como filho direto do SkillSelectWindow (TELA CHEIA), fora do filho Content — que tem VerticalLayoutGroup

  • ContentSizeFitter, e um filho ali reflui o painel inteiro. O botao nasce no canto superior direito do painel visivel (o Content, ~293x536 px), com o onClick SERIALIZADO limpo (RemoveAllListeners — ele chamava ConfirmLevelUpSelection, escrita) e apontando para o RunButton.OpenForTarget. O Ensure e idempotente por janela (o gancho roda de novo a cada personagem da fila de level-up, sobre o mesmo SkillSelectWindow).

RSTV-11b — RunButton.OpenForTarget() e o corpo unico. Extraido do antigo OnClick, e reusado pelo botao do HUD, pelo botao da janela do level-up e pelo atalho F10 (RSTV-11a), que antes duplicava o corpo. A maquinaria do re-parent (HomeInLevelUp/HomeInRow/CaptureHome, campos _home*, constante LadoMinimoLevelUp) saiu do RunButton.

Testes: t_rstv11b_botao_janela_levelup.py (suite pura) + cp_rstv11b_sem_idempotencia.py e cp_rstv11b_clone_no_content.py (iscas). Os dois defeitos — 'sem idempotencia' (2o botao) e 'clone no Content' (reflow) — foram plantados no fonte real, o teste saiu REPROVOU (exit 1) e o arquivo foi restaurado byte a byte (sha256 conferido). Log: tools/testes/rstv11b-prova-reprovando.log.

Historico (RSTV-8 + RSTV-9) — precisa de conferencia em jogo

RSTV-8 — o botao da run deixa de ser escondido no level-up. O portao RunTargets.GateOk escondia o botao com if (RoguelikeManager.IsNotNullAndIsActive) return false, mas isso significa "o manager foi CARREGADO" — o GameObject dele fica ativo a run inteira depois do primeiro level-up. A janela de verdade e o filho SkillSelectWindow.activeSelf (o mesmo criterio que o jogo usa em GUIManager.Update). Agora o level-up libera o botao, e o alvo passa a sair do PROPRIO manager (CurrentRoguelikeSkillSelectingCharacter, fallback CharactersWaitingForLevelUp[0].Character), nunca do CurrentlySelectedCharacter que ele troca. Os demais portoes (mira/ping/janela/spawn/turno/animacao) continuam.

RSTV-9 — o botao RENDERIZAR durante o level-up (caso a parte). [SUPERADO pela RSTV-11b — o re-parent abaixo foi APOSENTADO; o level-up agora tem um botao PROPRIO dentro da janela, LevelUpWindowButton.] Achado critico da RSTV-8: nesse instante o jogo desliga o HUD INTEIRO (GUIManager.Update, l.120492, faz CurrentCharacterUI.gameObject.SetActive(false) enquanto SkillSelectWindow esta ativo) e o clone da RSTV-5 e filho desse HUD — o portao liberar era NECESSARIO, mas o botao NAO renderizava. Agora, enquanto a janela do level-up esta aberta, o RunButton realoca o clone para DENTRO do SkillSelectWindow (canto superior direito, ultimo irmao para desenhar/clicar por cima, lado no minimo LadoMinimoLevelUp = 44 px) e o devolve para a linha do Ping Button quando ela fecha, restaurando a geometria original (sem reaplicar o ApplySquareLayout). O caso normal — tela Select Party e run no mapa-mundo E em combate — NAO muda: a realocacao so ocorre dentro do ramo de level-up; o tamanho da linha (Ping Button) segue intacto.

Testes: t_rstv9_render_levelup.py (suite pura) + cp_rstv9_botao_no_hud_desligado.py (isca). A prova de fogo fisica plantou o defeito no RunButton.cs real (o teste saiu REPROVOU, exit 1) e restaurou o fonte byte a byte.

0.2.0

RSTV-5 — o botão Skills agora existe DURANTE A RUN, ancorado no Ping Button (o botão de apontar o hex) do HUD, ao lado dele, na mesma linha. A janela é a MESMA Skill Tree nativa, no mesmo modo somente leitura. Nada disto foi conferido em jogo ainda — o roteiro está no README.md.

  • Botão no HUD da run: clone do botão nativo CurrentCharacterUI.pingBtn (l.209464, handler ButtonPressedPing l.210027 → GUIManager.TogglePingMode l.119299), injetado como irmão à direita dele com a mesma compensação de largura já validada na tela de party (reusa o SelectPartyButton.ApplySquareLayout nos 3 modos de layout). Injeção 1× pelo postfix de CurrentCharacterUI.InitSingleton (l.209626, o ponto em que o jogo instancia o HUD) e espelhamento no RstvHost.Update do mod — nunca no Update/UIUpdate do próprio HUD (l.209655, que roda todo frame e mexe no endTurnButton).
  • Alvo resolvido no clique: o personagem selecionado no momento; se o jogo estiver sem ninguém selecionado, o primeiro personagem local da party (fallback documentado).
  • Portão de segurança (sempre ativo, mesmo com o botão ligado): o botão some e recusa a abertura, com o motivo no log, quando há mira de skill ativa (HexCellManager.CurrentState == Action), modo de apontar hex ligado, não é o turno do jogador, alguém agindo/movendo, posicionamento inicial, level-up pendente (RoguelikeManager), janela de UI aberta ou GUIState fora de InBattle/InWorldMap/InTown. Em dúvida (até numa exceção ao ler o estado), não abre.

Blindagens implementadas (cada uma com a linha do decompilado no código)

  1. Ciclo de vida — a guarda deixou de ser "a tela de party está aberta" (durante a run ela está desligada e a árvore fecharia no frame seguinte): agora é "a instância nativa ainda está ativa" + whitelist de GUIState para o contexto de run; a checagem da tela de party continua só no contexto de party.
  2. Escrita de GameLogic.CurrentlySelectedCharacter (setter l.108082) — só escreve quando é inofensivo: se o alvo já é o selecionado (o setter retorna cedo, l.108097 → zero efeito) ou se o jogo está sem ninguém selecionado (a metade perigosa do setter só roda com um personagem anterior). O valor anterior é guardado e devolvido no fechamento (padrão do próprio jogo, l.163711/163942). Com outro personagem selecionado, a abertura é recusada — se não, o setter marcaria IsPartyLeader, travaria a câmera, forçaria HexCellManager.CurrentState = Movement (cancelando a mira) e chamaria NotifyPlayerOfTheirTurn em outro personagem.
  3. Slot único de CancelInterceptor (UIWindowManager.CancelInterceptor, l.215982) — o mod fotografa quem tinha o slot antes de abrir (antes do SetActive(true), que dispara o OnEnable do jogo, l.172890) e devolve esse delegate no fechamento (antes deixava null). O Esc deixa de ser consumido quando a janela do topo não é a nossa, a CharacterMenusManager ou a tela de party (mesmo critério do jogo, l.172924) — a mochila por cima continua fechando no Esc.
  4. Portão do botão — ver acima.
  5. Clique do hex — o branch de Action do PlayerMovement.ProcessLeftMouseClick (l.153393-153397) não checa PointerOverUIObject (o de movimento checa, l.153342). Duas camadas: a janela passa a ser registrada em UIWindowManager.OpenedWindows (→ GUIManager.InMenus, l.118297, faz ProcessUpdateInputs l.152986 retornar antes do clique) e um prefixo Harmony em ProcessLeftMouseClick(HexCell) (l.153277) consome o clique enquanto a sessão está ativa. Uma rede de segurança no host retira a entrada de OpenedWindows se a janela fechar por fora, para o input de batalha não ficar morto.
  6. Troca de personagem / de estado — se o jogo trocar o personagem selecionado ou sair de InBattle/InWorldMap/InTown, a sessão é fechada (nada de janela read-only com contexto errado).

Config

  • Geral → AtivarBotaoNaRun (padrão true): liga/desliga só o botão da run. O portão 4 vale mesmo com true.
  • Geral → AjustarZOrder (padrão true): ajusta o índice de irmão para a árvore ficar por cima do HUD na run. Desligue se o tooltip do jogo aparecer atrás da árvore.
  • Geral → AtivarBotao continua sendo o da tela Select Party (inalterado).

Outras mudanças

  • Ganchos: 8 → 10 (novos: postfix de CurrentCharacterUI.InitSingleton, prefixo de PlayerMovement.ProcessLeftMouseClick). A contagem do boot vira 10/10 ganchos, 10 metodos do jogo.
  • Diagnóstico: o botão da run diz por que está escondido, com throttle de 5 s por motivo, e avisa quando volta a aparecer; a abertura registra a geometria da linha, o targetGraphic do clone e quantos gráficos herdados foram desligados.
  • SkillTreeReadOnly ganhou contexto (PartyScreen/Run) e a sessão ganhou as guardas de ciclo de vida, a devolução da seleção e a limpeza de OpenedWindows.

0.1.0

Primeira versão. O mod ainda não foi conferido em jogo — a conferência está no roteiro do README.md.

  • Botão Skills na tela Select Party do modo Roguelike: quadrado, na mesma linha e colado à direita do Choose Powerups. É um clone do botão nativo (mesmo sprite e estados); a largura do Choose Powerups encolhe só o necessário, a linha não cresce e o Accept Party não muda de tamanho.
  • Abre a Skill Tree NATIVA do jogo (a mesma de Campaign → Change Skills) com o contexto real do personagem: nível, equipamento, atributos e estado das skills. A janela nativa é reaproveitada — o mod não reimplementa a árvore.
  • Modo somente leitura de verdade: tooltips (com os números do personagem certo), zoom, troca de abas e requisitos continuam funcionando; nada é aprendido, removido ou gasto. O clique no nó é bloqueado, o Respec Build fica escondido e o rodapé mostra 0 points available.
  • Alvo do botão: o último personagem que VOCÊ adicionou à party, resolvido no clique (nível e equipamento sempre atuais, sem cache). Cada jogador usa os seus; party local vazia = botão desabilitado.
  • Config: Geral → AtivarBotao em BepInEx/config/com.gumatos.roguelikeskilltreevisualizer.cfg (padrão true). Só serve para desligar o botão — quem não abrir o arquivo não vê diferença.
  • Diagnóstico: cada gancho Harmony é aplicado e logado individualmente (um gancho que falha não derruba os outros — antes o PatchAll() era tudo-ou-nada); o resumo do boot traz a contagem real (RSTV: patches Harmony aplicados (8/8 ganchos, 8 metodos do jogo)); e se o botão não for injetado o log diz por quê (botão nativo ausente, sem RectTransform, exceção, config desligado) com o estado da tela.

Correções da auditoria estática (contra o decompilado do jogo, sem rodar o jogo)

  • Segundo ponto de gravação fechado: SkillTreeManager.ResetSkillPoints() (l.173274) chama Character.ResetSkills() — que zera todas as skills do personagem e enfileira o save (l.37474) — sem passar pelo AcceptSkillChanges. É gancho novo (8º) e é bloqueado no modo somente leitura.
  • Árvore órfã: se a tela Select Party fechar por fora com a árvore aberta (aceitar a party muda o GUIState e o jogo fecha a tela em l.118416/l.133307/l.215520), nada no jogo fechava o SkillTreeManager — a árvore ficaria por cima do mapa com o interceptor de Esc instalado. Agora o host fecha a árvore quando a tela de party não está mais aberta.
  • O que não mudou de propósito (precisa de teste em jogo, está em Riscos conhecidos do README.md): CheckSkillDependancies(Character) (l.172328, sem chamador no assembly), resetButton/closeButton (ligados só no prefab) e o slot único de CancelInterceptor.

Notas de implementação

  • O jogo não tem um modo somente leitura nativo: a garantia vem de higienizar o ponto de gravação antes que ele execute, e não de bloqueá-lo. Se essa higienização falhar, o mod não deixa o original rodar e fecha a janela por conta própria — zero escrita em qualquer estado.
  • Nenhum arquivo do jogo é modificado; desinstalar é apagar a pasta do mod.
  • Compatibilidade declarada: Stolen Realm v1.3.1.
  • Ícone: arte do autor redimensionada para 256×256. O original de 1254×1254 não está versionado no repositório (ver a seção Ícone do README.md).