Programování textových her v TADS 3, část 4. – Kontejnery

Před časem jsem se setkal s názorem, že kontejnery v textových hrách nejsou vůbec potřeba. Je pravda, že v některých hrách hráč zadá příkaz >otevři truhlu, a v ten okamžik se v místnosti objeví všechny ukryté předměty, jakoby „vypadnou“ do místnosti. (A pochopitelně po zavření truhly zůstanou vidět, hra skutečně nerozlišuje, kde přesně předměty jsou.) Pro programátora programujícího textovku na zelené louce je to asi vítané zjednodušení, ale velké systémy pro tvorbu textovek používají mnohem detailnější model světa a mají k tomu mnoho dobrých důvodů.

Hierarchie objektů

V TADSu (ale i Informu a dalších) je kontejnerová hierarchie, tedy schopnost jednoho objektu mít v sobě obsaženy jiné objekty (a ty zase další do libovolné úrovně zanoření), ústředním bodem celého modelu světa, který prostupuje prakticky úplně vším. Zdaleka se nejedná jen o pomyslnou truhlu obsahující jiné věci. Úplně stejným způsobem je v TADSu ona truhla umístěna do místnosti, jako nejvyššího bodu hierarchie, a stejně je umístěn do místnosti i hráč. A vše, co má hráč v inventáři, je umístěno do objektu hráče, hráč obsahuje svůj inventář. Vzniká tak obsáhlá kontejnerová hierarchie objektů (nepleťte si s hierarchií dědičnosti tříd), která se v průběhu hry mění, jak hráč s předměty manipuluje a jak se hráč přesouvá mezi lokacemi. Je to vlastně takový strom:

  • místnost
    • truhla v místnosti
      • starodávná váza v truhle
        • ozdobná malba na váze
      • krabička zápalek v truhle
        • zlomená zápalka v krabičce
        • nová zápalka v krabičce
    • hráč v místnosti
      • ošacení hráče, které má na sobě
      • klíč v inventáři hráče

Celé je to zařízené ve zdrojovém kódu velice jednoduše. Každý objekt má vlastnost location, která je nastavena na objekt, ve kterém je ten daný objekt obsažen. Tedy location truhly ukazuje na místnost, location vázy ukazuje na truhlu a location malby ukazuje na vázu. Každý objekt může být obsažen jen v jediném jiném objektu.

TADS díky tomu, že ví, co je v čem obsažené, může řešit různé otázky ohledně dostupnosti objektů. Např. dokud je truhla zavřená, jsou objekty před hráčem ukryté. Otevřením se dostanou do pozornosti. Když hráč sebere vázu, změní se vlastně jen location vázy z truhly na objekt hráče. Objekt malba (samostatně prozkoumatelný detail na váze) automaticky putuje s vázou, protože je v ní obsažen, není pro to potřeba nic dělat. Znalosti o poloze objektů využívá i parser a na příkaz >seber jablko se umí zeptat: „A které jablko máš na mysli? Jablko na stromě nebo jablko na zemi?“

Jak vidíte, slovo obsažen je myšlené v nejširším možném smyslu slova. Kromě příkladů výše používá TADS ten samý mechanismus i pro umístění témat konverzace do NPC postav, položek do menu a mnoho dalšího. A protože umisťování objektů do jiných je při programování tak časté, umožňuje TADS nastavit vlastnost location mnohem jednodušším způsobem a ani přitom nemusíme objekty pojmenovávat. Stačí použít syntaxi se znaménkem plus:

myRoom: Room 'místnost' 'do místnosti'; 
+ OpenableContainer 'truhla' 'truhla' *3;
++ vase: Thing 'starodávná váza' 'starodávná váza' *3;
+++ Component 'ozdobná malba' 'ozdobná malba' *3;
++ Matchbook 'krabička/zápalek' 'krabička zápalek' *3;
+++ Matchstick 'zlomená zápalka' 'zlomená zápalka' *3;
+++ Matchstick 'nová zápalka' 'nová zápalka' *3;

me: Actor 'hráč' *1 @myRoom;
+ Wearable 'ošacení' 'ošacení' *4 wornBy = me;
+ Key 'klíč' 'klíč' *2;

Znaménko plus před definicí objektu znamená, že je objekt umístěn v tom objektu, který byl napsán ve zdrojovém kódu bezprostředně před ním a neměl před sebou žádné plus. Objekt se dvěma znaménky plus zase v objektu s jedním plusem atd. Zkrátka znaménka plus značí zanoření v hierarchii objektů stejně, jako odrážky v seznamu výše, kde jsme si tuto hierarchii poprvé představili.

U fyzických objektů je kromě znaménka plus ještě jedna možnost, jak umístit jeden objekt do jiného, která přitom nevyžaduje, aby byly objekty seřazené ve zdrojovém kódu pod sebou. Je jím zápis se znaménkem zavináč, kterým je v ukázce výše objekt hráče umístěn do místnosti myRoom, ve které hra začíná. To se hodí třeba tehdy, když máme postavu hráče naprogramovanou v jiném souboru, než místnost.

Komponenty

Ještě než se vrhneme na přemisťování objektů, rád bych upozornil na třídu Component, která byla použita v předchozím příkladu. Touto třídou se reprezentují detaily složitějších předmětů, které by měly jít samostatně prozkoumat. Vidíte, že máme vázu a samostatný objekt pro malbu na váze, který je pomocí kontejnerové hierarchie vložen do vázy. Pokud by se hráč pokusil udělat cokoliv kromě prozkoumání malby, hra by mu řekla, že to nemůže, protože malba je součástí vázy. Složitější předměty mohou být klidně složené z celé řady objektů. Takové předměty jsou běžné, v Základně na asteroidu jsme jich měli spoustu, namátkou jmenujme běžecký pás, který měl popruhy a pultík, na kterém byl regulátor a počítadlo.

Přemisťování objektů a psaní podmínek

Když už jsme pochopili způsob uspořádání objektů v hierarchii a naučili se definovat úvodní rozmístění objektů pomocí vlastnosti location resp. častěji pomocí znaménka plus ve zdrojovém kódu, můžeme si říci, jak přemisťovat objekty během hry. Použijeme k tomu metodu moveInto, kterou má každý objekt odvozený z třídy Thing či jejích potomků. Budeme-li chtít programově přemístit vázu do inventáře hráče, použijeme příkaz vase.moveInto(me); Jako parametr v závorce je předán cíl přemístění, v tomto případě objekt me reprezentující hráče. Chcete-li přemístit objekt do místnosti, uvedete jako cíl tu místnost, např. vase.moveInto(myRoom);

Na jednu věc si ale musíme dát pozor. Řekli jsme si, že i objekt hráče je umístěn v místnosti podobně, jako jiné objekty. Mohlo by vás tedy napadnout, že můžete programově přesunout hráče do jiné místnosti příkazem me.moveInto(myRoom); To ale nikdy nedělejte, pro přemístění jakékoliv postavy (i NPC) je určena jiná funkce, správně je me.moveIntoForTravel(myRoom);

To, co jsme si teď řekli, platí pro přemisťování objektů z vůle programátora hry ve zdrojovém kódu různých akcí atp., konkrétně si o nich povíme v dalším článku. Ale samozřejmě i hráč bude při hraní hry různé objekty sbírat a přenášet do různých lokací a tam je zase pokládat. Nejen na zem, ale třeba i do nějakého kontejneru. V řadě situací budete potřebovat zjistit, zda se někde určitý objekt nachází. K dispozici je celá řada metod, které lze vložit jako podmínku do příkazu if. Voláme je zase na objektu, takže např. if(vase.isIn(me)) { … } řekne, zda je váza u hráče. Můžete použít následující testy:

  • isIn(obj) zjišťuje, zda se objekt přímo či nepřímo nachází v daném kontejneru. V našem příkladu je váza v truhle, která je v místnosti. Váza se tedy nepřímo nachází v místnosti a metoda vrátí true.
  • isDirectlyIn(obj) zjišťuje, zda se objekt nachází přímo v předaném kontejneru. Váza je přímo v truhle a truhla je přímo v místnosti, ale test vázy v místnosti by byl neúspěšný, vrátil by nil.
  • isOrIsIn(obj) zjišťuje, zda předaný objekt je tentýž, na kterém se test volá nebo se přímo či nepřímo v něm nachází.
  • isHeldBy(actor) zjišťuje, zda objekt zájmu drží postava předaná jako parametr. Jediný rozdíl oproti isDirectlyIn je v tom, že oblečení, které má postava na sobě, se nepovažuje za držené v rukou.

Druhy kontejnerů

Kontejnerů existuje mnoho druhů, které mají společnou vlastnost, že uchovávají objekty uvnitř sebe, ale liší se svým chováním:

  • Container je běžný kontejner, jako koš, batoh či sáček, který obsahuje objekty uvnitř sebe.
  • Dispenser je speciální kontejner, který se používá např. pro krabičku sirek. Objekty třídy Dispensable se do něj obvykle nevracejí a dokud je hráč z kontejneru nevezme, tak ustupují objekty do pozadí, aby se nepletly s objekty, které hráč už vyndal.
  • RestrictedContainer může obsahovat jen určité objekty, které vyjmenujeme ve vlastnosti validContents či povolíme přetížením metody canPutIn(obj). Používá se např. pro svítilnu, do které lze vložit jen baterie či čtečku karet, do které lze vložit jen identifikační kartu atp.
  • SingleContainer může v každém okamžiku uchovávat jen jednu jedinou věc.
  • StretchyContainer mění svůj objem podle toho, co v sobě má. Na rozdíl od kufru, který je stále stejně velký, si představme třeba pytel.
  • OpenableContainer můžeme navíc otevírat a zavírat. Typickým příkladem je skříňka.
  • LockableContainer je předchozí kontejner navíc s možností být zamknutý či odemknutý, ale na odemknutí není nic potřeba, stane se automaticky. Autor si musí naprogramovat další chování či podmínky sám přetížením metody makeLocked(stat).
  • KeyedContainer je kontejner, který potřebuje k odemknutí klíč.
  • BagOfHolding je mix-in třída, kterou můžeme přidat k jinému kontejneru, jako třeba batohu hráče. Jakmile má hráč ruce přeplněné, automaticky se odkládají objekty do batohu, aby hra vypadala realističtěji.

Kromě těchto kontejnerů existují ještě jiné druhy, pro které slovo obsahovat znamená něco zcela jiného, než být uvnitř. Ačkoliv je tu hodně jazykových odlišností, liší se různé formulace a předložky, samotný princip je prakticky stejný. Typickým příkladem je stůl, na který lze položit věci. Vytvoříme ho pomocí třídy Surface.

Ačkoliv se to z názvu může zdát, Surface nemusí být jenom něco vodorovného placatého. Pamatujte, že jde hlavně o předložky, Surface je cokoliv, co může obsahovat věci na sobě, třeba i držák či háček, na kterém něco visí. Pak se hodí ho zkombinovat s třídou Fixture, aby se nedal odnést pryč.

+ hanger: Surface, Fixture 'držák (na) (skafandru)/(skafandry)/držák' 'držák' *2
    "Slouží k odložení skafandru. "

    gcName = 'držáku, držáku, držák, držáku, držákem'
    gcVocab = 'držáku držákem držáku/držákem'
;

++ spaceSuit: Wearable
    'vesmírný skafandr/oblek' 'vesmírný skafandr' *2
    "Je to úplně obyčejný skafandr pro krátkodobý pobyt v otevřeném vesmíru. Je
        vybaven podporou života na několik hodin, pouzdrem na nářadí, svítilnou
        a vysílačkou s krátkým dosahem. "

    gcName = 'vesmírného skafandru, vesmírnému skafandru, vesmírný skafandr,
        vesmírném skafandru, vesmírným skafandrem'
    gcVocab = 'vesmírného vesmírnému vesmírném vesmírným skafandru/skafandrem/
        obleku/oblekem'
;

Podobně jako RestrictedContainer existuje i RestrictedSurface, kterým lze omezit, co na daném kontejneru může ležet. Kromě nich existují ještě další třídy, které modelují několik méně obvyklých kontejnerů:

  • Underside je nějaký objekt, který může ukrývat další objekty pod sebou, jako třeba když ukryjete klíč pod koberec.
  • RearContainer umožňuje ukrýt objekty za sebe.
  • RearSurface se podobá předchozí třídě, ale objekty jsou připevněné ke kontejneru, místo aby za ním jen ležely. Při přenášení kontejneru jdou s ním.

I všechny tyto třídy mají své Restricted varianty. Často se tyto kontejnery používají s třídou Hidden pro objekty pod či za nimi ukryté.

Kapacita kontejneru

V leckteré staré textovce byla nosnost hráče omezena a ten mohl nést jen omezený počet předmětů. Popravdě řečeno, málo kdy to bylo využito k nějakému smysluplnému puzzlu, spíše to hráče prudí a moderní textovky od podobných věcí ustupují. Snaží se hráče upoutat příběhem a nezatěžovat ho mikromanagementem inventáře.

Chcete-li však, můžete si posloužit. TADS umí limitovat nosnost hráče a kapacitu kontejnerů dvěma způsoby – velikostí (bulk) a hmotností (weight). To jsou vlastnosti, které každému předmětu odvozenému od Thing či jeho potomků určí, kolik zabírá. Hodnoty nemají žádnou konkrétní jednotku, interpretace je na programátorovi, ale standardní hodnota velikosti i hmotnosti každého předmětu je 1 a postavy mají velikost i hmotnost 10, což samozřejmě můžeme změnit. Každý kontejner i hráč pak mají určenou svou kapacitu vlastnostmi bulkCapacity a weightCapacity, které jsou ve výchozím stavu nastaveny na 10000, aby nic neomezovaly.

Kromě celkové kapacity lze ještě limitovat maximální velikost jednoho objektu vlastností maxSingleBulk s výchozí hodnotou 10, takže při úpravách pozor a rovněž pozor na kapacitu židle, která je také 10 (právě na jednu postavu).

Materiály

Už jsme si řekli, že TADS používá znalost o umístění objektů v hierarchii kontejnerů k tomu, aby vyhodnotil dostupnost objektů. Váza zavřená v truhle není vidět, hráč o ní neví a nemůže ji vyndat, dokud truhlu neotevře. To je ale teprve začátek celé problematiky. Co když tím kontejnerem je prosklená skříňka nebo něco podobného? Pak hráč nutně vidí, co je ve skříňce obsaženo, prozkoumat to může (prozkoumání v textových hrách je de facto jen zaměření pozornosti hráče), ale dotknout se bez otevření nemůže.

++ trash: PresentLater, StretchyContainer, OpenableContainer
    'odpadkový pytel/koš/odpadky' 'odpadkový pytel' *2
    "Odpadkový pytel je nevelký tuhý průhledný plastikový pytel se zipem
        sloužící k odkládání suchého netříditelného odpadu zachycený ke kraji
        stolku. Nyní je téměř prázdný.<<splinters.discover>> "

    material = glass

    changeGender = '!pyt:5'
    gcName = 'odpadkového pytle, odpadkovému pytli, odpadkový pytel,
        odpadkovém pytli, odpadkovým pytlem'
    gcVocab = 'odpadkového odpadkovému odpadkovém odpadkovým pytle/pytli/pytlem/
        koše/koši/košem/odpadků/odpadkům/odpadcích/odpadky'
;

+++ splinters: Hidden 'dřevěné třísky' 'dřevěné třísky' *3
    "Třísky jsou na první pohled zbytky rozštípaných tužek, na mnoha místech na
        nich zbývá povrchová barva i s kousky nápisů. Tuhu někdo pečlivě
        odstranil. "

    dobjFor(LookIn) asDobjFor(Examine)
    dobjFor(Search) asDobjFor(Examine)

    isPlural = true
    gcName = 'dřevěných třísek, dřevěným třískám, dřevěné třísky,
        dřevěných třískách, dřevěnými třískami'
    gcVocab = 'dřevěných dřevěným dřevěnými třísek/třískám/třískách/třískami'
;

TADS uvažuje smysly hráče a rozlišuje různé materiály kontejnerů, které ovlivňují, zda zrak, sluch, hmat a čich kontejnerem prostupují, nebo ne. (Ano, i čich. Můžete tak simulovat naší lednici v práci, kterou když otevřu…) Knihovna definuje následující materiály, můžeme si ale vytvořit i vlastní kombinace:

  • adventium je neprostupné pro všechny smysly.
  • glass je průhledné pro světlo (zrak), ale pro ostatní smysly není prostupné.
  • paper je neprostupný pro hmat a zrak, ale pachy a zvuky jím prostupují.
  • fineMesh je prostupný všem smyslům, kromě hmatu.
  • coarseMesh je prostupný úplně všem smyslům vč. hmatu, ale nedovoluje předměty vyndat ven.

Fyzikální simulace je velice sofistikovaná, zavřeme-li rozsvícenou svítilnu do truhly v jinak temném sklepě, tak místnost potemní a nemůžeme v ní nic dělat. Smysly se dokonce dají přenášet na větší dálku do jiných lokací, takže můžeme zaslechnout vzdálený výbuch atp.

Komplexní kontejnery

Už od začátku jsem se snažil zdůrazňovat, že pojem kontejnerů v TADSu znamená mnohem víc, než jen truhlu na věci, že princip kontejnerové hierarchie je potřeba si dobře osvojit, protože se táhne celým programováním. Teď se podíváme na příklad ohřívače jídla. Je to kontejner v pravém slova smyslu, hráč do něj může umístit sendvič příkazem >dej sendvič do ohřívače, a pak si ho ohřát >stiskni knoflík.

Paradoxně však pomocí běžné kontejnerové hierarchie takový přístroj nejde vůbec vytvořit. Problém spočívá v tom, že když vytvoříme kontejner, do kterého má hráč dát jídlo a dáme mu dvířka a zapínací knoflík jako komponenty, tyto komponenty skončí také uvnitř ohřívače. Vyžadujeme-li zavřená dvířka před zapnutím, hráč knoflík uvnitř těžko stiskne. Podobně kdybychom vytvořili kufřík s držátkem, držátko skončí uvnitř kufříku, a když je zavřený, nebude vidět a nepůjde prozkoumat.

Na pomoc nám přicházejí komplexní kontejnery. Jsou realizovány třídou ComplexContainer a mohou sdružovat i více obyčejných kontejnerů, kterým se přidává třída ComplexComponent. V našem případě vytvoříme komplexní kontejner, který má svůj subContainer reprezentující prostor uvnitř trouby, kam se vloží sendvič.

++ owen: ComplexContainer, Fixture
    'mikrovlnný ohřívač/(jídla)' 'ohřívač jídla' *2
    "Ohřívač patří k nejdůležitějším přístrojům kosmického stravování.
        Elektromagnetickým zářením v oblasti mikrovln zahřívá obsah
        potravinových balíčků na poživatelnou teplotu. Vedle dvířek je jediný
        zapínací knoflík. "

    subContainer: ComplexComponent, OpenableContainer
    {
        maxSingleBulk = 15
        material = glass
    }

    gcName = 'ohřívače jídla, ohřívači jídla, ohřívač jídla, ohřívači jídla,
        ohřívačem jídla'
    gcVocab = 'mikrovlnného mikrovlnnému mikrovlnném mikrovlnným ohřívače/
        ohřívači/ohřívačem'
;

+++ ContainerDoor
    'dvířka (kuchyňského) (ohřívače)' 'dvířka kuchyňského ohřívače' *4
    "Jsou průhledná. Za nimi se dějí kulinářská kouzla pod taktovkou
        magnetronu. "

    isPlural = true
    gcName = 'dvířek kuchyňského ohřívače, dvířkám kuchyňského ohřívače,
        dvířka kuchyňského ohřívače, dvířkách kuchyňského ohřívače,
        dvířky kuchyňského ohřívače'
    gcVocab = 'dvířek dvířkám dvířkům dvířkách dvířky dvířkami -'
;

+++ Button, Component 'zapínací knoflík' 'zapínací knoflík' *2
    "Knoflíkem se spustí ohřev jídla, který proběhne jinak zcela automaticky. "

    gcName = 'zapínacího knoflíku, zapínacímu koflíku, zapínací knoflík,
        zapínacím knoflíku, zapínacím knoflíkem'
    gcVocab = 'zapínacího zapínacímu zapínacím knoflíku/knoflíkem'
;

+++ tube: Food 'vesmírný sendvič/tuba/pasta' 'tuba' *3
    "V tubě je napěchovaný vesmírný sendvič se šunkou a zeleninou upravený do
        podoby vymáčknutelné pasty. "

    subLocation = &subContainer

    gcName = 'tuby, tubě, tubu, tubě, tubou'
    gcVocab = 'vesmírného vesmírnému vesmírném vesmírným sendviče/sendviči/
        sendvičem/pasty/pastě/pastu/pastou/tuby/tubě/tubu/tubou'
;

Všechny věci se přidají do kontejneru pohromadě pomocí běžné kontejnerové hierarchie, ale ten objekt, který má být v subkontejneru, má navíc nastavenou vlastnost subLocation. Nepřehlédněte znak ampersand, který značí, že se předává ukazatel na vlastnost.

Podobným způsobem jsme v naší hře vytvořili např. stůl, který umožňuje dát věci na něj i pod něj. Byl to komplexní kontejner, který měl definovaný svůj subSurface a subUnderside.

Komplexní kontejnery pomáhají v situaci, kdy omezení běžné kontejnerové hierarchie (každý objekt má jedno umístění) nedovoluje rozlišit, kde přesně se daný objekt v rámci kontejneru nachází. Na tomto místě by bylo dobré poznamenat, že kontejnerová hierarchie má ještě jeden opačný problém. Nedovoluje jednomu objektu být na více místech současně. V podobných případech pomáhají třídy MultiLoc, MultiInstance a MultiFaceted, jejich popis jde ale už nad rámec článku.

Úprava listerů

Když už si vytvoříme nějaké zajímavé kontejnery, které představují něco méně obvyklého, než krabici, která „obsahuje koho/co“, hodí se také upravit listery. TADS zařizuje automaticky spoustu věcí a jeho jazykové algoritmy jsou velice rozsáhlé a sofistikované. Při prozkoumání kontejneru se za obvyklý popis připojí výpis obsažených předmětů.

>p držáky na nářadí
Držáky bezpečně zajišťují nástroje pro astronauty průzkumníky proti ztrátě i poškození a zároveň dovolují snadné uvolnění nástroje i v neforemných rukavicích skafandrů. V držácích nářadí je uchycený laser (který obsahuje dva vybité akumulátory) a přenosná svítilna (která obsahuje nabitý akumulátor).

V ukázce vidíte výsledek činnosti dokonce dvou vnořených listerů, ten první informuje o obsahu držáků a druhý v závorce o obsahu laseru resp. svítilny. Poslední jmenovaný je standardní lister s neutrální frází „který obsahuje…“. Podívejme se teď na ten první, který jsme si upravili, aby hovořil o „uchycených“ předmětech. V tomto případě nás zajímá descContentsLister, který vypisuje obsažené objekty při prozkoumání kontejneru. Změnit můžeme ale i contentsLister, který se uplatňuje v popisech místností či inventáři, inlineContentsLister, který vypisuje obsah v závorkách při druhé úrovni zanoření v jiném listeru (jako akumulátory v ukázce výše) a pár dalších.

++ roverHolder: RestrictedContainer, Component
    'držák držáky (na) nářadí/držák/držáky' 'držáky nářadí' *2
    "Držáky bezpečně zajišťují nástroje pro astronauty průzkumníky proti
        ztrátě i poškození a zároveň dovolují snadné uvolnění nástroje i v
        neforemných rukavicích skafandrů. "

    validContents = [laser, torch]
    cannotPutInMsg(obj)
    {
        gMessageParams(obj);
        return '{Kdoco obj} do držáků nepasuje, jsou určeny jen na výbavu
            průzkumníků. ';
    }
    contentsListed = nil

    descContentsLister: ContentsLister, BaseThingContentsLister
    {
        showListPrefixWide(itemCount, pov, parent, selector)
        {
            gMessageParams(parent);
            "V {komčem parent} <<selector < 4 ? 'je' : 'jsou'>> uchycen<<
                ['ý', 'á', 'é', 'í', 'é', 'á'][selector]>> ";
        }

        showListSuffixWide(itemCount, pov, parent, selector) { ". "; }

        showListItem(obj, options, pov, infoTab)
        {
            obj.showListItem(options | ListKdoCo, pov, infoTab);
        }
    }

    isPlural = true
    gcName = 'držáků nářadí, držákům nářadí, držáky nářadí, držácích nářadí,
        držáky nářadí'
    gcVocab = 'držáku držákem držáků držákům držácích nářadím/držáku/držákem/
        držáků/držákům/držácích'
;

Metoda showListPrefixWide uvnitř definice listeru vypisuje úsek textu před samotným seznamem objektů. V příkladu čistě pro ukázku, i když to zde není potřeba, parametrizujeme zprávu tak, aby mohl stejný lister sloužit pro jakýkoliv objekt. Proto je jméno dosazené parametrem {komčem parent}, ale mohli bychom napsat rovnou: „V držácích nářadí“. Zajímavější je část s proměnnou selector. Je to číslo od 1 do 6 a říká nám, v jakém rodu (1-3) a čísle (množné +3) máme text formulovat, aby seznam objektů správně navazoval.

Analogicky nám metoda showListSuffixWide umožňuje dopsat konec věty za seznamem objektů. Zde je jen tečka, ale kdybychom chtěli formulovat seznam jako „laser a svítilna jsou uchycené v držácích nářadí“ máme tu možnost. Proměnná selector nám samozřejmě správně řekne, že v tomto případě bychom měli použít množné číslo „jsou uchycené“.

Nakonec přetížením metody showListItem můžeme přepnout seznam objektů do prvního pádu místo běžného čtvrtého (je uchycený kdo/co vs. obsahuje koho/co) přidáním vlajky ListKdoCo do seznamu options.

NestedRoom

Všechny typy kontejnerů, o kterých jsme se zatím bavili, mohly obsahovat v sobě, na sobě atp. různé objekty, žádný však nemohl obsahovat postavu. V textových hrách totiž běžně nebývá důležité, kde přesně se postava v lokaci nachází, automaticky dosáhne na všechno, co v lokaci je. TADS má ale detailnější model světa, který umožňuje postavám, aby byly v nebo na nějakém objektu a dokonce i rozlišit postoj postavy, tedy zda postava stojí, sedí či leží. K čemu je to dobré? Možná to bude jasnější, když si vyjmenujeme konkrétní potomky třídy NestedRoom, které právě toto umožňují:

  • Chair je objekt, jako třeba židle, na kterém může postava sedět. To je výchozí postoj, ale postava si může na objekt i stoupnout. Česká lokalizace definuje i třídu Armchair, která se hodí pro křeslo, ve kterém se sedí. Rozdíl je v upravené předložce.
  • Bed je objekt, na kterém je pro postavu nejpřirozenější ležet, ale může na něm i stát nebo sedět.
  • Platform je objekt, na kterém postava obvykle stojí, ale může si i sednout či lehnout. V naší hře to byl třeba běžecký pás nebo zadní plošina vozítka. Můžete tak modelovat třeba i povrch stolu, pokud je důležité, aby si na něj mohla postava stoupnout.
  • Booth je objekt, do kterého si postava může stopnout, ale ve výchozím stavu je povolené i lehnutí či sednutí. Přidáním třídy Openable na první místo lze udělat zavíratelný objekt, jako např. skříň na šaty.
  • Vehicle je objekt, ve kterém když se postava nachází a dá příkaz pohybu, tak se pohybuje tento objekt i s postavou. Chcete ve své hře vytvořit bicykl? Stačí zkombinovat Chair, Vehicle a je hotovo.
  • HighNestedRoom se přimíchává k jiným NestedRoom třídám, když chceme udělat prostor, který je oproti zbytku místnosti vyvýšený a postava se do něj nemůže dostat bez vyřešení nějakého hlavolamu, jako přitáhnutí něčeho, na co si nejprve stoupne, aby dosáhla.

Hráč má k dispozici příkazy jako >sedni si na židli, >lehni si na postel, >stoupni si na stůl a spoustu synonym, jak se dostat >ven. Použití tříd jako Chair a Bed jen tak pro efekt je jednoduché, podstatně složitější je jejich zapojení do různých hlavolamů. Třeba když je vyžadováno, aby si postava hráče stoupla na židli, než dosáhne na polici vysoko na stěně, naprogramujeme polici jako kontejner s přimíchanou třídou OutOfReach, ve které definujeme pravidla, že hráč na ni dosáhne jen ze židle. Nebo typickým hlavolamem může být okno vysoko na zdi, kterým může hráč uniknout jen tehdy, pokud si stoupne na židli. Přesně tuto situaci rozebírá článek v technickém manuálu a podrobně vysvětluje, na co všechno si programátor musí dát pozor, aby situaci naprogramoval správně.

Další díly seriálu

Mohlo by se vám líbit...

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.