26 listopada 2025

Leaked instances

Znalazłem przyczynę wyciekających (okrutnie) instancji Node .... ale nie żebym od razu je wszystkie poprawił, co to to nie.

Wyciekające węzły.


Kłopotem jest to, że w ramach list rozwijalnych montuję pod poszczególnymi opcjami dodatkowe informacje w ramach klas c# zwane metadata. Żeby klasy mogły trafić do takiego typowo godotowego miejsca muszą rozszerzać typ Node ponieważ przez instrukcję SetItemMetadata(index,metadata) mogę dodać tylko coś co jest widoczne w godot i rozszerza typ Variant

Poniżej przykładowa instrukcja dodania metadanych podczas wypełniania listy rozwijalnej OptionButton dla tarcz

ShieldSelect.GetOptionButton().SetItemMetadata(index, metadata);

Klasa dodana jako metadata to dane pancerza. W tym przypadku pierwszym parametrem jest definicja tarczy z jsona, Drugim parametrem jest grupa, do której ta tarcza należy.

 var metadata = new ArmourMetadata(shield, shieldGroup.Key);

Ważna jest jednak definicja klasy ArmourMetadata a nie to co trzyma...


    public partial class ArmourMetadata : Node <-- to te nody najczęściem mi leakują
    {...pola klasy teraz nie są ważne...}

Jak widać rozszerzamy typ Node.

Jak to naprawiać

Ze względu na to, że część list rozwijalnych muszę czyścić i wypełniać na nowo per losowana postać, np. w przypadku broni gdzie chcę na liście oznaczyć, do których broni postać ma preferencyjne bonusy musze listy czyścić i wypełniać ponownie. Tworząc nowe węzły w drzewie...

Nie wystarczy wtedy przeiterować przez kolekcję dzieci dla OptionButtrona jak niżej

foreach (Node child in mentalContainer.GetChildren())
{ reszta kodu z usuwaniem dzieci }

i zrobić na poszczególnych dzieciach child.QueeFree() bo to nie zrzuca metadanych. Potrzeba oddzielnej iteracji po kolekcji metadanych i dodatkowe ich zrzucenie.

Stąd powstała metoda zrzucająca metadane zawarte pod poszczególnymi opcjami

  public void ClearOptionsMetadata()
        {
            for (int i = optionButton.GetItemCount() - 1; i >= 0; i--)
            {
//tu pobieramy metadane
                Variant metadata = optionButton.GetItemMetadata(i);

                if (metadata.VariantType == Variant.Type.Object)
                {
                    Node node = metadata.As<Node>();
                    if (node.IsInsideTree())
                    {
//tu zrzucamy jeśli są wezłem w drzewie
                        node.QueueFree();
                    }
                }
                optionButton.RemoveItem(i);
            }
        }

Podejrzewam, że będę musiał wywołać tę metodę na jakimś ogólnym Dispose() dla obiektów...

24 listopada 2025

To ja go tnę!

Udało się (prawie, patrz na koniec wpisu) dodać losowanie biegłości postaci  i wybór broni oraz pancerzy. To jest na prawdę masa kodu. Zasady w KC są niemiłosiernie skomplikowane w tym aspekcie i przerzucenie ich do postaci klas c# zajęło mi bardzo dużo czasu. 

ALE działa i jest.


Stan wyjściowy po wylosowaniu bohatera.


Dodanie broni do postaci

Podbicie specjalizacji do podwójnie biegłego

Dodanie zbroi


I cyk tarcza
    
Nie ograniczamy się też do jednej możliwej broni, można mieć ich wiele.

Albo i kilka z jednej grupy. Tu espadon i miecz półtoraręczny.



Współczynniki przeliczane są na bieżąco. 


Błędy? Jakie błędy!

Mechanizm ma jeszcze kilka wizualnych błędów, np. po dodaniu tarcz nagle przestał mi działać podgląd wybranych broni w zakładce broni i widać tylko ostatnią.  Inna niedoróbka to nie uwzględnianie wzrostu biegłości per poziom postaci czy pancerza naturalnego wynikającego ze skóry. Są to już jednak drobiazgi w porównaniu z całym mechanizmem
Ważne, że się pojawił. Błędy to rzecz pewna w tak rozbudowanym kodzie.

19 listopada 2025

Krótki wpis o broni

 Może to i mała aktualizacja ale jakże ważna w kontekście całego generatora. Do postaci można dodać broń i wylosować biegłość. Nie działa to jeszcze idealnie (miałem mało czasu na zrobienie tej zmiany), ale dzięki tak małym i działającym modyfikacjom chce się pisać dalej. 


Może i nie ma wszystkich parametrów ale ważne
jest że się pojawiają w opisie bohatera.



Leaked instance

Z niedoróbek - w kodzie często posługuję się klasami od typu `Node` i jeśli taki typ źle się zrzuca z pamięci nie używając polecania  

      public void Dispose()
        {
            QueueFree();
        }
   

dochodzi do wycieków... 

Przy zamykaniu aplikacji są ich ... dziesiątki :D. Pomyślę kiedyś czy to porawiać



16 listopada 2025

Więcej pomysłów niż dostępnego czasu

 Kilka ostatnich dni pozwoliło mi na wprowadzenie sporej ilości zmian w generatorze. Problematyczne stało się to, że rozgrzebałem jednocześnie kilka funkcjonalności i żadna nie działa tak jak powinna, a ja przeskakuję na kolejną i tu trochę kodu wrzucę, tam dodam trzy linijki... Przy takim trybie pisania ciężko kończy  się jakikolwiek projekt. Postaram się poniżej opisać co dodałem i może takie podsumowanie pozwoli mi na spojrzenie z góry na projekt i dokończenie po kolei rozgrzebanych elementów.

Dwie profesje

Dodałem możliwość wybrania dwóch profesji do generowanej postaci oraz uwzględnieniu w nich poziomów zaawansowania. Spora zmiana na plus względem poprzedniego podejścia do tego tematu. Teraz wiem skąd wynikają wartości składające się na cechy oraz raczej nie ma w tym błędów (tak mi się przynajmniej wydaje) Stworzenie opisanej wcześniej meta-profesji z dwóch było dobrym kierunkiem.

Postać dwu profesyjna z uwzględnionymi w cechach
poziomami per profesja

Na zakładce statystyk widać skąd wynika suma. Tu  
Żywotność dla poziomów 1-3 została powiększona o większą 
wartość wynikającą z profesji Wojownika a poziomy 4-5 już z Kupca

I tu pierwsza (grubsza) niedoróbka,
Na zakładce z odpornościami można dodać
bonus wnikający z poziomów ale tylko do.... odporności psychicznej

Plus jest taki że przynajmniej dobrze się liczy. 
Musze dodać jakichś... checkbox czy coś wskazujący,
w którą bazową pompujesz rzuty.


Zakładka Broni

Dodałem dwie nowe zakładki do generatora, w których będzie można dopełnić definicje postaci. Pierwsza z nich to zakładka broni.

Rzeczona zakładka

Wybór będzie standardowy - dobieramy grupę broni, dobieramy broń i losujemy biegłość. Na razie pracuję nad tym, aby w ogóle działała poprawnie, a postać prawidłowo się aktualizowała.

Wybór grup broni

Wybór broni noszonej przez postać

I efekt wyboru broni do postaci. Jak widać prace nad nim trwają.

Bez kłopotu można dobrać wiele broni.


W tej zakładce kryje się multum logiki i do tej pory pokryłem niewielką  jej część. Podstawowe problemy to oznaczenie, że broń jest preferowana w kontekście wybranej rasy i później zawodu (vide profesja Kowal i jego bonus do młotów) . Generator musi też wiedzieć jakie grupy broni wylosowano żeby per wybór np. różnych mieczy nie losował biegłości wielokrotnie, podobnie z poziomem biegłości, oburęcznością itp. itd. Znawcy zasad KC-tów wiedzą, że to ciężki kawałek charakterystyki postaci. Później trzeba do tego dodać cechy postaci, przeliczyć możliwe obrażenia (możliwe 6! rodzajów - ponieważ broń może być do walki wręcz i zasięgowa). W skrócie multum kodu. Na koniec trzeba to wyświetlić na podsumowaniu postaci w kc-towym szablonie.


Zakładka Zbroje

Tutaj na szczęście nie będzie jakichś cudów. Jest o wiele prostsza niż to co jest w zakładce broni. Wybór elementu i wrzucenie do cech w postać. Praca jest zaledwie rozgrzebana do poziomu wczytania listy pancerzy i tarcz na ekran.

Wybór pancerza

I sam efekt rozwinięcia listy. Jak łatwo zauważyć
 sama lista rozwijalna ma pewne niedoróbki.

I tyle można zrobić, wybrane elementy nigdzie nie są uwzględniane.


Podsumowując

Zostało jeszcze sporo od napisania. Z samego c# i godota zaczęły przychodzić mi z pomocą EventHandlery ponieważ zwykłe przenoszenie do głównego kontrolera informacji o zmianach na zakładce broni jest tak rozbudowane, że w przypadku próby zbudowania tego przez zwyczajne przenoszenie danych między klasami doprowadziłoby do powstania kilkudziesięciu metod mających tylko za zadanie przerzucania stanu.

Stąd też powstała klasa-singleton trzymająca definicje sygnałów (powoli skłaniam się do podzielenia jej per zakładka oraz per główne akcje na ekranie).

using Godot;
using OptionsMetadata;

partial class GeneratorSignal : Node
{
    [Signal]
    public delegate void AddLevelClickedEventHandler(int index);

    [Signal]
    public delegate void CharacterReadyEventHandler();

    [Signal]
    public delegate void ReloadCharacterEventHandler(CharacterSheet character);

    [Signal]
    public delegate void CharacterChangedEventHandler();

    [Signal]
    public delegate void WeaponGropupRemovedEventHandler(WeaponMetadata metadata);

    [Signal]
    public delegate void WeaponSkillIncreaseEventHandler(WeaponMetadata metadata);

    [Signal]
    public delegate void WeaponDoubleHandAddedEventHandler(WeaponMetadata metadata);

    [Signal]
    public delegate void WeaponSkilUpdatedEventHandler(WeaponMetadata metadata);

    private static readonly GeneratorSignal _Instance = new();

    private GeneratorSignal() { }

    public static GeneratorSignal Instance
    {
        get { return _Instance; }
    }
}

I zwykła obsługa emisji sygnałów przez Emit

    public void OnAddDoubleHandPressed()
    {
        GeneratorSignal.Instance.EmitSignal(
            GeneratorSignal.SignalName.WeaponDoubleHandAdded,
            weaponMetadata
        );
    }


i przechwytywania przez Connect i obsługa Callable

private void ConnectSignals()
    {
        GeneratorSignal.Instance.Connect(
            GeneratorSignal.SignalName.AddLevelClicked,
            new Callable(this, nameof(OnSomethingHappened))
        );

        GeneratorSignal.Instance.Connect(
            GeneratorSignal.SignalName.CharacterChanged,
            new Callable(this, nameof(CharacterChanged))
        );
    }

 

02 listopada 2025

Zmian ciąg dalszy

Udało mi się przenieść część funkcjonalności do nowego szablonu i już teraz widzę że generator jest wygodniejszy w użyciu nie tylko za sprawą  ładniejszego gui, ale jako sama baza kodu. Zrobiłem spory refaktor, przepakietowanie, jakieś poprawki nazewnictwa i namespaców. Całkowicie przebudowałem część odpowiedzialną za dobieranie cech w przypadku więcej niż jednej profesji. 

Algorytm

W poprzedniej wersji algorytmu przy dwóch profesjach losowałem dwie postacie i dobierałem wartości wylosowanych cech zgodnie z ich kolejnością losowania przy czym do końcowego podsumowania przenoszona była wyższa wartość. Jednakże bardzo trudno było sprawdzić czy cechy zostały dobrze dobrane, czy prawidłowo się połączyły i czy w ogóle końcowy efekt jest poprawny (zwykle nie był). Przeliczenie odporności stało się za skomplikowane i byłem pewien nieprawidłowych wartości w podsumowaniu postaci. Musiałem podjąć drastyczne kroki. Cały ten kod wyrzuciłem i napisałem od nowa.

Aktualna wersja działa inaczej. Przy dwóch profesjach tworzę meta profesję, do której dobieram tylko lepsze (wyższe) cechy z wybranych profesji. Dzięki temu generator cech i odporności uruchamiany jest jeden raz (tak jakby to była jedna profesja) i nie ma problemu z modyfikacjami współczynników wynikających z innych kroków generatora np. wagi postaci. Nie muszę uruchamiać algorytmu łączącego dwie wylosowane postacie i dobierającego lepsze cechy. 

Stan wyjściowy generatora


Na zrzucie widać generowanie wojownika. W logu pod przyciskiem losowania 
widać jakie cechy zostały dobrane do profesji z jakimi kostkami i takimi tam
sterującymi rzeczami. Docelowo log będzie wyświetlał postać
w szablonie znanym z kryształów.


Teraz dwie profesje - Barbarzyńca/Astrolog i zebrane cechy 
profesji. Jak widać dla barbarzyńcy 20+ to HP/SF/ZR/SZ ale już z 
astrologa 20+ dla MD/WI oraz +10 IQ/CH/UM. Podobnie zostały dobrane 
odporności - te większe. Jeśli byłaby możliwa taka konfiguracja
profesji to jedynie Elektryczność nie będzie zwiększana rzutem.
To te -1 przy parametrze BaseProfession dla tej odporności oraz D0 (dice 0 )

Testy

Ach bym prawie zapomniał - do projektu dodałem kilka prostackich testów. W c# nigdy ich nie pisałem także jest to dla mnie terra incognita. Mam nadzieję, że wspomogą mnie w pracy nad projektem ... o ile będzie mi się chciało powiększać ich bazę.

31 października 2025

Zmiany idą

 Musiałem zmienić wygląd gui. Poprzednie całkowicie nie sprawdza się na telefonie. Mam nadzieję, że nowe będzie czytelniejszy oraz wygodniejsze w użyciu. Niestety jego pełne uruchomienie będzie wymagał sporo pracy i zmian w projekcie


GUI upodobni się do tego jakie jest w starym generatorze
napisanym w java script



I po kilku chwilach pracy mam zmianę na lepsze

Teraz tylko muszę przywrócić pełną funkcjonalność.

23 października 2025

Apka na telefonie

Tak jak pisałem ze dwa posty temu chciałbym mieć możliwość uruchomienia aplikacji na telefonie. Stąd też zamiast zająć się naprawianiem kilku oczywistych błędów w aplikacji ... odpaliłem AndroidStudio i po pewnych perturbacjach wrzuciłem aplikację na swój telefon (no na cudzy to się nie da :D )
Śliczna ikonka  :) Copilot się sprawił



Jakież było moje zaskoczenie po uruchomieniu programu, że ten ... delikatnie mówiąc, źle skaluje GUI względem rozdzielczości na ekranie. Właściwie to wcale ponieważ nie pomyślałem że będzie to problemem. Do kompletu nie ładuje się konfiguracja... Efekt poniżej. 

Obrazek dopasujcie wielkością do ekranu telefonów
to zrozumiecie w czym problem.

Przy tak małym GUI do zestawu z aplikacją powinienem dosyłać:
- lupę
- rysik

Brak konfiguracji powoduje, że aplikacja jest całkowicie nieprzydatna.

Walka z kodem i AndroidStudio

Kilka kliknięć później oraz parę godzin surfowania po forach... Konfiguracja ładuje się prawidłowo ale dalej jest brzydko. 

Fajnie, konfiguracja się załadowała,
tyle że litery są tak małe że nie da się w nią niemal kliknąć. 

Wybór postaci dwuprofesyjnej jest prawie niemożliwy bo odpowiadający za to checkbox ma z 10x10 px. Jak już uda się kliknąć na klawisz losowania postaci samo przejście na zakładkę z cechami jest  sporym wyzwaniem. Podgląd okienka bazy parametrów, z której tak się cieszyłem w poprzednim poście, jest niewidoczny - zasłania ją opuszek palca...


To jednak przejściowe problemy.
Kamień milowy uważam za osiągnięty! Aplikacja jest na telefonie? Jest. Trochę nieczytelna? No trochę. Wymaga pracy? Taaa i to dużej.

Godot C# JSON i AndroidStudio i jak to uruchomić

Teraz  będzie trochę programistycznego bełkotu tj. jak wczytać pliki json w apce godot do androida. A jak w ogóle się wyeksportować to znajdziecie multum tutoriali w sieci. 

Naprawienie błędu zamyka się w podpięciu prawidłowej klasy wczytującej zasoby JSON do projektu. 

Należy zrezygnować z klasy z C# 

Rezygnujemy z File z System.IO  (implementacja poprzednia)

 string json = File.ReadAllText(filepath);

na godotową klasę FileAccess (implementacja aktualna)

  var file = FileAccess.Open(filepath, FileAccess.ModeFlags.Read);
                    string json = file.GetAsText();

i kolejna sprawa. 

Ścieżki do plików json musza wskazywać na zasoby gry, a nie na plik na classpath.

Stąd też zmieniamy:
  private const string CONFIG_FILE = "assets/db/config.json";

na
 private const string CONFIG_FILE = "res://assets/db/config.json";

I jeśli pliki konfiguracyjne same w sobie wskazują kolejne pliki to ścieżki także powinny zawierać res:. Tak jak poniżej. Oczywiście wskazanie res:// możemy dodać statycznie z poziomu kodu, ale w pliku jest bardziej profesjonalnie ;-)

  {
            "filepath": "res://assets/db/profession/knights/paladin.json"
        },


I tyle. U mnie działa.

Różowo nie jest

Nie przyzwyczaiłem się jeszcze do nowego gui  AndroidStudio w efekcie napotkałem problem, który uniemożliwia mi zdalne przeniesienie wyeksportowanej aplikacji do telefonu. Rozwiązaniem jest ręczne wklejanie pliku apk na tel. Znacząco wydłuża to proces sprawdzania czy aplikacja działa.

Drugi problem - i pewnie połączony z powyższym - to brak debuga po uruchomieniu aplikacji na telefonie. Przy moim poprzednim podejściu do androida nie miałem takich problemów, a tym razem robię coś ewidentnie nieprawidłowo. Poczytam... popatrzę i znajdę rozwiązanie.

A i żeby nie było że to całe AndroidStudio to psu na budę w tej mojej zabawie. Poprawnie skonfigurowałem logcata i widzę, że losowanie postaci rzeczywiście ma miejsce na androidzie. 

To tyle na dziś.

19 października 2025

Podgląd cech postaci

Nie miałem natchnienia na dodanie kolejnych elementów generatora dlatego też postanowiłem go trochę upiększyć. Jedną z uciążliwości generatora było poprawne wyświetlanie informacji skąd wzięła się wysokość cechy postaci. 

W ekranie dodałem specjalną etykietka, która ładowała źródło wylosowanych cech. Miała jednak spory mankament...

Po staremu

Podgląd cech

A teraz jeszcze jakieś poziomy...

Jak widać powyżej - etykieta niemiłosiernie rozpychała ekran. Do tego stopnia, że nie dało się zobaczyć niczego poniżej charyzmy czy prezencji postaci. Jak dochodziły jeszcze poziomy robiło się jeszcze gorzej. Czasem na wysokości IQ ekran wpadał za krawędź. Stąd ten element wymagał naprawienia.


Na ratunek przyszłą pływająca etykieta umieszczona w CanvasLayer, które jest niezależne od elementów na ekranie. 

Po nowemu

Po zmianach informacja o wylosowanych cechach ładowana jest w etykietkę przyklejoną do cechy. Dodatkowo dorzuciłem jakieś proste przełączanie panelu z rysunkiem. aby było wiadomo, która cecha jest aktualnie w podglądzie. 

Po najechaniu na ikonkę kwadracika zamienia się on w celownik,
a poniżej pojawia się zestaw, z którego zostały wylosowane cechy.

Teraz całość mieści się bez problemu na ekranie.

Problem jest jednak z czytelnym debug w przypadku dwóch profesji. Ze względu na to, że zgodnie z regułami wybierane są większe wyniki dla poszczególnych cech nie jestem w stanie zachować danych z profesji A oraz B i ich sensownie wyświetlić.

Tutaj przykładowe losowanie dla takiego oto bohatera

profesja Wojownik / Kleryk
poziomy 5/7


Niby wszystko ok... +3  na końcu wynikają z tego,
że kleryk jest z wyższego poziomu niż wojownik.

Przy tym łączeniu dwóch profesji nie jestem pewien czy dobrze wyliczają się cechy, które w profesji A są losowane a w drugiej już nie czyli np. dla wojownika ZR to 10+k10 na starcie, a kapłan nie ma tu żadnego bonusu. Muszę trochę dopracować budowanie tego elementu ponieważ bez mozolnego porównywania wartości w konsoli nie jestem w stanie stwierdzić czy postać w 100% wylosowała się prawidłowo. Na pierwszy rzut oka wydaje się, że tak, ale pewności brak.

Nie mam zielonego pojęcia czy tutaj jest prawidłowa wartość...

Nowy podgląd działa też z panelem odporności, ale przy dwóch profesjach nie wiem czy cokolwiek obliczane jest poprawnie.

Jak widać poziomy nadal nie są uwzględniane.


Dopracuje i to, ale nie wiem czy zdążę  na Kryształkon 2025 - więcej info tu


13 października 2025

Godot i optionButton....

Plany były większe, ale weekend zszedł mi na dość się prostej rzeczy. Przynajmniej tak do tego poszedłem na początku i srogo się zdziwiłem komplikacją.

Mianowicie jak prawidłowo obsłużyć dwie listy rozwijalne tak, aby wybrane elementy z listy A nie były dostępne do wyboru w liście B i vice versa. Jak łatwo się domyślić (i o ile czytelniku znasz zasady kryształów czasu) chodziło o wybór profesji dla postaci dwuprofesyjnej. Nikt przecież nie chce grać Wojownikiem-Wojownikiem 😊

Cel

  • jak chcemy mieć tylko postać jednoprofesyjną to wszystkie opcje powinny być dostępne 
  • jak wybieramy opcję postaci dwuprofesyjnej to w ramach listy dla drugiej profesji do wyboru nie może być dostępna aktualna wybrana profesja z listy pierwszej
  • jak zmieniamy selekcję w liście z pierwszej (lub odpowiednio drugiej) listy to opcje muszą się na przemian blokować czyli opcja A z listy pierwszej profesji nie może być dostępna w liście drugiej profesji i oczywiście odwrotnie
  • zmiana selekcji powoduje odpowiednie odblokowania / blokady w obu listach
  • jak zamykamy listę drugiej profesji musza być możliwe do wyboru wszystkie opcje dla listy pierwszej profesji
  • powinny odpowiednio zmieniać się etykiety  na ekranie
  • podobnie etykiety poziomów, które teraz sprzęgnięte są z wyborem profesji

Doprowadzenie kodu do działania zajęło nadzwyczaj dużo czasu, a powodem tego była nieznajomość … powiedzmy, że API stojącym za listami rozwijalnymi godot czyli obiektami OptionButton. Cała trudność polegała na prawidłowym obsługiwaniu kolekcji, która trzyma w sobie definicje poszczególnych elementów listy rozwijalnej czyli tzw. popup. Zanim to zrozumiałem wsparłem się copilotem (wypluł trochę bzdur) oraz zwykłym – mozolnym – testowaniem co się stanie gdy ustawię opcję tak albo śmak. I powoli, powoli udało się. 

Efekt pracy


Stan startowy - jedna profesja
    

A tutaj już widać wybór profesji. Jak widać pogrupowane są per typ. 
Jak uzyskać jakieś ikonki niżej.

Wybieramy drugą profesję i ładuje się druga lista.


Profesja Hunter nie jest dostępna w drugiej liście do wyboru ponieważ wybrano ją
w liście pierwszej.

I odpowiednio profesja Knight wybrana w drugiej liście blokuję wybór w liście pierwszej.

Po zamknięciu listy drugiej profesji i zdjęciu checkboxa że tworzymy
postać dwuprofesyjną ponownie wszystkie opcje dostępne.

Jak widać na zrzucie jest jeszcze jakiś błąd z tym, że czasem odblokowuje się nagłówek grupy, ale ... to poprawie przy okazji. 

Co do kodu

Cała magia z podstawowym blokowaniem list opierała się na poprawnym przekazaniu selekcji między listami i ich ciągłe odświeżanie.


    public void EnabledOptions(OptionButton firstOption, OptionButton secondOption)
    {
        var firstOptionSelected = firstOption.Selected;

        var secondOptionSelected = secondOption.Selected;
        var popupOptions = secondOption.GetPopup();
        var popupCount = popupOptions.ItemCount;

        for (int itemIndex = 0; itemIndex < popupCount; itemIndex++)
        {

            if (itemIndex == secondOptionSelected)
            {
                continue;
            }
            if (popupOptions.IsItemDisabled(itemIndex))
            {
                popupOptions.SetItemDisabled(itemIndex, false);
            }
            if (itemIndex == firstOptionSelected)
            {
                popupOptions.SetItemDisabled(itemIndex, true);
            }
        }
    }

Kłopotem okazało się przekazanie, które elementy są nagłówkiem listy grupującym elementy oraz co zawierają poszczególne wiersze tak żeby nie opierać się na zawartości textowej wybranej opcji. Na potrzeby przekazania do generatora chciałbym wiedzieć czy coś jest Grupą Rycerz - Profesja Paladyn czy czymkolwiek innym. Stąd powstał obiekt opakowujący każdą opcję i do niej przypisany. Żeby był możliwy do użycia w kontolce silnika musi rozszerzać typ dla Variant, a najłatwiej po prostu Node

    public partial class ProfessionMetadata(
        ReadConfig.Profession proffesion,
        int index,
        int groupIndex
    ) : Node
    {
        public int ProfIndex { get; private set; } = index;
        public int GroupIndex { get; private set; } = groupIndex;
        public int OptionIndex { get; private set; } = groupIndex + index;
        public string ProfName { get; private set; } = proffesion.Name;
        public string Class { get; private set; } = proffesion.ProfessionClass;
    }

Tenże obiek wpinany był w każdą opcję z użyciem dostępnej dla wierszy listy rozwijalnej metody .SetItemMetadata

Poniżej ustawienie dla nagłówka grupy:

        optionButton.SetItemMetadata(
            header_index,
            new ProfessionMetadata(new ReadConfig.Profession(), -1, -1)
        );

Dla wiersza z profesją dane wstawiane były identycznie (tylko zmieniał się zakres) ponieważ najpierw wczytaną z json kolekcję profesji grupowałem, tworzyłem obiekty ProfessionMetadata per profesja i przypisywałem do wierszy listy rozwijalnej.

    private Dictionary<String, List<ProfessionMetadata>> BuildProfessionGroup(
        ConfigReader.ReadConfig config
    )
    {
        var professions = config.Professions;
        var professionsGroups = new Dictionary<String, List<ProfessionMetadata>>();

        var professionIndex = 0;
        int groupIndex = 0;
        foreach (var prof in professions)
        {
            var currentClass = prof.ProfessionClass;
            professionsGroups.TryGetValue(currentClass, out List<ProfessionMetadata> foundGroup);
            if (foundGroup == null)
            {
                groupIndex++;
                var group = new List<ProfessionMetadata> { new(prof, professionIndex, groupIndex) };
                professionsGroups.Add(prof.ProfessionClass, group);
            }
            else
            {
                foundGroup.Add(new ProfessionMetadata(prof, professionIndex, groupIndex));
            }
            professionIndex++;
        }
        return professionsGroups;
    }

A teraz cała funkcja budująca opcje dla podanej grupy profesji (i tak wiem ifologia dla ikon powinna zniknąć a ikonka powinna pochodzić z konfiguracji jsona. Się to kiedyś zrefaktoruje)

    private static void AddGroupToProfessionOption(
        KeyValuePair<string, List<ProfessionMetadata>> group,
        OptionButton optionButton,
        bool isFirst
    )
    {
        var header_index = optionButton.ItemCount;
        optionButton.AddItem($" --- {group.Key} ---");
        optionButton.SetItemDisabled(header_index, true);
        optionButton.SetItemIcon(header_index, null);
        Texture2D icon = null;
        if (group.Key.Equals("Warrior"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0098.png");
        }
        if (group.Key.Equals("Knight"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0096.png");
        }
        if (group.Key.Equals("Thief"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0088.png");
        }
        if (group.Key.Equals("Cleric"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0100.png");
        }
        if (group.Key.Equals("Wizard"))
        {
            icon = GD.Load<Texture2D>("res://assets//icon//tile_0084.png");
        }
        optionButton.SetItemIcon(header_index, icon);
        optionButton.SetItemMetadata(
            header_index,
            new ProfessionMetadata(new ReadConfig.Profession(), -1, -1)
        );

        foreach (var metadata in group.Value)
        {
            optionButton.AddItem(metadata.ProfName);
            optionButton.SetItemMetadata(metadata.OptionIndex, metadata);
        }
    }


I są jeszcze dwie metody wspierające przywracanie dostępności poszczególnych opcji per zmiana czy tworzymy jedno czy dwu-profesyjną postać, ale jako, że jest w nich jeszcze błąd czyli to nieszczęsne odblokowywanie nagłówków, to nie pokazuję. :)

Z rzeczy, które trzeba będzie dodać to brakuje wykluczenia profesji, aby nie wybrać np. paladyna-złodzieja, jednakże ten filtr będzie już taką wisienką na torcie jak cała funkcja będzie mi działać bez błędów.

p.s

Dorzuciłem w międzyczasie sporo konfiguracji do jsonów jak np. listy broni czy zbroi.