Ocena wątku:
  • 2 głosów - średnia: 5
  • 1
  • 2
  • 3
  • 4
  • 5
Inne Tworzenie NewGRF w NML #1 - drezyna WM-15A.
#1
Cool 
Poniższy tutorial jest perwszym z cuklu tutoriali opisujących tworzenie dodatków NewGRF z wykorzystaniem specjalnie stworzonego w tym celu języka programowania - NML (NewGRF Meta Language). Pełną specyfikację tego języka można znaleźć w wiki: http://newgrf-specs.tt-wiki.net/wiki/NML:Main . Tam też opisane są wszystkie programy i narzędzia potrzebne do rozpoczęcia pracy: http://newgrf-specs.tt-wiki.net/wiki/NML...ng_started . Wszystkie tutoriale będą zgodne ze specyfikacją GRFv8, obsługiwaną od wersji 1.2.0 OTTD.

Zaczynamy!
W pierwszym tutorialu opiszę sposób tworzenia małego dodatku NewGRF, zawierającego dwa nowe pojazdy kolejowe. Wybór padł na... drezynę motorową WM-15A oraz jej przyczepę PWM-15. Są to małe pojazdy szynowe, które oczywiście nie będą miały większego znaczenia ekonomicznego w grze, niemniej stanowią one fajną wprawkę w programowaniu GRF-ów. Tongue
Na przykładzie tej drezyny pokażę podstawowe reguły tworzenia pojazdów, a także nieco bardziej zaawansowane metody, pozwalające np. na dodanie prostej logiki, kontrolującej zachowanie pojazdów w grze.

Szablony graficzne
Projektowanie każdego nowego pojazdu najlepiej zacząć od przygotowania podstawowych grafik i zakodowania ich w postaci sprite-ów. Bez względu na planowaną ilość obrazków, sprite-y te powinno się kodować z wykorzystaniem tzw. template-ów języka NML. Wynika to z faktu, że późniejsze "odpluskwianie" GRF-a i ręczne korygowanie offsetów dla pojedynczych sprite-ów może się okazać zdecydowanie bardziej pracochłonne niż poświęcenie paru minut na dopasowanie swoich grafik do gotowego szablonu.
W Internecie można znaleźć różne przykłady szablonów, niestety nieco różniących się między sobą. Dlatego na ich podstawie oraz na podstawie różnych testów z OpenTTD opracowałem własny szablon, moim zdaniem najwygodniejszy w użyciu. Szablon uwzględnia różne długości taboru - od długości maksymalnej (8 jednostek) aż do wersji skróconej do 5 jednostek. Poprzez analogię można utworzyć także szablony dla taboru o jeszcze mniejszej długości (1 - 4 jednostek), jednak projektowanie tak krótkich pojazdów mija się już chyba z sensem...
W starej specyfikacji (GRFv7 i wcześniejszych) długości pojazdów podawało się jako części ułamkowe standardowego pojazdu, stąd na moich rysunkach często występują jeszcze opisy: 8/8, 7/8, 6/8, itp., co po prostu oznacza odpowiednio długość 8, 7 i 6 jednostek.

[Obrazek: 480template8_7_6_5.png]

Kolorowe bryły, widoczne na poszczególnych rzutach to granice, w których musi zmieścić się rysunek taboru. Aby zaprojektowane wagony stały później na torze w jednej linii i nie odstawały na boki, należy uwzględnić także kilka dodatkowych zasad. W przedstawionym szablonie najniższy rząd pixeli jest przewidziany na koła bądź inną formę podwozia (mono-rail, maglev). Drugi rząd pixeli może stanowić ramę podwozia wagonu (wagony towarowe, np. platformy) lub pierwszy rząd ściany wagonu osobowego. W rzucie z boku należy też uwzględnić na obu końcach wagonu po jednym wolnym pixelu na zderzaki lub gumowe przejścia międzywagonowe. Natomiast w rzutach skośnych należy uwzględnić odpowiednie obniżenia krawędzi dachu dla wagonów z zaokrąglonym dachem.

Zaletą powyższego szablonu graficznego jest to, że kod template-u w języku NML do definiowania sprite-ów jest wspólny dla wszystkich długości taboru. Wygląda on następująco:

Kod:
template template_sprite_train(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         12,        28,          -5,        -16]
    [x+ 16,      y,         26,        28,         -17,        -18]
    [x+ 46,      y,         36,        20,         -17,        -14]
    [x+ 86,      y,         26,        28,          -7,        -18]
    [x+120,      y,         12,        28,          -5,        -16]
    [x+136,      y,         26,        28,         -17,        -18]
    [x+166,      y,         36,        20,         -17,        -14]
    [x+206,      y,         26,        28,          -7,        -18]
}

template template_sprite_purchase(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         50,        12,         -25,         -8]
}
(x, y) - współrzędne lewego górnego narożnika szablonu w pliku graficznym.

Obcięcie zbędnego nadmiaru tła, występującego w przedstawionym szablonie, można dokonać automatycznie, dodając dyrektywę "-c" podczas kompilacji.

c.d.n.
#2
Zasady projektowania grafiki OTTD
Paleta barw
Wszystkie podstawowe grafiki do OpenTTD muszą być wykonane w 8-bitowej (256-kolorowej) palecie barw, a konkretnie w jednej z dwóch, ściśle określonych palet: DOS lub Windows ( http://newgrf-specs.tt-wiki.net/wiki/Pal...oordinates ):

[Obrazek: Dospal.png] [Obrazek: Windowspal.png]

Istotną cechą obu tych palet jest to, że niektóre przedziały kolorów (a dokładniej ich indeksów) są zastrzeżone lub pełnią specjalne funkcje. Do indeksów zastrzeżonych, których nie wolno używać do rysowania, zaliczają się wszystkie bloki oznaczone na różowo, np. opisane jako "WinAPI". Natomiast przydatnym blokiem o znaczeniu specjalnym jest przedział indeksów oznaczony jako "Company". Fragmenty rysunku, które zostaną narysowane kolorami z tego zakresu, w trakcie gry zostaną zastąpione kolorem danego gracza. Pozostałe wyróżnione bloki pełnią funkcje animacyjne.
To, którą paletę użyjemy, zależy wyłącznie od nas. W Internecie można jednak znaleźć opinie, że praktyczniejsza jest paleta DOS.

Światłocień
Rysując poszczególne widoki pojazdów lub domów, należy zwrócić uwagę na kierunek padania światła. W OpenTTD światło pada z prawej strony, lekko z góry - widać to chociażby po oświetleniu nierówności terenu. W związku z tym, nasze modele również powinny odwzorowywać taki kierunek oświetlenia. W szczególności, ściany odwrócone w prawo powinny być jaśniejsze, zaś ściany skierowane w lewo powinny być pogrążone w cieniu.
Właściwe odwzorowanie światłocienia komplikuje nieco rysowanie pociągów lub samochodów, gdyż do tworzenia kolejnych rzutów nie wystarczy zwykłe lustrzane odbicie...

Formaty plików
Pobranie lub zaimportowanie palety najprościej dokonać, wczytując ją z innego, gotowego obrazka. Przedstawiony w poprzednim poście szablon dla taboru ma już ustawioną gotową paletę DOS. Można więc w nim od razu rysować pojazdy, bez obawy o niezgodność kolorów.
W trakcie rysowania należy pamiętać, aby zapisywać gotowe rysunki w formacie obsługującym palety kolorów, jak np. BMP lub PNG. Format JPG nie obsługuje palet! Należy też przypilnować, aby używany do rysowania program graficzny nie dokonał optymalizacji palety w trakcie zapisu obrazu.

c.d.n.
#3
Drezyna WM-15A - wersja podstawowa...
Korzystając z przedstawionych wcześniej zasad narysowałem widoki drezyny WM-15A oraz towarzyszącej jej często przyczepy PWM-15. Grafiki zostały przygotowane w oparciu o opracowane wcześniej szablony, wykorzystujące paletę DOS. Pasek pod oknami kabiny został narysowany z użyciem kolorów o indeksach z grupy "Company", w grze będzie więc przybierał kolory gracza:

[Obrazek: 244wm15a.png]

Dla tak przygotowanego rysunku należy następnie zdefiniować w języku NML zestawy sprite-ów, opisujące poszczególne pojazdy. Do tego celu wykorzystamy template-y, zdefiniowane w jednym z wcześniejszych postów:
Kod:
spriteset(spriteset_wm15a_empty, "gfx/wm15a.png") {
    template_sprite_train(4, 20)
}
spriteset(spriteset_wm15a_purchase, "gfx/wm15a.png") {
    template_sprite_purchase(4, 100)
}
spriteset(spriteset_pwm15_empty, "gfx/wm15a.png") {
    template_sprite_train(4, 60)
}
spriteset(spriteset_pwm15_purchase, "gfx/wm15a.png") {
    template_sprite_purchase(4, 120)
}

Spriteset-y to zestawy sprite-ów, wykorzystywanych przez grę do wyświetlenia pojazdu. Typowo jest to 8 sprite-ów, ukazujących pojazd ze wszystkich stron. W przypadku pojazdów symetrycznych, w dodatku rozrysowanych na szablonie 8/8, zestaw sprite-ów może zostać ograniczony do 4 widoków. W naszym przypadku zestaw 8 widoków drezyny jest wczytywany za pomocą jednego wywołania template-u template_sprite_train.
Pojedyncze sprite-y, które mają w nazwie "_purchase" to ikonki, wyświetlane w menu kupowania nowego taboru. Sprite-y te są opcjonalne. Mogą być wykorzystane np. do wyświetlenia dodatkowych informacji o pojeździe.

Kolejnym krokiem jest zdefiniowanie w NML pojazdu oraz zaprogramowanie jego parametrów. Kod NML, definiujący drezynę WM-15A wygląda następująco:
Kod:
item(FEAT_TRAINS, item_wm15a) {
    property {
        /* common properties */
        name:                           string(STR_WM15A_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1977, 1, 1);
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   20;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_PASSENGERS, CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
        non_refittable_cargo_classes:   bitmask(CC_REFRIGERATED, CC_LIQUID);
        cargo_allow_refit:              [];
        cargo_disallow_refit:           [];
        loading_speed:                  5;
        cost_factor:                    10;
        running_cost_factor:            18;
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          80 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_CARGO;
        power:                          147 kW;
        running_cost_base:              RUNNING_COST_DIESEL;
        dual_headed:                    0;
        cargo_capacity:                 15;
        weight:                         20 ton;
        ai_engine_rank:                 0; // not intended to be used by the ai
        engine_class:                   ENGINE_CLASS_DIESEL;
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3;
        air_drag_coefficient:           0.1;
        length:                         8;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DIESEL, -2, DISABLE_WAGON_POWER);
        bitmask_vehicle_info:           0;
    }
    graphics {
        default:            spriteset_wm15a_empty;
        purchase:           spriteset_wm15a_purchase;
    }
}

W sekcji property znajduje się lista parametrów, definiujących właściwości nowego pojazdu. Przyglądając się kodowi, warto zwrócić uwagę na parę rzeczy. Jedną z nich jest sposób zdefiniowania listy obsługiwanych ładunków. Konfiguracja ładunków jest dokonana wyłącznie przy użyciu tzw. klas towarów, bez wymieniania ich z nazwy. Zgodnie z zapisem, drezyna będzie bardzo uniwersalnym pojazdem. Będzie ona mogła wozić zarówno pasażerów (jakoś się wcisną do małej kabiny Smile), pocztę, jak i różne towary, określane ogólnie "drobnicą", lecz z wykluczeniem towarów wymagających przewożenia w chłodniach oraz ciekłych. Taka ogólna konfiguracja pozwala na obsługę również innych towarów niż występujące domyślnie w OTTD, np. towarów wprowadzonych do gry przez inne dodatki.
Pełną listę oraz opis dostępnych parametrów pojazdów można znaleźć tutaj: http://newgrf-specs.tt-wiki.net/wiki/NML...icle_types .

W sekcji graphics zdefiniowane są natomiast zestawy sprite-ów, wymaganych do wyświetlenia pojazdu na ekranie. W najprostszej wersji wymagany jest tylko jeden zestaw: default.

W podobny sposób została zdefiniowana przyczepka PWM-15:
Kod:
item(FEAT_TRAINS, item_pwm15) {
    property {
        /* common properties */
        name:                           string(STR_PWM15_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1977, 1, 1);
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   20;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
        non_refittable_cargo_classes:   bitmask(CC_REFRIGERATED, CC_LIQUID);
        cargo_allow_refit:              [];
        cargo_disallow_refit:           [];
        loading_speed:                  5;
        cost_factor:                    130;
        running_cost_factor:            1;
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          80 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_CARGO;
        power:                          0;
        running_cost_base:              RUNNING_COST_DIESEL;
        cargo_capacity:                 15;
        weight:                         12 ton;
        ai_engine_rank:                 0; // not intended to be used by the ai
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3;
        air_drag_coefficient:           0.1;
        length:                         6;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DISABLE, 0, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:         0 ton;
        bitmask_vehicle_info:           0;
    }
    graphics {
        default:            spriteset_pwm15_empty;
        purchase:           spriteset_pwm15_purchase;
    }
}

Różnica, czy dany pojazd będzie traktowany przez grę jako wagon czy lokomotywa, wynika w zasadzie tylko z jednego parametru. W przypadku wagonu power = 0;
Niektóre parametry są w przypadku wagonu nieistotne lub mają trochę inne znaczenie. Przykładowo, parametr speed, który dla lokomotywy określa jej maksymalną prędkość, dla wagonu określa jego limit prędkości, spowalniający cały pociąg...

Niezależnie od powyższych różnic, przyczepa PWM15 ma ustawioną inną długość (6 jednostek), gdyż została zaprojektowana na szablonie 6/8. Została też pozbawiona (z oczywistych powodów) możliwości przewożenia pasażerów.

Na koniec jeszcze jedna praktyczna porada. Tworząc nowy dodatek GRF warto umieścić też w jego kodzie, w miejscu poprzedzającym wszelkie definicje spite'ów i pojazdów, niewielki fragment kodu, odpowiedzialny za prawidłowe wyświetlanie sprite'ów w menu zakupu pojazdów oraz w zajezdni:
Kod:
train_width_32_px = 1;
traininfo_y_offset = 2;

Kompletny kod dodatku
Poniżej zamieszczam pełną treść plików, niezbędnych do skompilowania dodatku:
custom_tags.txt
Kod:
VERSION     :0.1.0
TITLE       :WM-15A

lang/english.lng
Kod:
##grflangid 0x01
# This is the English language file

# GRF name and description
STR_GRF_NAME        :{TITLE} {VERSION}
STR_GRF_DESCRIPTION :{TITLE} - the most known polish trolley.{}{COPYRIGHT}2011 Tadeusz Domagalski{}License: GPL v2

STR_WM15A_NAME      :Trolley WM-15A (Diesel)
STR_PWM15_NAME      :Trailer PWM-15

lang/polish.lng
Kod:
##grflangid 0x30
# Polska wersja językowa

# Nazwa GRF oraz opis
STR_GRF_NAME        :{TITLE} {VERSION}
STR_GRF_DESCRIPTION :{TITLE} - popularna polska drezyna.{}{COPYRIGHT}2011 Tadeusz Domagalski{}License: GPL v2

STR_WM15A_NAME      :Drezyna WM-15A (Diesel)
STR_PWM15_NAME      :Przyczepa PWM-15

wm15a.nml
Kod:
grf {
    grfid: "TD\01\01";
    name: string(STR_GRF_NAME);
    desc: string(STR_GRF_DESCRIPTION);
    version: 0;
    min_compatible_version: 0;
}

/* graphics definition */
template template_sprite_train(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         12,        28,          -5,        -16]
    [x+ 16,      y,         26,        28,         -17,        -18]
    [x+ 46,      y,         36,        20,         -17,        -14]
    [x+ 86,      y,         26,        28,          -7,        -18]
    [x+120,      y,         12,        28,          -5,        -16]
    [x+136,      y,         26,        28,         -17,        -18]
    [x+166,      y,         36,        20,         -17,        -14]
    [x+206,      y,         26,        28,          -7,        -18]
}
template template_sprite_purchase(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         50,        12,         -25,         -8]
}

train_width_32_px = 1;
traininfo_y_offset = 2;

/* empty sprites */
spriteset(spriteset_wm15a_empty, "gfx/wm15a.png") {
    template_sprite_train(4, 20)
}
spriteset(spriteset_wm15a_purchase, "gfx/wm15a.png") {
    template_sprite_purchase(4, 100)
}
spriteset(spriteset_pwm15_empty, "gfx/wm15a.png") {
    template_sprite_train(4, 60)
}
spriteset(spriteset_pwm15_purchase, "gfx/wm15a.png") {
    template_sprite_purchase(4, 120)
}

/* Define the actual train */
item(FEAT_TRAINS, item_wm15a) {
    property {
        /* common properties */
        name:                           string(STR_WM15A_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1977, 1, 1);
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   20;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_PASSENGERS, CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
        non_refittable_cargo_classes:   bitmask(CC_REFRIGERATED, CC_LIQUID);
        cargo_allow_refit:              [];
        cargo_disallow_refit:           [];
        loading_speed:                  5;
        cost_factor:                    10;
        running_cost_factor:            18;
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          80 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_CARGO;
        power:                          147 kW;
        running_cost_base:              RUNNING_COST_DIESEL;
        dual_headed:                    0;
        cargo_capacity:                 15;
        weight:                         20 ton;
        ai_engine_rank:                 0; // not intended to be used by the ai
        engine_class:                   ENGINE_CLASS_DIESEL;
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3;
        air_drag_coefficient:           0.1;
        length:                         8;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DIESEL, -2, DISABLE_WAGON_POWER);
        bitmask_vehicle_info:           0;
    }
    graphics {
        default:            spriteset_wm15a_empty;
        purchase:           spriteset_wm15a_purchase;
    }
}

item(FEAT_TRAINS, item_pwm15) {
    property {
        /* common properties */
        name:                           string(STR_PWM15_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1977, 1, 1);
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   20;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
        non_refittable_cargo_classes:   bitmask(CC_REFRIGERATED, CC_LIQUID);
        cargo_allow_refit:              [];
        cargo_disallow_refit:           [];
        loading_speed:                  5;
        cost_factor:                    130;
        running_cost_factor:            1;
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          80 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_CARGO;
        power:                          0;
        running_cost_base:              RUNNING_COST_DIESEL;
        cargo_capacity:                 15;
        weight:                         12 ton;
        ai_engine_rank:                 0; // not intended to be used by the ai
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3;
        air_drag_coefficient:           0.1;
        length:                         6;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DISABLE, 0, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:         0 ton;
        bitmask_vehicle_info:           0;
    }
    graphics {
        default:            spriteset_pwm15_empty;
        purchase:           spriteset_pwm15_purchase;
    }
}

Po skompilowaniu dodatku do formatu GRF i przekopiowaniu go do katalogu ...\content_download\newgrf, można się już cieszyć nowymi pojazdami, które są dostępne z menu zajezdni jako nowa lokomotywa i nowy wagon.Tongue

c.d.n.
#4
Drezyna WM-15A - dodanie logiki działania...
Zaprezentowana w poprzednim poście drezyna jeździ i wozi towary, jednak jej zachowanie nie do końca odpowiada rzeczywistości...Sad
Pierwszym mankamentem jest to, że drezyna oraz jej przyczepka są "widziane" przez program jako zwykła lokomotywa i wagon. Można więc je łączyć w długie składy, w dodatku wymieszane z innymi wagonami. Drugim problemem jest niewłaściwa ilość przewożonych towarów, wynikająca z użycia przez OpenTTD domyślnych przeliczników sztuk na tony i odwrotnie.
Oba powyższe problemy można rozwiązać stosując mechanizm tzw. callback-ów i switch-y, czyli pewnego rodzaju funkcji, za pośrednictwem których gra "dowiaduje się", jak ma wyglądać i zachowywać się nasz pojazd w pewnych określonych przypadkach.

Ograniczenie długości i składu pociągu
Problem mieszanych i za długich składów pociągów można rozwiązać definiując w kodzie NML następujące switch-e:
Kod:
switch(FEAT_TRAINS, SELF, switch_wm15a_can_attach, vehicle_type_id) {
    item_pwm15:  return CB_RESULT_ATTACH_ALLOW;
    return string(STR_WM15A_ATTACH_DISALLOW);
}
switch (FEAT_TRAINS, SELF, switch_wm15a_start_stop, num_vehs_in_consist) {
    1..2: return CB_RESULT_NO_TEXT;
    return string(STR_WM15A_CANNOT_START);
}

...które należy następnie podpiąć pod odpowiednie callback-i w sekcji graphics projektowanej drezyny:
Kod:
graphics {
        purchase:             spriteset_wm15a_purchase;
        default:              spriteset_wm15a_empty;
        can_attach_wagon:     switch_wm15a_can_attach;    // <- dodane
        start_stop:           switch_wm15a_start_stop;    // <- dodane
}

Wyjaśnię teraz, jak to działa. Callback can_attach_wagon jest wywoływany przez OTTD za każdym razem, gdy gracz w oknie zajezdni próbuje dołączyć wagon do lokomotywy. Wywołanie tego callback-a powoduje uruchomienie switch-a switch_wm15a_can_attach, który sprawdza identyfikator dołączanego wagonu (vehicle_type_id); Jeśli dołączanym wagonem jest przyczepa WM15, wszystko będzie OK. W przeciwnym wypadku zwrócony zostanie komunikat o błędzie:

[Obrazek: 721blad_typu.png]

Drugi z użytych callback-ów (start_stop) jest wywoływany przez OTTD m.in. w momencie, gdy gracz próbuje wypuścić pociąg z zajezdni. W powyższym przykładzie uruchamiany jest switch switch_wm15a_start_stop, który sprawdza długość pociągu (num_vehs_in_consist). Jeśli długość mieści się w zakresie 1-2 pojazdów, pociąg zostanie wypuszczany. W przeciwnym wypadku zwrócony zostanie komunikat błędu:

[Obrazek: 833blad_dlugosci.png]

Ustawienie właściwej pojemności
Problem niewłaściwej ilości wożonych towarów wynika z domyślnego mechanizmu przeliczania przez OTTD sztuk na tony i odwrotnie. Parametr cargo_capacity, określający pojemność wagonu, odnosi się bowiem zawsze do pierwszego w kolejności typu wożonego ładunku. W przypadku drezyny WM-15A definicja:
Kod:
refittable_cargo_classes:       bitmask(CC_PASSENGERS, CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
...
cargo_capacity:                 15;
...oznacza więc 15 pasażerów, a nie 15 ton ładunku.
W momencie przebudowy drezyny do wożenia innych towarów, gra przeliczy pojemność wagonu z 15 pasażerów na...zaledwie 7 ton ładunku. Tymczasem prawdziwa WM-15A może przewieźć aż 15 ton ładunku, lecz pasażerów w kabinie zmieści się zaledwie 7-miu, czyli zupełnie odwrotnie niż policzyła gra...

Aby temu zaradzić, należy poinformować grę o tym, jak ma traktować poszczególne rodzaje ładunku. W tym celu należy jednak już operować identyfikatorami konkretnych towarów, a nie jak do tej pory - ogólnymi klasami towarów. Wynika to z faktu, że nawet towary z jednej klasy mogą mieć różne jednostki miary.
Aby wprowadzić indywidualną obsługę towarów, najpierw trzeba zdefiniować tzw. tabelę towarów (cargotable), w której należy wyliczyć wszystkie towary, które będą traktowane przez nasz NewGRF w szczególny sposób (pełną listę identyfikatorów towarów można znaleźć tutaj: http://newgrf-specs.tt-wiki.net/wiki/Car...rgo_Labels ):
Kod:
cargotable {
    PASS, TOUR, MAIL
}

Następnie należy zdefiniować switch, który na podstawie informacji o aktualnie przewożonym ładunku (cargo_type_in_veh) będzie zwracał poprawną pojemność pojazdu. W przypadku drezyny będzie to 7 pasażerów(lub turystów), 30 paczek poczty lub 15 sztuk/skrzyń/ton dowolnego innego towaru:
Kod:
switch (FEAT_TRAINS, SELF, switch_wm15a_capacity, cargo_type_in_veh) {
    PASS..TOUR: return 7;
    MAIL: return 30;
    return 15;
}

Aby właściwe wyliczanie pojemności drezyny działało prawidłowo, należy jeszcze podpiąć napisany wyżej switch pod odpowiedni callback w sekcji graphics naszej drezyny:
Kod:
graphics {
        purchase:             spriteset_wm15a_purchase;
        default:              spriteset_wm15a_empty;
        can_attach_wagon:     switch_wm15a_can_attach;
        start_stop:           switch_wm15a_start_stop;
        cargo_capacity:       switch_wm15a_capacity;    // <- dodane
}

Analogiczne zmiany w obsłudze towarów należy również wprowadzić dla przyczepy PMW-15.

Kompletny kod NewGRF
Poniżej zamieszczam treść plików, które zmieniły się w wyniku wprowadzenia opisanych zmian:
lang/english.lng
Kod:
##grflangid 0x01
# This is the English language file

# GRF name and description
STR_GRF_NAME        :{TITLE} {VERSION}
STR_GRF_DESCRIPTION :{TITLE} - the most known polish trolley.{}{COPYRIGHT}2011 Tadeusz Domagalski{}License: GPL v2

STR_WM15A_NAME      :Trolley WM-15A (Diesel)
STR_PWM15_NAME      :Trailer PWM-15
STR_WM15A_CANNOT_START      :Train too long! Only 1 carriage allowed.
STR_WM15A_ATTACH_DISALLOW   :Only PWM-15 can be attached to WM-15A.

lang/polish.lng
Kod:
##grflangid 0x30
# Polska wersja językowa

# Nazwa GRF oraz opis
STR_GRF_NAME        :{TITLE} {VERSION}
STR_GRF_DESCRIPTION :{TITLE} - popularna polska drezyna.{}{COPYRIGHT}2011 Tadeusz Domagalski{}License: GPL v2

STR_WM15A_NAME      :Drezyna WM-15A (Diesel)
STR_PWM15_NAME      :Przyczepa PWM-15
STR_WM15A_CANNOT_START      :Pociąg jest za długi! Drezyna WM-15A może uciągnąć tylko 1 wagon.
STR_WM15A_ATTACH_DISALLOW   :Drezyna WM-15A może współpracować tylko z przyczepą PWM-15.

wm15a.nml
Kod:
grf {
    grfid: "TD\01\01";
    name: string(STR_GRF_NAME);
    desc: string(STR_GRF_DESCRIPTION);
    version: 0;
    min_compatible_version: 0;
}

/* graphics definition */
template template_sprite_train(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         12,        28,          -5,        -16]
    [x+ 16,      y,         26,        28,         -17,        -18]
    [x+ 46,      y,         36,        20,         -17,        -14]
    [x+ 86,      y,         26,        28,          -7,        -18]
    [x+120,      y,         12,        28,          -5,        -16]
    [x+136,      y,         26,        28,         -17,        -18]
    [x+166,      y,         36,        20,         -17,        -14]
    [x+206,      y,         26,        28,          -7,        -18]
}
template template_sprite_purchase(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         50,        12,         -25,         -8]
}

train_width_32_px = 1;
traininfo_y_offset = 2;

/* empty sprites */
spriteset(spriteset_wm15a_empty, "gfx/wm15a.png") {
    template_sprite_train(4, 20)
}
spriteset(spriteset_wm15a_purchase, "gfx/wm15a.png") {
    template_sprite_purchase(4, 100)
}
spriteset(spriteset_pwm15_empty, "gfx/wm15a.png") {
    template_sprite_train(4, 60)
}
spriteset(spriteset_pwm15_purchase, "gfx/wm15a.png") {
    template_sprite_purchase(4, 120)
}

/* cargos */
cargotable {
    PASS, TOUR, MAIL
}

/* switches */
switch(FEAT_TRAINS, SELF, switch_wm15a_can_attach, vehicle_type_id) {
    item_pwm15:  return CB_RESULT_ATTACH_ALLOW;
    return string(STR_WM15A_ATTACH_DISALLOW);
}
switch (FEAT_TRAINS, SELF, switch_wm15a_start_stop, num_vehs_in_consist) {
    1..2: return CB_RESULT_NO_TEXT;
    return string(STR_WM15A_CANNOT_START);
}
switch (FEAT_TRAINS, SELF, switch_wm15a_capacity, cargo_type_in_veh) {
    PASS..TOUR: return 7;
    MAIL: return 30;
    return 15;
}switch (FEAT_TRAINS, SELF, switch_pwm15_capacity, cargo_type_in_veh) {
    MAIL: return 30;
    return 15;
}
/* Define the actual train */
item(FEAT_TRAINS, item_wm15a) {
    property {
        /* common properties */
        name:                           string(STR_WM15A_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1977, 1, 1);
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   20;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_PASSENGERS, CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
        non_refittable_cargo_classes:   bitmask(CC_REFRIGERATED, CC_LIQUID);
        cargo_allow_refit:              [];
        cargo_disallow_refit:           [];
        loading_speed:                  5;
        cost_factor:                    9;
        running_cost_factor:            18;
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          80 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_CARGO;
        power:                          147 kW;
        running_cost_base:              RUNNING_COST_DIESEL;
        dual_headed:                    0;
        cargo_capacity:                 15;
        weight:                         20 ton;
        ai_engine_rank:                 0; // not intended to be used by the ai
        engine_class:                   ENGINE_CLASS_DIESEL;
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3;
        air_drag_coefficient:           0.1;
        length:                         8;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DIESEL, -2, DISABLE_WAGON_POWER);
        bitmask_vehicle_info:           0;
    }
    graphics {
        default:            spriteset_wm15a_empty;
        purchase:           spriteset_wm15a_purchase;
        can_attach_wagon:   switch_wm15a_can_attach;
        start_stop:         switch_wm15a_start_stop;
        cargo_capacity:     switch_wm15a_capacity;
    }
}

item(FEAT_TRAINS, item_pwm15) {
    property {
        /* common properties */
        name:                           string(STR_PWM15_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1977, 1, 1);
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   20;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
        non_refittable_cargo_classes:   bitmask(CC_REFRIGERATED, CC_LIQUID);
        cargo_allow_refit:              [];
        cargo_disallow_refit:           [];
        loading_speed:                  5;
        cost_factor:                    130;
        running_cost_factor:            1;
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          80 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_CARGO;
        power:                          0;
        running_cost_base:              RUNNING_COST_DIESEL;
        cargo_capacity:                 15;
        weight:                         12 ton;
        ai_engine_rank:                 0; // not intended to be used by the ai
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3;
        air_drag_coefficient:           0.1;
        length:                         6;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DISABLE, 0, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:         0 ton;
        bitmask_vehicle_info:           0;
    }
    graphics {
        default:            spriteset_pwm15_empty;
        purchase:           spriteset_pwm15_purchase;
        cargo_capacity:     switch_pwm15_capacity;
    }
}
#5
Kontynuując wątek tworzenia drezyny WM-15A, przedstawiam opis kolejnego etapu prac.

Projekt drezyny - wizualizacja towarów
Kolejnym etapem rozwoju projektu drezyny WM-15A jest dodanie wizualizacji przewożonych ładunków. Dotychczas bowiem drezyna była widoczna w grze cały czas jako pusta, nawet wtedy, kiedy była załadowana jakimś towarem.
Wprowadzenie takiej wizualizacji wymaga wcześniejszego opracowania dodatkowych kompletów grafik, przedstawiających pojazd w stanie załadowanym. W przypadku prezentowanej drezyny zestaw takich dodatkowych grafik jest bardzo obszerny, gdyż drezynę można przebudowywać do przewozu przeróżnych towarów:

[Obrazek: 308wm15a.png]

Powyższy zestaw grafik ukazuje drezynę wypełnioną towarami wynikającymi z obsługiwanej kombinacji (CC_PASSANGERS, CC_MAIL, CC_EXPRESS, CC_GOODS). Mamy więc: drewno, stal, żywiec i papier. Pozostałe ładunki wynikające z tej kombinacji (poczta i towary) oraz towary wprowadzone przez inne dodatki (FIRS, ECS, itp.) będą przedstawiane umownie w postaci uniwersalnych skrzyń. Pasażerowie (z oczywistych powodów) nie będą wizualizowani. Wink
Każdy komplet grafik przedstawia dodatkowo wagon załadowany w różnym stopniu: w około 50% oraz w 100%. Grafiki te będą wykorzystane m.in. do animacji procesu ładowania wagonu.

Uaktywnienie powyższych grafik wymaga zdefiniowania w kodzie NML wszystkich nowych spriteset-ów, przedstawiających ładunki, oraz połączenia ich w tzw. grupy sprite-ów (spritegroup), odpowiedzialnych za animacje. Grupa sprite-ów to obiekt, zawierający dwie listy spriteset-ów. Lista loading przedstawia sekwencję stanów pojazdu w trakcie jego załadunku/wyładunku. Natomiast lista loaded przedstawia stany pojazdu już pustego/załadowanego (będącego w trasie). Każda z obu list może zawierać dowolną liczbę spriteset-ów, przy czym pierwszy na liście odpowiada załadunkowi 0%, ostatni: 100%, a pośrednie: kolejnym stanom pośrednim załadunku. Definicja spritegroup-y dla jednego z przykładowych ładunków (towary) będzie wyglądała następująco:
Kod:
/* empty sprites */
spriteset(spriteset_wm15a_empty, "gfx/wm15a.png") {
    template_sprite_train(4, 20)
}
/* sprites with cargos */
spriteset(spriteset_wm15a_goods_half, "gfx/wm15a.png") {
    template_sprite_train(4, 80)
}
spriteset(spriteset_wm15a_goods_full, "gfx/wm15a.png") {
    template_sprite_train(4, 120)
}
spritegroup spritegroup_wm15a_goods {
    loaded:  [spriteset_wm15a_empty, spriteset_wm15a_goods_half, spriteset_wm15a_goods_full];
    loading: [spriteset_wm15a_empty, spriteset_wm15a_goods_half, spriteset_wm15a_goods_full];
}

Tak zdefiniowana przykładowa grupa sprite-ów będzie odpowiedzialna za animację, w której ilość widocznego ładunku będzie zależna od stopnia załadowania wagonu:
0 - 33%: wagon pusty,
33 - 66%: wagon z 1 skrzynią,
66 - 100%: wagon z 2 skrzyniami.
Takie przedziały animacji nie są jednak optymalne. Dla wagonu załadowanego np. w 67% bardziej bowiem pasowałby widok wagonu załadowanego w 50% niż w 100%.
Na szczęście "charakterystykę załadunku" można kształtować w dowolny sposób. I tak na przykład, zapis:
Kod:
spritegroup spritegroup_wm15a_goods {
    loaded:  [spriteset_wm15a_empty, spriteset_wm15a_goods_half, spriteset_wm15a_goods_half, spriteset_wm15a_goods_full];
    loading: [spriteset_wm15a_empty, spriteset_wm15a_goods_half, spriteset_wm15a_goods_half, spriteset_wm15a_goods_full];
}
... spowoduje, że zależność od stopnia załadowania wagonu będzie teraz wyglądała następująco:
0 - 25%: wagon pusty,
25 - 75%: wagon z 1 skrzynią,
75 - 100%: wagon z 2 skrzyniami.

Zdefiniowane spritegroup-y należy teraz powiązać z konkretnymi rodzajami ładunku. W tym celu konieczna jest rozbudowa naszej tabeli towarów o nowe wizualizowane towary:
Kod:
/* cargos */
cargotable {
    PASS, TOUR, MAIL,
    GOOD, WOOD, LVST, STEL, PAPR    // <- dodane
}

Wyświetlanie odpowiednich grafik można zrealizować za pośrednictwem switch-y. Jednak najprostszą metodą jest powiązanie towarów z grafikami bezpośrednio w sekcji graphics pojazdu:
Kod:
graphics {
        PASS:               spriteset_wm15a_empty;
        TOUR:               spriteset_wm15a_empty;
        WOOD:               spritegroup_wm15a_wood;
        LVST:               spritegroup_wm15a_lvstck;
        STEL:               spritegroup_wm15a_steel;
        PAPR:               spritegroup_wm15a_paper;
        default:            spritegroup_wm15a_goods;
        purchase:           spriteset_wm15a_purchase;
        can_attach_wagon:   switch_wm15a_can_attach;
        start_stop:         switch_wm15a_start_stop;
        cargo_capacity:     switch_wm15a_capacity;
    }
W przypadku pasażerów i turystów wyświetlany jest zawsze tylko jeden i ten sam spriteset, ukazujący drezynę z pustą paką. Dla innych wyszczególnionych ładunków wyświetlane są odpowiednie spritegroup-y. Natomiast w przypadku pozostałych ładunków (również tych nie wymienionych w GRF-ie) będzie wyświetlany uniwersalny spritegroup przedstawiający ładunek skrzyń.

Kompletny kod dodatku NewGRF
Poniżej zamieszczam pełną treść pliku nml, zawierającego wyżej opisane zmiany. Pozostałe pliki, prezentowane w poprzednim poście, nie uległy zmianie.

wm15a.nml
Kod:
grf {
    grfid: "TD\01\01";
    name: string(STR_GRF_NAME);
    desc: string(STR_GRF_DESCRIPTION);
    version: 0;
    min_compatible_version: 0;
}

/* graphics definition */
template template_sprite_train(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         10,        28,          -5,        -14]
    [x+ 16,      y,         26,        28,         -15,        -17]
    [x+ 46,      y,         36,        20,         -18,        -13]
    [x+ 86,      y,         26,        28,          -7,        -17]
    [x+120,      y,         10,        28,          -5,        -14]
    [x+136,      y,         26,        28,         -15,        -17]
    [x+166,      y,         36,        20,         -18,        -13]
    [x+206,      y,         26,        28,          -7,        -17]
}
template template_sprite_purchase(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         50,        12,         -25,         -6]
}

/* empty sprites */
spriteset(spriteset_wm15a_purchase, "gfx/wm15a.png") {
    template_sprite_purchase(4, 60)
}
spriteset(spriteset_wm15a_empty, "gfx/wm15a.png") {
    template_sprite_train(4, 20)
}
spriteset(spriteset_pwm15_purchase, "gfx/wm15a.png") {
    template_sprite_purchase(244, 60)
}
spriteset(spriteset_pwm15_empty, "gfx/wm15a.png") {
    template_sprite_train(244, 20)
}
/* sprites with cargos */
spriteset(spriteset_wm15a_goods_half, "gfx/wm15a.png") {
    template_sprite_train(4, 80)
}
spriteset(spriteset_wm15a_goods_full, "gfx/wm15a.png") {
    template_sprite_train(4, 120)
}
spriteset(spriteset_pwm15_goods_half, "gfx/wm15a.png") {
    template_sprite_train(244, 80)
}
spriteset(spriteset_pwm15_goods_full, "gfx/wm15a.png") {
    template_sprite_train(244, 120)
}
spritegroup spritegroup_wm15a_goods {
    loaded:  [spriteset_wm15a_empty, spriteset_wm15a_goods_half, spriteset_wm15a_goods_half, spriteset_wm15a_goods_full];
    loading: [spriteset_wm15a_empty, spriteset_wm15a_goods_half, spriteset_wm15a_goods_half, spriteset_wm15a_goods_full];
}
spritegroup spritegroup_pwm15_goods {
    loaded:  [spriteset_pwm15_empty, spriteset_pwm15_goods_half, spriteset_pwm15_goods_half, spriteset_pwm15_goods_full];
    loading: [spriteset_pwm15_empty, spriteset_pwm15_goods_half, spriteset_pwm15_goods_half, spriteset_pwm15_goods_full];
}
spriteset(spriteset_wm15a_wood_half, "gfx/wm15a.png") {
    template_sprite_train(4, 160)
}
spriteset(spriteset_wm15a_wood_full, "gfx/wm15a.png") {
    template_sprite_train(4, 200)
}
spriteset(spriteset_pwm15_wood_half, "gfx/wm15a.png") {
    template_sprite_train(244, 160)
}
spriteset(spriteset_pwm15_wood_full, "gfx/wm15a.png") {
    template_sprite_train(244, 200)
}
spritegroup spritegroup_wm15a_wood {
    loaded:  [spriteset_wm15a_empty, spriteset_wm15a_wood_half, spriteset_wm15a_wood_half, spriteset_wm15a_wood_full];
    loading: [spriteset_wm15a_empty, spriteset_wm15a_wood_half, spriteset_wm15a_wood_half, spriteset_wm15a_wood_full];
}
spritegroup spritegroup_pwm15_wood {
    loaded:  [spriteset_pwm15_empty, spriteset_pwm15_wood_half, spriteset_pwm15_wood_half, spriteset_pwm15_wood_full];
    loading: [spriteset_pwm15_empty, spriteset_pwm15_wood_half, spriteset_pwm15_wood_half, spriteset_pwm15_wood_full];
}
spriteset(spriteset_wm15a_steel_half, "gfx/wm15a.png") {
    template_sprite_train(4, 240)
}
spriteset(spriteset_wm15a_steel_full, "gfx/wm15a.png") {
    template_sprite_train(4, 280)
}
spriteset(spriteset_pwm15_steel_half, "gfx/wm15a.png") {
    template_sprite_train(244, 240)
}
spriteset(spriteset_pwm15_steel_full, "gfx/wm15a.png") {
    template_sprite_train(244, 280)
}
spritegroup spritegroup_wm15a_steel {
    loaded:  [spriteset_wm15a_empty, spriteset_wm15a_steel_half, spriteset_wm15a_steel_half, spriteset_wm15a_steel_full];
    loading: [spriteset_wm15a_empty, spriteset_wm15a_steel_half, spriteset_wm15a_steel_half, spriteset_wm15a_steel_full];
}
spritegroup spritegroup_pwm15_steel {
    loaded:  [spriteset_pwm15_empty, spriteset_pwm15_steel_half, spriteset_pwm15_steel_half, spriteset_pwm15_steel_full];
    loading: [spriteset_pwm15_empty, spriteset_pwm15_steel_half, spriteset_pwm15_steel_half, spriteset_pwm15_steel_full];
}
spriteset(spriteset_wm15a_lvstck_half, "gfx/wm15a.png") {
    template_sprite_train(4, 320)
}
spriteset(spriteset_wm15a_lvstck_full, "gfx/wm15a.png") {
    template_sprite_train(4, 360)
}
spriteset(spriteset_pwm15_lvstck_half, "gfx/wm15a.png") {
    template_sprite_train(244, 320)
}
spriteset(spriteset_pwm15_lvstck_full, "gfx/wm15a.png") {
    template_sprite_train(244, 360)
}
spritegroup spritegroup_wm15a_lvstck {
    loaded:  [spriteset_wm15a_empty, spriteset_wm15a_lvstck_half, spriteset_wm15a_lvstck_half, spriteset_wm15a_lvstck_full];
    loading: [spriteset_wm15a_empty, spriteset_wm15a_lvstck_half, spriteset_wm15a_lvstck_half, spriteset_wm15a_lvstck_full];
}
spritegroup spritegroup_pwm15_lvstck {
    loaded:  [spriteset_pwm15_empty, spriteset_pwm15_lvstck_half, spriteset_pwm15_lvstck_half, spriteset_pwm15_lvstck_full];
    loading: [spriteset_pwm15_empty, spriteset_pwm15_lvstck_half, spriteset_pwm15_lvstck_half, spriteset_pwm15_lvstck_full];
}
spriteset(spriteset_wm15a_paper_half, "gfx/wm15a.png") {
    template_sprite_train(4, 400)
}
spriteset(spriteset_wm15a_paper_full, "gfx/wm15a.png") {
    template_sprite_train(4, 440)
}
spriteset(spriteset_pwm15_paper_half, "gfx/wm15a.png") {
    template_sprite_train(244, 400)
}
spriteset(spriteset_pwm15_paper_full, "gfx/wm15a.png") {
    template_sprite_train(244, 440)
}
spritegroup spritegroup_wm15a_paper {
    loaded:  [spriteset_wm15a_empty, spriteset_wm15a_paper_half, spriteset_wm15a_paper_half, spriteset_wm15a_paper_full];
    loading: [spriteset_wm15a_empty, spriteset_wm15a_paper_half, spriteset_wm15a_paper_half, spriteset_wm15a_paper_full];
}
spritegroup spritegroup_pwm15_paper {
    loaded:  [spriteset_pwm15_empty, spriteset_pwm15_paper_half, spriteset_pwm15_paper_half, spriteset_pwm15_paper_full];
    loading: [spriteset_pwm15_empty, spriteset_pwm15_paper_half, spriteset_pwm15_paper_half, spriteset_pwm15_paper_full];
}

/* cargos */
cargotable {
    PASS, TOUR, MAIL,
    GOOD, WOOD, LVST, STEL, PAPR
}

/* switches */
switch(FEAT_TRAINS, SELF, switch_wm15a_can_attach, vehicle_type_id) {
    item_pwm15:  return CB_RESULT_ATTACH_ALLOW;
    return string(STR_WM15A_ATTACH_DISALLOW);
}
switch (FEAT_TRAINS, SELF, switch_wm15a_start_stop, num_vehs_in_consist) {
    1..2: return 0xFF;
    return string(STR_WM15A_CANNOT_START);
}
switch (FEAT_TRAINS, SELF, switch_wm15a_capacity, cargo_type_in_veh) {
    PASS..TOUR: return 7;
    MAIL: return 30;
    return 15;
}
switch (FEAT_TRAINS, SELF, switch_pwm15_capacity, cargo_type_in_veh) {
    MAIL: return 30;
    return 15;
}

/* Define the actual train */
item(FEAT_TRAINS, item_wm15a) {
    property {
        /* common properties */
        name:                           string(STR_WM15A_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1900, 1, 1);            // !!!
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   30;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_PASSENGERS, CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
        non_refittable_cargo_classes:   bitmask(CC_REFRIGERATED);
        refittable_cargo_types:         bitmask();
        loading_speed:                  5;
        cost_factor:                    18;     // 7%
        running_cost_factor:            25;     // 10%
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          80 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_CARGO;
        power:                          147 kW;
        running_cost_base:              RUNNING_COST_DIESEL;
        dual_headed:                    0;
        cargo_capacity:                 15;
        weight:                         20 ton;
        ai_engine_rank:                 0; // not intended to be used by the ai
        engine_class:                   ENGINE_CLASS_DIESEL;
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3;
        air_drag_coefficient:           0.1;
        shorten_vehicle:                SHORTEN_TO_8_8;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DIESEL, -2, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:         0 ton;
        bitmask_vehicle_info:           0;
    }

    graphics {
        PASS:               spriteset_wm15a_empty;
        TOUR:               spriteset_wm15a_empty;
        WOOD:               spritegroup_wm15a_wood;
        LVST:               spritegroup_wm15a_lvstck;
        STEL:               spritegroup_wm15a_steel;
        PAPR:               spritegroup_wm15a_paper;
        default:            spritegroup_wm15a_goods;
        purchase:           spriteset_wm15a_purchase;
        can_attach_wagon:   switch_wm15a_can_attach;
        start_stop:         switch_wm15a_start_stop;
        cargo_capacity:     switch_wm15a_capacity;
    }
}

item(FEAT_TRAINS, item_pwm15) {
    property {
        /* common properties */
        name:                           string(STR_PWM15_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1900, 1, 1);            // !!!
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   30;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_MAIL, CC_EXPRESS, CC_PIECE_GOODS);
        non_refittable_cargo_classes:   bitmask(CC_REFRIGERATED);
        refittable_cargo_types:         bitmask();
        loading_speed:                  5;
        cost_factor:                    25;     // 10%
        running_cost_factor:            0;
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          80 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_CARGO;
        power:                          0;
        running_cost_base:              RUNNING_COST_NONE;
        dual_headed:                    0;
        cargo_capacity:                 15;
        weight:                         12 ton;
        ai_engine_rank:                 0; // not intended to be used by the ai
        engine_class:                   ENGINE_CLASS_DIESEL;
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3;
        air_drag_coefficient:           0.1;
        shorten_vehicle:                SHORTEN_TO_6_8;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DISABLE, 0, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:         0 ton;
        bitmask_vehicle_info:           0;
    }

    graphics {
        WOOD:               spritegroup_pwm15_wood;
        LVST:               spritegroup_pwm15_lvstck;
        STEL:               spritegroup_pwm15_steel;
        PAPR:               spritegroup_pwm15_paper;
        default:            spritegroup_pwm15_goods;
        purchase:           spriteset_pwm15_purchase;
        cargo_capacity:     switch_pwm15_capacity;
    }
}
#6
Gdyby ktoś chciał pobawić się opisywaną wyżej drezyną WM-15A, to można ją pobrać w postaci NewGRF-a z linku poniżej:
http://www.modelarstwott.pl//files/ottd/WM15A-0.1.grf

Wszelkie dyskusje i pytania dotyczące niniejszego tutoriala są mile widziane. Smile W tym celu, aby nie "zaśmiecać" samego tutoriala, zapraszam do wydzielonego wątku: http://openttd-polska.pl/Temat-Dyskusja-...yna-WM-15A
#7
Jak mam pobrać ten program, przekierowywuje mnie na wiki i dalej jak klikam pobierz to pokazuję się okno ,,zapisz plik, otwórz za pomocą..."
#8
Interesuje cie plik .zip, pliki .tar są dla linuksa. Szczerze, to nie mając podstawowej wiedzy nt. programowania i maszyn wirtualnych(no chyba że ktoś jest na tyle szalony żeby robić to na windowsie XD) albo i samego linuksa to nie warto sie za to brać.
#9
Zacznij od samego rysowania i podsyłaj gotowe grafiki koderom, np TadeuszowiD.
#10
(19-09-2018, 10:10)FrontierPharmacist napisał(a): Zacznij od samego rysowania i podsyłaj gotowe grafiki koderom, np TadeuszowiD.

A w jakim programie mam narysować?
#11
(19-09-2018, 19:52)ja14n1 napisał(a):
(19-09-2018, 10:10)FrontierPharmacist napisał(a): Zacznij od samego rysowania i podsyłaj gotowe grafiki koderom, np TadeuszowiD.

A w jakim programie mam narysować?

W każdym programie obsługującym grafikę rastrową (GIMP, Photoshop, nawet paint może być, ale prędzej zwariujesz niż coś narysujesz)


Skocz do:


Użytkownicy przeglądający ten wątek: 1 gości