Vodná mapa pre Wesnoth, vylepšená
V predchádzajúcich dvoch článkoch sme si vytvorili scenár na súboj dvoch hráčov s názvom "2p - Pool", kde jeden hráč mohol verbovať iba morských ľudí a druhý hráč iba morských hadov a saurov. Scenár sme uverejnili na serveri Wesnothu. V poslednom článku presunieme výber medzi morskými ľuďmi a hadmi dovnútra scenára. Každý hráč si vyberie svoj tím presunutím veliterľa do jednej z dvoch začiatočných pevností; budú teda možné aj bitky morských ľudí proti morským ľuďom alebo hadov proti hadom.
Poznámka: Bolo by "prirodzenejšie" vytvoriť dve frakcie morských ľudí a hadov, a potom éru, ktorá by obsahovala tieto frakcie. Pri spustení hry pre viacerých hráčov by mohol hostiteľ vybrať túto (alebo hocijakú inú) éru pre tento (alebo hocijaký iný) scenár. Ale my to urobíme inak, pretože chceme použiť tieto frakcie iba pre tento scenár, a pretože toto je návod na programovanie udalostí v scenári.
Logika scenára sa zadáva pomocou udalostí. Udalosti sú príkazy v tvare: "keď sa stane toto, urob tamto". Na wiki stránkach Wesnothu (po anglicky) nájdete zoznam možných príkazov; v tomto článku vysvetlím iba tie, ktoré teraz použijeme.
Začnime tým, že na jednotlivé pevnosti umiestnime nápisy ako "toto je pevnosť morských ľudí" a "toto je pevnosť hadov". Použijeme konfiguračný súbor scenára "2p_Pool.cfg" z predchádzajúceho článku, a na jeho koniec pridáme udalosti.
[multiplayer] # ...tu su veci z predchadzajuceho clanku... [event] name=prestart [label] x,y=4,5 text=_"Morski ludia" [/label] [label] x,y=4,9 text=_"Hadi" [/label] [label] x,y=30,5 text=_"Morski ludia" [/label] [label] x,y=30,9 text=_"Hadi" [/label] [/event] [/multiplayer]
Značky "[event]" a "[/event]" znamenajú "toto je začiatok / koniec popisu udalosti". Parameter "name" určuje, kedy nastane táto udalosť; hodnota "prestart" znamená "pred štartom scenára, skôr ako sa čokoľvek ukáže na obrazovke". To je správne miesto na záverečné úpravy mapy. Nasledujúce značky sú príkazy, ktoré sa majú vykonať, keď sa spustí udalosť; čiže pred začiatkom scenára.
Značky "[label]" a "[/label]" znamenajú príkaz "daj na dané políčko mapy nápis". Parametre "x" a "y" sú súradnice políčka. (Súradnice sa zobrazujú v pravom hornom rohu okna, v editore máp aj v hre.) Parameter "text" je text nápisu. Pred textom je znamienko "_", aby ho bolo možné neskôr preložiť do iných jazykov.
Toto je najjednoduchší spôsob ako napísať skript. Ale nie je ho takto jednoduché udržiavať, najmä ak bude dlhší. Nie je zrejmé, že "4,5" sú súradnice pevnosti morských ľudí hráča číslo 1; a keby sa dizajnér mapy rozhodol presunúť pevnosť na iné miesto, nebolo by jasné, ktoré časti skriptu treba aktualizovať. To sa dá vyriešiť vytvorením makra s názvom "PEVNOST_H1M", ktoré bude predstavovať umiestnenie pevnosti morských ľudí hráča číslo 1, ale stačí ho definovať na jednom mieste a môžeme ho používať viackrát. Makrá môžeme definovať v hociktorej časti skriptu, len ich musíme definovať skôr ako ich použijeme, takže toto makro môžeme dať pred udalosti, alebo na začiatok súboru. Verzia s makrami bude vyzerať asi takto:
#define PEVNOST_H1M x,y=4,5 #enddef #define PEVNOST_H1H x,y=4,9 #enddef #define PEVNOST_H2M x,y=30,5 #enddef #define PEVNOST_H2H x,y=30,9 #enddef [multiplayer] # ...tu su veci z predchadzajuceho clanku... [event] name=prestart [label] {PEVNOST_H1M} text=_"Morski ludia" [/label] [label] {PEVNOST_H1H} text=_"Hadi" [/label] [label] {PEVNOST_H2M} text=_"Morski ludia" [/label] [label] {PEVNOST_H2H} text=_"Hadi" [/label] [/event] [/multiplayer]
Ďalšia udalosť bude výber pevnosti. Napíšme kód, ako si hráč číslo 1 vyberie pevnosť morských ľudí. Keď jednotka, ktorá patrí hráčovi číslo 1 vstúpi na pevnosť morských ľudí hráča číslo 1, hráč číslo 1 dostane možnosť verbovať jednotky morských ľudí, a pevnosť hadov hráča číslo 1 sa zničí.
[multiplayer] # ...tu su predchadzajuce veci... [event] name=moveto [filter] side=1 x,y=4,5 [filter_location] terrain=Khw [/filter_location] [/filter] first_time_only=yes [set_recruit] side=1 recruit=Merman Fighter,Merman Hunter,Mermaid Initiate [/set_recruit] [label] x,y=4,9 text="" [/label] [terrain] x= 3, 3, 4, 4, 4, 5, 5 y= 9,10, 8, 9,10, 9,10 terrain=Wo [/terrain] [terrain] x,y=4,7 terrain=Ww [/terrain] [/event] [/multiplayer]
Udalosť typu "moveto" nastane vtedy, keď sa jednotka presunie do situácie popísanej v značke "[filter]". (Táto jednotka musí prísť na to políčko, nestačí keď cezeň iba prechádza.) Pretože takáto situácia môže nastať mnohokrát, parameter "first_time_only" určuje, či sa udalosť vykoná iba keď situácia nastane prvýkrát, alebo pri každej takejto situácii. My chceme vybrať pevnosť iba raz.
Značky "[filter]" a "[/filter]" ohraničujú popis situácie, pri ktorej má nastať udalosť. Parameter "side" určuje, že to bude po pohybe jednotky patriacej hráčovi číslo 1. Parametre "x,y" určujú, že to bude, keď sa táto jednotka presunie na políčko 4,5 (pevnosť morských ľudí hráča číslo 1). Značka "[filter_location]" určuje ďalšie podmienky ohľadom terénu, na ktorý sa jednotka posunie; musí to byť terén "Khw", čo je kód pre vodnú pevnosť. (Prečo testujeme, či je na políčku vodná pevnosť, keď už vieme, že je to políčko 4,5? Týmto trikom zabránime výberu už zničenej pevnosti. Ak si hráč číslo 1 vyberie pevnosť hadov, pevnosť morských ľudí sa zničí; ak neskôr nejaká jednotka príde na políčko 4,5, nebude tam terén vodnej pevnosti, takže sa táto udalosť nespustí.)
Značky "[set_recruit]" a "[/set_recruit]" znamenajú príkaz nastaviť zoznam verbovateľných typov jednotiek; hráč číslo 1 bude môcť verbovať iba jednotky morských ľudí. Značky "[label]" a "[/label]" umiestnia nápis na políčko mapy; v tomto prípade nahradia už existujúci nápis ("Hadi" na pevnosti hadov hráča číslo 1) prázdnym textom, čiže vlastne vymažú nápis.
Značky "[terrain]" a "[/terrain]" znamenajú príkaz na zmenu terénu mapy; "x,y" je pozícia políčka, ktoré sa má zmeniť a "terrain" je kód nového terénu. Môžeme zadať viacero políčok naraz, ak uvedieme zoznam x-ových súradníc a zoznam zodpovedajúcich y-nových súradníc; čiže políčka v uvedenom príklade sú 3,9, 3,10, 4,8, 4,9, 4,10, 5,9, a 5,10. Kód terénu "Wo" je hlboká voda, chceme druhý hrad poriadne potopiť. Kód terénu "Ww" je plytká voda, chceme iba zrušiť most na štarte.
Aj tento kus programu by sa jednoduchšie udržiaval, keby využíval makrá:
[multiplayer] # ...tu su predchadzajuce veci... [event] name=moveto [filter] side=1 {PEVNOST_H1M} [filter_location] terrain={TEREN_PEVNOST} [/filter_location] [/filter] first_time_only=yes [set_recruit] side=1 recruit={JEDNOTKY_MORSKE} [/set_recruit] [label] {PEVNOST_H1H} text="" [/label] [terrain] {HRAD_H1H} terrain={TEREN_VODA_HLBOKA} [/terrain] [terrain] {START_H1} terrain={TEREN_VODA} [/terrain] [/event] [/multiplayer]
Potrebujeme 4 takéto udalosti: hráč číslo 1 si vyberá pevnosť morských ľudí, hráč číslo 1 si vyberá pevnosť hadov, hráč číslo 2 si vyberá pevnosť morských ľudí, hráč číslo 2 si vyberá pevnosť hadov. Mohli by sme urobiť 4 kópie tejto udalosti a zmeniť, čo treba. Alebo môžeme vytvoriť makro (s názvom VYBER_TIM) využívajúce parametre. Makrá stačí napísať raz a môžeme ich použiť viackrát. Parametre sú tie časti makra, ktoré sa menia.
V tomto príklade sú premenlivé časti makra: číslo hráča, 1 alebo 2 (nazvime ho HRAC); súradnice zvolenej pevnosti (nazvime ju PEVNOST); zoznam jednotiek na verbovanie (JEDNOTKY); súradnice tej druhej pevnosti, odkiaľ vymažeme nápis (INA_PEVNOST); súradnice toho druhého hradu vrátane pevnosti, ktoré potopíme (INY_HRAD); a štartová pozícia daného hráča (START). Takže makro na túto udalosť bude vyzerať takto:
#define VYBER_TIM HRAC PEVNOST JEDNOTKY INA_PEVNOST INY_HRAD START [event] name=moveto [filter] side={HRAC} {PEVNOST} [filter_location] terrain={TEREN_PEVNOST} [/filter_location] [/filter] first_time_only=yes [set_recruit] side={HRAC} recruit={JEDNOTKY} [/set_recruit] [label] {INA_PEVNOST} text="" [/label] [terrain] {INY_HRAD} terrain={TEREN_VODA_HLBOKA} [/terrain] [terrain] {START} terrain={TEREN_VODA} [/terrain] [/event] #enddef
Makro použijeme takto:
#define JEDNOTKY_MORSKE Merman Fighter,Merman Hunter,Mermaid Initiate#enddef #define JEDNOTKY_HADIE Naga Fighter,Saurian Augur,Saurian Skirmisher#enddef #define TEREN_PEVNOST Khw#enddef #define TEREN_VODA Ww#enddef #define TEREN_VODA_HLBOKA Wo#enddef #define START_H1 x,y=4,7 #enddef #define START_H2 x,y=30,7 #enddef #define PEVNOST_H1M x,y=4,5 #enddef # ...tu su ostatne pevnosti... #define HRAD_H1M x= 3, 3, 4, 4, 4, 5, 5 y= 5, 6, 4, 5, 6, 5, 6 #enddef #define HRAD_H1H x= 3, 3, 4, 4, 4, 5, 5 y= 9,10, 8, 9,10, 9,10 #enddef #define HRAD_H2M x=29,29,30,30,30,31,31 y= 5, 6, 4, 5, 6, 5, 6 #enddef #define HRAD_H2H x=29,29,30,30,30,31,31 y= 9,10, 8, 9,10, 9,10 #enddef [multiplayer] # ...tu su predchadzajuce veci... #define VYBER_TIM HRAC PEVNOST JEDNOTKY INA_PEVNOST INY_HRAD START # ...viete co... #enddef {VYBER_TIM 1 {PEVNOST_H1M} {JEDNOTKY_MORSKE} {PEVNOST_H1H} {HRAD_H1H} {START_H1}} {VYBER_TIM 1 {PEVNOST_H1H} {JEDNOTKY_HADIE} {PEVNOST_H1M} {HRAD_H1M} {START_H1}} {VYBER_TIM 2 {PEVNOST_H2M} {JEDNOTKY_MORSKE} {PEVNOST_H2H} {HRAD_H2H} {START_H2}} {VYBER_TIM 2 {PEVNOST_H2H} {JEDNOTKY_HADIE} {PEVNOST_H2M} {HRAD_H2M} {START_H2}} [/multiplayer]
Ak ste sa stratili v tých značkách "...", na konci článku nájdete napísaný celý kód scenára. (Len sa snažím tento článok skrátiť, aj keď mi to veľmi nejde.) Pointa je, že makro bez parametrov sa volá takto: "{MOJE_MAKRO}", a makro s parametrami sa volá takto: "{MOJE_MAKRO PARAMETER1 PARAMETER2}". Ako parameter makra môžeme použiť aj iné makro a vyzerá to takto: "{MOJE_MAKRO {INE_MAKRO}}".
Niektoré makrá majú "#enddef" napísané tesne za textom. Ak to napíšeme takto, nedostane sa do makra znak "koniec riadku". Robím to pri makrách, ktoré tvoria iba časť riadku v skripte; mohol by som potom dať do jedného riadku viacero takýchto makier, napríklad "recruit={JEDNOTKY_MORSKE},{JEDNOTKY_HADIE}". Ak makro obsahuje celé riadky skriptu, nezáleží na tom, či je tam nejaký "koniec riadku" navyše, lebo prázdne riadky sa ignorujú.
V tejto chvíli je scenár hotový. Jediný problém je, že AI (umelá inteligencia, čiže "Počítačový hráč" vo Wesnothe) nášmu systému nerozumie, takže z jeho pohľadu sú naše pevnosti iba obyčajné navzájom rovnocenné pevnosti; a z nejakých technických dôvodov si teda vždy vyberie tú severnejšiu. Pridáme teda nejakú logiku pre AI.
Nemôžeme AI vysvetliť náš systém a očakávať, že pochopí, čo z toho vyplýva, a vymyslí si príslušnú stratégiu. (Ak vie niekto naprogramovať takú chytrú AI, nech prosím kontaktuje programátorov Wesnothu.) Namiesto toho použijeme trik: vyberieme si na začiatku ťahu niektorú pevnosť a zničíme tú druhú, takže AI si potom prirodzene "vyberie" tú zostávajúcu (čím spustí horeuvedenú udalosť).
Algoritmus na výber je takýto: vyberieme si náhodnú pevnosť. Čože, očakávali ste niečo múdrejšie? No tak dobre: ak je počítačový hráč hráčom číslo 1, vyberie si náhodne. Ak je počítačový hráč hráčom číslo 2, vyberie si opak toho, čo si vybral hráč číslo 1. Ale ak si hráč číslo 1 počas prvého ťahu nevybral žiadnu pevnosť (čo je čudné, ale možné), vyberie si aj počítačový hráč číslo 2 náhodne. Povedzme to trochu inými slovami: Počítačový hráč si vyberie pevnosť náhodne, ale počítačový hráč číslo 2 si vyberie opak toho, čo si vybral hráč číslo 1, ak si hráč číslo 1 niečo vybral.
Budeme potrebovať udalosť "ai turn", ktorá sa spúšťa na začiatku ťahu počítačového hráča. Budeme však používať náhodné čísla, a z nejakých technických dôvodov je zakázané generovať náhodné čísla počas udalosti "ai turn", takže využijeme ešte udalosť "side turn", ktorá sa spúšťa na začiatku ťahu každého hráča (skôr ako "ai turn"). V udalosti "side turn" vygenerujeme náhodné číslo a uložíme ho do premennej; v udalosti "ai turn" toto číslo použijeme, alebo ho budeme ignorovať, ak je na ťahu človek.
Náhodné číslo uložíme do premennej s názvom "vyber"; bude to číslo 1 alebo 2, ale aby sa nám skript ľahšie čítal, vytvoríme si makrá VYBER_MORSKE a VYBER_HADIE s hodnotami 1 a 2, takže si nemusíme pamätať, ktoré číslo znamená výber ktorých jednotiek.
Použijeme nové značky "[set_variable]" na uloženie hodnoty do premennej; "[if]" a "[then]" na rozhodnovanie (ak, tak); "[variable]" na testovanie, či dané premenná obsahuje danú hodnotu; "[have_location]" na testovanie, či sa na danom políčku nachádza (alebo nenachádza, ak použijeme znamienko "!") daný terén. Použijeme aj premenné udalosti "turn_number" na zistenie, či je to prvý ťah, a "side_number" na zistenie, či je to hráč číslo 2.
V iných programovacích jazykoch by náš algoritmus vyzeral nejako takto. (Toto je iba vymyslený pseudokód na základe nejakých jazykov.)
keď ŤaháHráč (ťah) { ak ťah == 1 { výber := náhodné(1..2) } } keď ŤaháAI (ťah, hráč) { ak ťah == 1 { ak hráč == 2 a terén(PEVNOST_H1M) != TEREN_PEVNOST { výber := VYBER_MORSKE } ak hráč == 2 a terén(PEVNOST_H1H) != TEREN_PEVNOST { výber := VYBER_HADIE } znič_pevnosť(hráč, nie výber) } }
A toto je hotový skript scenára:
#define JEDNOTKY_MORSKE Merman Fighter,Merman Hunter,Mermaid Initiate#enddef #define JEDNOTKY_HADIE Naga Fighter,Saurian Augur,Saurian Skirmisher#enddef #define TEREN_PEVNOST Khw#enddef #define TEREN_VODA Ww#enddef #define TEREN_VODA_HLBOKA Wo#enddef #define START_H1 x,y=4,7 #enddef #define START_H2 x,y=30,7 #enddef #define PEVNOST_H1M x,y=4,5 #enddef #define PEVNOST_H1H x,y=4,9 #enddef #define PEVNOST_H2M x,y=30,5 #enddef #define PEVNOST_H2H x,y=30,9 #enddef #define HRAD_H1M x= 3, 3, 4, 4, 4, 5, 5 y= 5, 6, 4, 5, 6, 5, 6 #enddef #define HRAD_H1H x= 3, 3, 4, 4, 4, 5, 5 y= 9,10, 8, 9,10, 9,10 #enddef #define HRAD_H2M x=29,29,30,30,30,31,31 y= 5, 6, 4, 5, 6, 5, 6 #enddef #define HRAD_H2H x=29,29,30,30,30,31,31 y= 9,10, 8, 9,10, 9,10 #enddef #define VYBER_MORSKE 1#enddef #define VYBER_HADIE 2#enddef [multiplayer] id=multiplayer_2p_Pool name= _ "(2) Pool" description= _ "Vodna bitka medzi vodnymi ludmi a hadmi." map_data="{@campaigns/Examples_by_Viliam/maps/multiplayer/2p_Pool.map}" {DEFAULT_SCHEDULE} {DEFAULT_MUSIC_PLAYLIST} [side] side=1 controller=human canrecruit=yes team_name=zapad user_team_name= _ "team^Zapad" [/side] [side] side=2 controller=human canrecruit=yes team_name=vychod user_team_name= _ "team^Vychod" [/side] [event] name=prestart [label] {PEVNOST_H1M} text=_"Morski ludia" [/label] [label] {PEVNOST_H1H} text=_"Hadi" [/label] [label] {PEVNOST_H2M} text=_"Morski ludia" [/label] [label] {PEVNOST_H2H} text=_"Hadi" [/label] [/event] #define VYBER_TIM HRAC PEVNOST JEDNOTKY INA_PEVNOST INY_HRAD START [event] name=moveto [filter] side={HRAC} {PEVNOST} [filter_location] terrain={TEREN_PEVNOST} [/filter_location] [/filter] first_time_only=yes [set_recruit] side={HRAC} recruit={JEDNOTKY} [/set_recruit] [label] {INA_PEVNOST} text="" [/label] [terrain] {INY_HRAD} terrain={TEREN_VODA_HLBOKA} [/terrain] [terrain] {START} terrain={TEREN_VODA} [/terrain] [/event] #enddef {VYBER_TIM 1 {PEVNOST_H1M} {JEDNOTKY_MORSKE} {PEVNOST_H1H} {HRAD_H1H} {START_H1}} {VYBER_TIM 1 {PEVNOST_H1H} {JEDNOTKY_HADIE} {PEVNOST_H1M} {HRAD_H1M} {START_H1}} {VYBER_TIM 2 {PEVNOST_H2M} {JEDNOTKY_MORSKE} {PEVNOST_H2H} {HRAD_H2H} {START_H2}} {VYBER_TIM 2 {PEVNOST_H2H} {JEDNOTKY_HADIE} {PEVNOST_H2M} {HRAD_H2M} {START_H2}} [event] name=side turn first_time_only=no [if] [variable] name=turn_number numerical_equals=1 [/variable] [then] [set_variable] name=vyber rand=1..2 [/set_variable] [/then] [/if] [/event] [event] name=ai turn first_time_only=no [if] [variable] name=turn_number numerical_equals=1 [/variable] [then] [if] [variable] name=side_number numerical_equals=2 [/variable] [have_location] {PEVNOST_H1M} terrain=!,{TEREN_PEVNOST} [/have_location] [then] [set_variable] name=vyber value={VYBER_MORSKE} [/set_variable] [/then] [/if] [if] [variable] name=side_number numerical_equals=2 [/variable] [have_location] {PEVNOST_H1H} terrain=!,{TEREN_PEVNOST} [/have_location] [then] [set_variable] name=vyber value={VYBER_HADIE} [/set_variable] [/then] [/if] #define PREDVYBER_TIM HRAC VYBER INA_PEVNOST [if] [variable] name=side_number numerical_equals={HRAC} [/variable] [variable] name=vyber numerical_equals={VYBER} [/variable] [then] [terrain] {INA_PEVNOST} terrain={TEREN_VODA} [/terrain] [/then] [/if] #enddef {PREDVYBER_TIM 1 {VYBER_MORSKE} {PEVNOST_H1H}} {PREDVYBER_TIM 1 {VYBER_HADIE} {PEVNOST_H1M}} {PREDVYBER_TIM 2 {VYBER_MORSKE} {PEVNOST_H2H}} {PREDVYBER_TIM 2 {VYBER_HADIE} {PEVNOST_H2M}} [/then] [/if] [/event] [/multiplayer]
Tento scenár (po anglicky, s nejakými vylepšeniami, ktoré v článkoch nespomínam) si môžete stiahnuť ako doplnok k Wesnothu "Examples by Viliam".
Wesnoth 1.4 žiaľ obsahuje chybu v programe, kvôli ktorej má AI problém verbovať jednotky z určitých netypických zoznamov, ako sú napríklad tieto. Počítačový hráč potom nenaverbuje žiadne jednotky, alebo ich naverbuje príliš málo. Nenechajte sa tým odradiť, berte to ako pripomienku, že hra sa stále vyvíja (napísal som autorom chybové hlásenie a snáď to jedného dňa vo verzii 1.6 opravia), a že ak robíte niečo nezvyčajné, možno budete potrebovať kontaktovať autorov hry. Čím viac sa vaše scenáre budú podobať na scenáre z hry, tým menšia je šanca narazenia na chybu. A tento scenár aspoň funguje poriadne, keď hrá človek proti človeku.
Súvisiace články:
- Vodná mapa pre Wesnoth
- Vodná mapa pre Wesnoth, uverejnená
- Vodná mapa pre Wesnoth, vylepšená
0 komentárov:
Zverejnenie komentára
Prihlásiť na odber Zverejniť komentáre [Atom]
<< Domov