Dnes bych chtěl napsat něco málo o hře Hexen: Beyond Heretic z roku 1995. Možná si pamatujete mou recenzi a ptáte se, proč je tu další text, když už tahle hra dostala svůj prostor?
Inu, tohle nebude recenze, ale podíváme se pod kapotu celé hry a prozkoumáme, jak funguje. Programátoři, kteří na Hexenovi pracovali jsou sice dobří, ale ne dokonalí a pár chybiček se vloudilo. A jedna taková chybička se nachází na konci čtvrté epizody v mapě Gibbet v trůnním sálu, který zároveň funguje jako aréna pro boj s bossem. Když hráč do arény vstoupí, následují tyto události:
1) Nejdříve hráče přepadnou kentauři (Centaur).
2) Až je všechny pobije, může se jít posadit na trůn, což způsobí že po stranách se sníží velké ocelové sloupy a ty do sálu vypustí hejno ptáků ohniváků (Affrit).
3) Jakmile hráč vybije všechny ptáky ohniváky, do trůnního sálu se teleportují velká zelená monstra (Chaos Serpent).
4) Po chvíli se otevřou po stranách menší komůrky, ze kterých hráče přepadnou čarodějové (Dark Bishop).
5) Po jejich vybití se otevře zadní stěna místnosti a za ní čeká boss - démonický čaroděj (Heresiarch).
Někdy však nastane menší problém mezi bodem 3 a 4. Tedy, objeví se zelená monstra, hráč je pobije, ale komůrky s hromadou čarodějů se neotevřou. V takové chvíli je hráč navždy uvězněn v trůnním sálu a hru není možné dohrát (jedná se o tzv. softlock). Ale jak je to ale možné?
Na vině je tenhle malý script:
script 13 OPEN
{
until (thingcount(T_DEMON, 0) == 3)
{
delay(const: 210);
}
Door_Open(const: 28, 32);
tagwait(const: 28);
Stairs_BuildDownSync(const: 18, 8, 16, 0);
}
Co tenhle script přesně způsobuje? Pojďme si ho rozebrat.
První řádek je jen definice toho, že se jedná o script a jeho identifikační číslo (které je 13, protože je to třináctý script v levelu Gibbet). Následuje OPEN, což znamená, že se script spustí ve chvíli, když hráč vstoupí do levelu. Všechny scripty v Hexenovi se dělí do třech kategorií - void scripty se vyvolají na povel hráče (např. když přepne nějakou páku), enter scripty se vyvolají pokaždé, když hráč vstoupí do levelu a nakonec open scripty se vyvolají pouze při prvním vstupu do levelu, ale při dalších návštěvách už nikoliv.
Co přesně dělá následující kód?
until (thingcount(T_DEMON, 0) == 3)
{
delay(const: 210);
}
Jakmile vstoupíte do levelu Gibbet, začne tenhle příkaz počítat, kolik je v daném levelu zelených monster (Chaos Serpentů). V daném levelu je jich na počátku nula. A pokud jich je nula, nestane se nic.
until = opakuj příkaz, dokud nebude naplněna (nějaká) podmínka.
thingcount = zkontroluj počet nějakých věcí (zelených monster).
T_DEMON = zelená monstra (Chaos Serpent) se ve zdrojovém kódu jmenují T_DEMON. GZDoom je později přejmenoval na T_DEMON1, aby nebyla v konfliktu s růžovým prasátkem z Dooma, které se také ve zdrojáku jmenuje T_DEMON. Jméno je také možné nahradit ID číslem, což je v případě Chaos Serpentů 3, ale to teď dejme stranou.
delay(const: 210) = zpoždění 210 tiků (což dělá zhruba 6 reálných sekund, protože abychom dostali 1 sekundu, musíme ty tiky vydělit 35. Proč? Protože CRT monitory měly 35 snímků za sekundu - nebo aspoň Doom engine byl napsaný tak, aby měl 35 snímků za sekundu :))).
Takže script každých 6 sekund zkontroluje, kolik je v mapě zelených monster. A když je jejich počet jiný, než přesně tři, tak za dalších 6 sekund zkontroluje jejich počet znovu. A tak to opakuje pořád dokola. Počítadlo zamrzne jen ve chvíli, kdy hráč opustí level a znovu se dá do pohybu, jakmile se tam vrátí.
Pokud zelených monster (Chaos Serpentů) BUDE přesně tři, tak script konečně může pokračovat dál na tuhle část:
Door_Open(const: 28, 32);
tagwait(const: 28);
Stairs_BuildDownSync(const: 18, 8, 16, 0);
Door_Open(const: 28, 32); = otevřou se dveře s identifikačním číslem 28 rychlostí 32 bitů za jeden tik.
tagwait(const: 28); = počkej, až budou dveře s identifikačním číslem 28 otevřené a pak teprve pokračuj dál. Jinými slovy, příkaz tagwait čeká, až bude nějaká akce dokončená, a pak teprve povolí spuštění další akce.
Stairs_BuildDownSync(const: 18, 8, 16, 0); = tenhle příkaz postaví schody v sektoru č. 18 o výšku 8 pixelů rychlostí 16 bitů za jeden tik. A 0 znamená, že se to stane pouze jednou, nikoliv opakovaně. Příkaz také obsahuje část Sync - ta způsobí, že jednotlivé plošinky, které vytvoří schodiště, se nebudou zvedat jedna po druhé, ale naráz všechny vystřelí vzhůru ve stejnou dobu a zastaví se na požadované výšce (v tomhle případě vždycky bude schod o 8 pixelů vyšší, než ten předchozí).
No, a proč dojde k onomu softlocku?
Do trůnního sálu se teleportují přesně TŘI zelená monstra, což aktivuje script, který před tím nemohl být aktivován, protože jich bylo nula. Neaktivuje se ihned, ale až vyprší delay 210 ticků, což znamená pokud zabijete aspoň jednoho zeleného oškliváka ve chvíli, než proběhne jejich přepočet (který probíhá každých 6 sekund), teoreticky může dojít k onomu softlocku. Ta šance, že se tak stane, je celkem malá, takže to není tolik zajímavé.
Mnohem zajímavější je, pokud hrajte ne nejtěžší obtížnost. Na nejtěžší obtížnost se do trůnního sálu teleportují ČTYŘI zelená monstra, díky čemuž se script nevyvolá. Řešením je zabít pouze JEDNO zelené monstrum, čímž zbydou TŘI a chvíli počkat, až se script vyvolá (tj. až se vynuluje šestisekundový interval). To jakožto hráč samozřejmě nemůžete vědět, že hra potřebuje, aby monstra byla tři, a přirozeně se je pokusíte pobít všechny naráz, že jo...
Jak k onomu bugu došlo? Za prvé, programátor nejspíše omylem dal ==3 místo >=3, tedy "je jich přesně tři" versus "jsou tři a více". Možná bych to i snížil na 1, aby na jejich počtu nezáleželo vůbec. Před tím jich bylo nula, takže by stačilo kontrolovat, jestli se vůbec nějaký zelený ošklivák v mapě nachází.
Je dost možné, že původně se tam teleportovali tři, a level designér později přidal čtyři na nejtěžší obtížnost, ale už neupravil script a následně to nikdo netestoval, protože je tlačilo datum vydání.
Každopádně tohle byl legendární bug v trůnním sálu v Hexenovi v mapě Gibbet. Snad vám to připadalo zajímavé a bavilo vás to. Ve hře se nachází ještě jeden bug až u final bosse, a mohl bych mu věnovat nějaký další budoucí článek, ale záleží na tom, jestli o to bude zájem. Dejte vědět do komentářů, jestli by vás to zajímalo a určitě byste si to rádi přečetli.
Ano, jen tak dál.
Bezva článek, více takových.
Super článek. Určitě víc takových. Klidně i z jiných her. Možná by z toho mohla být i celkem pěkná série, nějakých „mini článků“?
Zábavné. Btw Doom měl taky některé pozoruhodné bugy:
https://www.youtube.com/watch?v=-IYfwCWZeD0
Parádní rozbor, další díly rozhodně potěší :-)
Mne tohle bavi. Chci vic.
CRT 70Hz refresh / 2.
oook, díky za upřesnění :)
Za mě taky super čtení ;) Určitě v tom pokračuj je to zajímavá změna na rozdíl od klasických recenzí.
Bezva clanek. Diky a tesim se na dalsi.
Super článek, pro mě jako neprogramátora velmi zajímavý vhled pod kapotu ;)
Jsi grafoman, to je pro tento server požehnání; a ano, bavilo nás to a chceme další ;)
Požehnání – ano, je, souhlasím! :) Můj výstup je především novinkový, na obsáhlejší články nemám dost prostoru, takže jsem nadšen, že je tu Pollux a další, co to zachraňují! :)
Ano, prosím pokračuj v těchto rozborech, pro nás samouky co se snažíme fušovat do kódování je to parádně poučující ;)