Pokazywanie postów oznaczonych etykietą c#. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą c#. Pokaż wszystkie posty

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ść.

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.



08 października 2025

Ja tu tylko programuję

Łatwo stwierdzić po datach, że od lutego niewiele się tu działo, a to z dość zaskakującego powodu. Gdzieś na początku kwietnia złapałem covida. Ten ostro mnie przeczołgał i przy okazji całkowicie wybił z rytmu oraz chęci pisania. Wystarczyło 2-3 tygodnie niezbyt dobrego samopoczucia (to tak oględnie mówiąc), aby przez kolejne miesiące nie wrócić do Sladuma i programowania w domowym zaciszu .

Dopiero niedawno naszła mnie chęć do wieczornych posiadówek z Godot i C#, a ich efektem jest rozszerzenie generatora postaci KC. I to właśnie o nim będzie ten wpis.

Aktualny stan mini projektu to możliwość wylosowania postaci z wybranych kilku ras, większości dostępnych profesji i dodania poziomu zaawansowania i kilku drobiazgów. 

Zrzut z niezbyt pięknego menu generatora


Lista obsługiwanych profesji 

Konfiguracja dla generatora została przygotowana na podstawie plików JSON. Te znów to efekt obróbki danych zawartych w moim starym generatorze napisanym w javaScript. Dostępny jest on-line na stronie Kryształów Czasu pod linkiem Tworzenie postaci do Kryształów Czasu na samym dole. 

A o samym kodzie 

Mamy teraz erę SI stąd mocno skorzystałem z tego narzędzia przy przekształceniu zawartości plików javascript do postaci json. Kilka iteracji promtów i otrzymałem eleganckie skrypty, które dane z javoscriptowchy obiektów generowały jsony. Te znów przerzuciłem do projektu w godot. Poniżej przykładowy plik definiujący cechy dla człowieka.

{
    "name": "Human",
    "genders": [
        {
            "gender": "male",
            "traits": [
                {
                    "name": "HP",
                    "base": 100
                },
                {
                    "name": "SF",
                    "base": 50
                },
                {
                    "name": "AG",
                    "base": 35
                },
                {
                    "name": "SP",
                    "base": 25
                },
                {
                    "name": "IQ",
                    "base": 60
                },
                {
                    "name": "WD",
                    "base": 70
                },
                {
                    "name": "SP",
                    "base": 0,
                    "special": true
                },
                {
                    "name": "CH",
                    "base": 40
                },
                {
                    "name": "PR",
                    "base": 20
                },
                {
                    "name": "FH",
                    "base": 10
                },
                {
                    "name": "DA",
                    "base": 0
                }
            ],
            "height": {
                "min": 150,
                "middleFrom": 171,
                "middleTo": 180,
                "max": 200
            },
            "weight": {
                "min": 50,
                "middleFrom": 71,
                "middleTo": 80,
                "max": 100
            }
        },
        {
            "gender": "female",
            "traits": [
                {

 Stąd już prosta (tia) droga do wygenerowania postaci zgodnie z założonymi w systemie zasadami. 

Tu zakładka z cechami podstawowymi

I zakładka odporności

Aktualnie pracuję nad uwzględnieniem poziomów postaci w zwiększaniu wysokości odporności per poziom postaci. Stąd pasek z kwadracikami na zamieszczonym powyżej zrzucie ekranowym z zakładki  `Resistances` . .

W konsoli można tymczasem podejrzeć kolejność wylosowanych cech oraz co - kiedy zostało wylosowane.


Co już jest zrobione:

  • wybór ras
  • wybór profesji
  • wybór poziomu 1-10
  • wybór charakteru
  • losowanie cech
  • uwzględnienie w nich wybranego poziomu
  • wyliczenie odporności
  • pochodzenie
  • dochód 
  • uwzględnienie pochodzenia w cechach postaci 

Czego brakuje?

  • losowania zalet
  • losowania wad
  • losowania zawodów zależnych od pochodzenia
  • postaci dwuprofesyjnej
  • dodawania poziomu do odporności
  • losowania biegłości
  • wybór broni
  • wybór zbroi
  • ładniejszego wyglądu
  • filtry na dostęp do profesji dla poszczególnych ras 

Dodatki, które chce zaimplementować

  • możliwości otworzenia na telefonie
  • możliwość zapisu postaci
  • możliwość odczytu postaci
  • dodawanie podnoszenia postaci po jednym poziomie

Czego się nauczyłem

Patrząc w kod to lepszej obsługi kolekcji w c# oraz dostępu do statycznych property. Szczególnie zadowolony jestem z (napisanego na nowo) kodu odpowiadającego za losowanie z użyciem wirtualnych kostek k10, k100 itp.

Wywołanie mocno się uprościło:

 var statRoll = Dices.d100.Roll;

Definicje klas poszczeg ólnych kostek są maksymalnie uproszczone (zmienia się oczywiście zakres od-do)

    public class D100 : Dice
    {
        private readonly Random rng = new Random();
        public int Roll => rng.Next(1, 101);
    }

A całość opakowuje klasa wystawiająca dostęp statycznie 

public class Dices
{
    public static readonly Dice add0 = new D0();
    public static readonly Dice d5 = new D5();
    public static readonly Dice d10 = new D10();
    public static readonly Dice d10Premium = new D10Premium();
    public static readonly Dice d50 = new D50();
    public static readonly Dice d100 = new D100();
    public static readonly Dice d76100 = new D76100();

Kod jest o niebo lepszy od mojego ostatniego podejścia do problemu implementacji kostek i rzucania nimi (ze 4 posty niżej pisałem o tym)

Na potrzeby zbierania wyników, których  sumy tworzą cechy widoczne na karcie postaci, utworzyłem specjalny obiekt rozszerzający typ Dictionary. Dało mi to eleganckie obejście problemu związanego z wiedza skąd biorą się wartości, z których powstają cechy. Obiekt na starcie buduje definicje cech jakie można losować w jego zakresie. Zachowuję przy tym kolejność dodawania składników pochodzących od profesji, rasy itp. co znacznie ułatwia obliczenia i szukanie ewentualnych błędów. Rozdzielenie odpowiedzialności wymusiło, że powstały dwa: jeden dla cech postaci a jeden dla odporności.

Mój stary kod w javascript radził sobie z tym elementem o wiele, wiele gorzej.

public class RessRollsCollection : Dictionary<StatDefinition.StatName, List<int>>
{
    public RessRollsCollection()
    {
        Init();
    }

    private void Init()
    {

        this.Add(StatDefinition.StatName.BM, new());
        this.Add(StatDefinition.StatName.ILLUSION, new());
        this.Add(StatDefinition.StatName.SUGESTION, new());
        this.Add(StatDefinition.StatName.CURSES, new());
        this.Add(StatDefinition.StatName.SHOCK, new());
        this.Add(StatDefinition.StatName.SPECIAL, new());
        this.Add(StatDefinition.StatName.BP, new());
        this.Add(StatDefinition.StatName.POISON, new());
        this.Add(StatDefinition.StatName.FUMES, new());
        this.Add(StatDefinition.StatName.TEMPERATURES, new());
        this.Add(StatDefinition.StatName.ELECTRITY, new());
        this.Add(StatDefinition.StatName.PETRIFICATION, new());
    }
public void AddStat(StatDefinition.StatName key, int value)
    {
        if (this.TryGetValue(key, out List<int> values))
        {
            values.Add(value);
        }
        else
        {
            Console.WriteLine($"Klucz: {key} nie istnieje.");
        }
    }
public int GetResistanceValue(StatDefinition.StatName searchedStat)
    {
        int computedStat = 0;
        if (this.TryGetValue(searchedStat, out List<int> values))
        {
            foreach (int number in values)
            {
                computedStat += number;
            }
        }
        return computedStat;
    }

Wyliczenie cech na poziomie karty postaci przekształciło się z prostackiego przypisania 

rolledHero.TEMPERATURES= 10 

do sumy zliczanej z pojedynczych elementów z wskazanych w obiekcie kolekcji.

   public int TEMPERATURES
    {
        get { return ResCollection.GetResistanceValue(StatDefinition.StatName.TEMPERATURES); }
    }

Nie zależy mi jakoś na wydajności rozwiązania a iterowania w koło Wojtek po kolekcjach cech i odporności niekoniecznie mi przeszkadza.

Na potrzeby odczytania jsonów powstał generyczny wczytywacz :)

        public static class JsonLoader
        {
            public static T LoadJson<T>(string filepath)
            {
                try
                {
                    string json = File.ReadAllText(filepath);
                    return JsonSerializer.Deserialize<T>(
                        json,
                        new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
                    );
                }
                catch (FileNotFoundException)
                {
                    Console.WriteLine($"❌ Plik nie znaleziony: {filepath}");
                }
                catch (JsonException ex)
                {
                    Console.WriteLine($"❌ Błąd JSON w pliku {filepath}: {ex.Message}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"❌ Inny błąd: {ex.Message}");
                }

                return default;
            }
        }


Piszę i pisze o c# a co z godot?

Cóż.. mało go tutaj: D 

Zaledwie kilka scen zlepianych w całość. Same w sobie nie są niczym nadzwyczajnym, zwykły TabContainer i trochę marginesów z labelkami i listami wyboru... Jak zacznę dodawać do nich jakiś sensowny wygląd napiszę więcej, a teraz po prostu nie ma za bardzo o czym.


No dobra coś ciekawego było. Przez jakiś czas ekran dwa razy ładował mi konfigurację co powodowało dublowanie wyników w listach rozwijalnych. Linijka kodu poniżej (z 56 linii) pozwoliła mi ustalić skąd startowany jest ekran.



Okazało się, że pomimo usunięcia ze sceny przycisku, którym przez jakiś ręcznie ładowałem konfigurację to tenże button nadal był widoczny, ale tylko w tekstowej definicji sceny w pliku tscn, Nie był widoczny w drzewie i na ekranie w trybie edycji w godot. Miał też dalej podpiętą akcję ładowania zawartości mechanizmu losowania jsonów i do tego (tu już nie wiem jak... nie wnikałem) restartował ekran.

Tyle!