SAPIENS

par davidnurdin · Remakes
pas encore noté
▶ Jouer Sources Éditer GitHub
. / src / Sapiens / GestTrait.php · commit 080ad4c
<?phpnamespace App\Sapiens;/** * SAPGEST.C: flint knapping (cursil), the character (defsap), the density of the world (DEF3D), the * start of an adventure, load / save, the "SAPIENS" menu (trgest), making a weapon (trfab). * * The knapping game keeps its state in the pixels of Ecran1 (lit_sil / ecrit_sil), like the original. * Saves: the original byte format ("GOD" + the soma bytes XOR Random() + checksum + "MAGIC" lines), * base64 in the options (key sapiens_save_<slot>, 3 slots). Mac dialogs are in-game screens. */trait GestTrait{    private const NB_LIGNE_SILEX = 200;    private const NB_COL_SILEX = 300;    private const COULEUR_FOND_NUCLEUS = 0x000;    private const NB_COUL_SILEX = 8;    private const PRECISION_SILEX = 300;    private const CAR_UTILE = 180;    private const NB_JAUGES_3D = 5;    private const NB_NUAGES_MAX = 100 * 20;    private const NB_OISEAUX_MAX = 20 * 20;    private const NB_ROCHERS_MAX = 50000;    private const NB_ARBRES_MAX = 100000;    private const NB_MAX_HERBE_AV_PLAN = 100;    private const GEST_SLOTS = 3;    private const FORSY1 = [0, 0, 0, 0, 1, 1, 2, 3, 4, 4,        6, 6, 7, 7, 8, 8, 9, 9, 10, 10,        11, 11, 12, 12, 13, 13, 14, 14, 15, 15,        16, 16, 16, 16, 17, 17, 18, 18, 18, 18,        19, 19, 20, 20, 20, 20, 21, 21, 22, 22,        22, 22, 22, 22, 23, 23, 23, 23, 23, 23,        24, 24, 24, 24, 24, 24, 24, 24, 23, 23,        23, 23, 23, 23, 22, 22, 22, 22, 21, 21,        20, 19, 18, 17, 16, 15, 14, 12, 10, 8,        6, 5, 3, 1, 0, 0, 0, 0, 0, 0];    private const FORSY2 = [0, 0, 0, 2, 4, 5, 6, 7, 8, 8,        8, 9, 10, 10, 11, 11, 12, 13, 14, 14,        15, 15, 16, 16, 17, 17, 18, 18, 19, 19,        20, 20, 21, 21, 22, 22, 23, 23, 24, 24,        25, 25, 26, 26, 27, 27, 28, 28, 28, 28,        29, 29, 29, 29, 30, 30, 30, 30, 30, 30,        30, 30, 31, 31, 32, 32, 32, 32, 32, 32,        32, 32, 32, 32, 32, 32, 32, 32, 32, 32,        32, 32, 31, 31, 30, 29, 28, 26, 24, 22,        20, 16, 12, 6, 0, 0, 0, 0, 0, 0];    private const FORMONDE = [50, 50, 50, 50, 49, 49, 49, 49, 48, 48,        47, 47, 47, 46, 46, 46, 46, 45, 45, 44,        43, 43, 42, 42, 41, 40, 39, 39, 38, 38,        37, 37, 36, 35, 34, 34, 33, 33, 32, 32,        31, 30, 29, 28, 27, 26, 25, 25, 24, 24,        23, 23, 22, 21, 20, 20, 19, 19, 18, 18,        17, 17, 16, 16, 15, 15, 15, 15, 14, 14,        13, 13, 12, 12, 11, 11, 10, 10, 9, 9,        8, 8, 7, 7, 6, 6, 5, 5, 4, 4,        3, 3, 2, 2, 1, 1, 0, 0, 0, 0];    /** Colours of the nucleus, dark to light (16-bit RGB) */    private const TAB_COUL_SILEX_RGB = [        [0x0000, 0x0000, 0x0000],        [0x3b00, 0x1b00, 0x1200],        [0x6d00, 0x4900, 0x0000],        [0x9200, 0x6d00, 0x0000],        [0xB600, 0x9200, 0x0000],        [0xDB00, 0x9200, 0x2400],        [0xDB00, 0xB600, 0x0000],        [0xFD00, 0xE700, 0x0000],    ];    /** thickness -> colour */    private const EPAISS = [        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,        0, 0, 0, 0, 0,        1, 1, 1, 1, 1, 2, 2, 2, 2, 3,        3, 3, 3, 4, 4, 4, 4, 5, 5, 5,        5, 6, 6, 6, 6, 7, 7, 7, 7, 7];    /** char * traps[7] (cheat names) */    private const TRAPS = ['CHAUVET', 'GROTTE', 'MUSICS', 'DETAIL', 'AUROCH', 'MYRIAD', 'CAT   '];    /** @var int[] forsyl: the shape to obtain (FORSY1 or FORSY2) */    private array $forsyl = self::FORSY1;    /** @var array{0:int,1:int,2:int,3:int} the "next" arrow: Rect {top,left,bottom,right} = {80,264,112,296} */    private array $AireSuite = [320 - 16 - 32 - 8, 100 - 20, 320 - 16 - 8, 100 - 20 + 32];    /** @var int[] palSilex: system indexes of the colours of the nucleus */    public array $palSilex = [0, 0, 0, 0, 0, 0, 0, 0];    /** @var array<int, int> system index -> colour of the nucleus (first match, like the C loop) */    private array $silexIndex = [];    /** the slot of the last load / save */    private int $gestSlot = 0;    private function DEHORS(): int    {        return $this->mobBambou + 20;    }    private function FRAPPE(): int    {        return $this->mobBambou + 21;    }    /** plot_ecr: a coloured point on the current port (ST line, column, ST colour) */    private function plot_ecr(int $y, int $x, int $couleur): void    {        [$r, $g, $b] = $this->stRgb($couleur);        $this->RGBForeColor($r, $g, $b);        $this->DoPenSize(1, 1);        $this->DoMoveTo($x * 2, $y * 2);        $this->DoLine(1, 0);        $this->DoPenSize(1, 1);        $this->qdFore = Mac::BLACK;    }    /** CvtCouleurStEnRgb */    private function stRgb(int $val): array    {        return [(($val & 0x700) >> 8) * 9362, (($val & 0x070) >> 4) * 9362, ($val & 0x007) * 9362];    }    /** lit_sil: the colour (0..7) of a point of the nucleus, read on Ecran1 */    private function lit_sil(int $y, int $x): int    {        $index = $this->GetIndexPixBrt($this->Ecran1, $x + 106, $y);        if (isset($this->silexIndex[$index])) {            return $this->silexIndex[$index];        }        return 0;    }    /** ecrit_sil: writes a colour (0..7) of the nucleus on Ecran1 */    private function ecrit_sil(int $y, int $x, int $couleur): void    {        $this->PlotIndexPixBrt($this->Ecran1, $x + 106, $y, $this->palSilex[$couleur]);    }    public function cat(): void    {    }    /** TRAPSURE: reserved for the initiated (names typed in getnom) */    public function trapsure(int $quoi): \Generator    {        $this->flefba = 0;        if ($quoi > 0) {            $this->comment(114 + $quoi);        }        switch ($quoi) {            case 0:                yield from $this->DEF3D();                break;            case 1:                $this->xj = 22528;                $this->yj = 21504;                break;            case 2:                yield from $this->sceveu(11);                break;            case 3:                yield from $this->sceveu(13);                break;            case 4:                $this->ajoutl($this->liste1, 22);                $this->ajoutl($this->liste1, 25);                $this->ajoutl($this->liste1, 46);                break;            case 5:                yield from $this->sceveu(12);                break;            case 6:                $this->cat();                break;        }    }    /** TABDRT: right edge of the nucleus on a line */    private function tabdrt(int $pos): int    {        $i = self::NB_COL_SILEX + 1;        while (!$this->lit_sil($pos, --$i) && $i > self::NB_COL_SILEX / 2) {        }        return $i;    }    /** TABGAU: left edge of the nucleus on a line */    private function tabgau(int $pos): int    {        $i = 0;        while (!$this->lit_sil($pos, $i++) && $i < self::NB_COL_SILEX / 2) {        }        return $i;    }    /** VALSYL: the error between the nucleus and the shape to obtain */    private function valsyl(): int    {        $ecart = 0;        for ($i = 0; $i < self::NB_LIGNE_SILEX; $i += 2) {            $f = $this->forsyl[intdiv($i, 2)];            $trav1 = abs(self::ss(intdiv(self::NB_COL_SILEX, 2) - 2 * $f - $this->tabgau($i)));            $trav2 = abs(self::ss(intdiv(self::NB_COL_SILEX, 2) + 2 * $f - $this->tabdrt($i)));            $ecart += $trav1 + $trav2;        }        return intdiv($ecart, 3) & 0xFFFF;    }    private function AfficheSuite(): void    {        $this->affomb($this->mobFleches + 19, $this->AireSuite[1], $this->AireSuite[0], 2, 2, true);    }    private function MouseDansSuite(int $posx, int $posy): bool    {        return self::PtInRect([$posx, $posy], $this->AireSuite);    }    /** SILEX: the nucleus and the ideal shape over it */    private function TraceSilex(): void    {        $gbak = $this->curPort;        $this->curPort = $this->debecr;        $this->eff_sour();        $this->tfrvirt();        for ($i = 0; $i < self::NB_LIGNE_SILEX; $i += 4) {            $f = $this->forsyl[intdiv($i, 2)];            if ($f) {                $this->plot_ecr(intdiv($i, 2) + 1, 160 + $f, 0x007);                $this->plot_ecr(intdiv($i, 2) + 1, 160 - $f, 0x007);            }        }        $this->AfficheSuite();        $this->TfrEcr(null);        $this->curPort = $gbak;    }    /** FabriqueNucleus: a raw nucleus on Ecran1 */    private function FabriqueNucleus(): void    {        $this->effima(self::COULEUR_FOND_NUCLEUS);        $half = intdiv(self::NB_COL_SILEX, 2);        $delta1 = 60;        $kk = $this->myRandom(3, 16);        for ($y = 0; $y < self::NB_LIGNE_SILEX; $y++) {            $delta1 += $this->myRandom(-1, 2);            if ($delta1 < 0) {                $delta1 = 0;            }            $posTheo = intdiv($this->forsyl[intdiv($y, 2)] + ($this->forsyl[intdiv($y, 2) + 1] ?? 0), 2);            $pos1 = $half - $posTheo - $delta1;            $delta1 += $this->myRandom(-1, 2);            if ($delta1 < 0) {                $delta1 = 0;            }            $pos2 = $half + $posTheo + $delta1;            if ($pos1 < 0) {                $pos1 = 0;            }            if ($pos2 > self::NB_COL_SILEX) {                $pos2 = self::NB_COL_SILEX;            }            $yy = $y < intdiv(self::NB_LIGNE_SILEX, 2) ? $y : self::NB_LIGNE_SILEX - $y;            for ($x = $pos1; $x <= $half; $x++) {                $couleur = 1 + intdiv(($x - $pos1) * $yy + $this->Rnd() * $kk, 1000);                if ($couleur >= self::NB_COUL_SILEX) {                    $couleur = self::NB_COUL_SILEX - 1;                }                $this->ecrit_sil($y, $x, $couleur);            }            for ($x = $pos2; $x > $half; $x--) {                $couleur = 1 + intdiv(($pos2 - $x) * $yy + $this->Rnd() * $kk, 1000);                if ($couleur >= self::NB_COUL_SILEX) {                    $couleur = self::NB_COUL_SILEX - 1;                }                $this->ecrit_sil($y, $x, $couleur);            }        }    }    /** AppliqueNlleEpaiss: a new thickness if thinner than the old one */    private function AppliqueNlleEpaiss(int $y, int $x, int $epaiss): void    {        $anc = $this->lit_sil($y, $x);        if ($anc < $epaiss || $epaiss === 0) {            $this->ecrit_sil($y, $x, $epaiss);        }    }    /** One line of the flake (left or right) */    private function eclatLigne(int $ligne, int $onde, int $forcef, int $posimp, bool $senson): void    {        if ($senson) {            $a = self::ss(intdiv(self::FORMONDE[$onde] * $forcef, 256) + $posimp);            $distact = self::ss($a - $this->tabgau($ligne));            if ($distact > 0) {                $pas = intdiv(7400, $distact + 1);                if ($pas === 0) {                    $pas = 1;                }                for ($i = 7400; $i >= 0; $i -= $pas, $a--) {                    $this->AppliqueNlleEpaiss($ligne, $a, self::EPAISS[intdiv($i, 100)]);                }            }        } else {            $a = self::ss($posimp - intdiv(self::FORMONDE[$onde] * $forcef, 256));            $distact = self::ss($this->tabdrt($ligne) - $a);            if ($distact > 0) {                $pas = intdiv(7400, $distact);                if ($pas === 0) {                    $pas = 1;                }                for ($i = 7400; $i >= 0; $i -= $pas, $a++) {                    $this->AppliqueNlleEpaiss($ligne, $a, self::EPAISS[intdiv($i, 100)]);                }            }        }    }    /**     * AppliqueImpact: a strike at (posxf, posyf) with the force forcef.     * @return int the sound to play     */    private function AppliqueImpact(int $posxf, int $posyf, int $forcef): int    {        $okbruit = 0;        $cass = 0;        $half = intdiv(self::NB_COL_SILEX, 2);        $posxf *= 2;        $posyf *= 2;        if ($posxf > $this->tabdrt($posyf) || $posxf < $this->tabgau($posyf)) {            $this->TraceSilex();            return $okbruit;        }        $senson = $posxf < $half;        $tfr = intdiv($forcef, 16) & 0xFFFF;        $penet = intdiv($forcef * 50, 256);        if ($senson) {            $tfr = ($tfr + $posxf) & 0xFFFF;            if ($tfr < $penet) {                $tfr = $penet & 0xFFFF;            }            if ($tfr > $half) {                $cass++;            }            $posimp = self::ss($tfr - $penet);            $distcb = ($penet + ($posimp - $this->tabgau($posyf))) & 0xFFFF;        } else {            $tfr = ($posxf - $tfr) & 0xFFFF;            if ($tfr < $half) {                $cass++;            }            $posimp = self::ss($tfr + $penet);            $distcb = ($penet - ($posimp - $this->tabdrt($posyf))) & 0xFFFF;        }        if (intdiv(($forcef - 1) * 48, 256) > $distcb) {            if ($cass) {                // broken: the top or the bottom of the flint goes away                $okbruit = 3;                if ($posyf < intdiv(self::NB_LIGNE_SILEX, 2)) {                    for ($i = $posyf + 4; $i >= 0 && $i < self::NB_LIGNE_SILEX; $i--) {                        for ($j = 0; $j < self::NB_COL_SILEX; $j++) {                            $this->AppliqueNlleEpaiss($i, $j, 0);                        }                    }                } else {                    for ($i = $posyf - 5; $i >= 0 && $i < self::NB_LIGNE_SILEX; $i++) {                        for ($j = 0; $j < self::NB_COL_SILEX; $j++) {                            $this->AppliqueNlleEpaiss($i, $j, 0);                        }                    }                }            } else {                // a flake                $paslec = (($forcef ^ 255) * 8 + 128) & 0xFFFF;                $yact = $posyf & 0xFFFF;                $calc = ($posyf + 1) & 0xFFFF;                $onde = 0;                $trav1 = 0;                while ($onde < 100) {                    if ($yact < self::NB_LIGNE_SILEX) {                        $this->eclatLigne($yact, $onde, $forcef, $posimp, $senson);                    }                    if ($calc < self::NB_LIGNE_SILEX) {                        $this->eclatLigne($calc, $onde, $forcef, $posimp, $senson);                    }                    $calc = ($calc + 1) & 0xFFFF;                    $yact = ($yact - 1) & 0xFFFF;                    $trav1 += $paslec & 255;                    $onde = ($onde + ($trav1 > 255 ? 1 : 0) + ($paslec >> 8)) & 0xFFFF;                    $trav1 &= 255;                }                $okbruit = 4;            }        } else {            // not strong enough            $okbruit = 5;        }        $this->TraceSilex();        return $okbruit;    }    /**     * CURSIL: knapping (regb 0 = spear shape, 1 = axe shape).     * @return \Generator<int, null, mixed, int> the error (valsyl)     */    private function cursil(int $regb): \Generator    {        $gbak = $this->curPort;        $this->curPort = $this->debecr;        $this->silexIndex = [];        for ($i = 0; $i < self::NB_COUL_SILEX; $i++) {            $c = self::TAB_COUL_SILEX_RGB[$i];            $this->palSilex[$i] = Mac::color2Index($c[0], $c[1], $c[2]);            if (!isset($this->silexIndex[$this->palSilex[$i]])) {                $this->silexIndex[$this->palSilex[$i]] = $i;            }        }        $this->curPort = $gbak;        $this->forsyl = $regb === 0 ? self::FORSY1 : self::FORSY2;        $this->FabriqueNucleus();        $this->flefba = 0;        $this->bande(1);        $this->TraceSilex();        while (true) {            do {                $this->vq_mouse($this->bouton, $this->posxf, $this->posyf);                if ($this->bouton) {                    yield;                }            } while ($this->bouton);            do {                yield;                $this->vq_mouse($this->bouton, $this->posxf, $this->posyf);                if ($this->posxf < 32 || $this->posxf > 320 || $this->posyf > 100 || $this->posyf < 2) {                    if ($this->posyf > 100) {                        $this->posyf = 100;                    }                    if ($this->posxf > 320 - 16) {                        $this->posxf = 320 - 16;                    }                    $this->aff_sour($this->DEHORS(), $this->posyf, $this->posxf);                    $this->bouton &= 2;                } elseif ($this->MouseDansSuite($this->posxf, $this->posyf)) {                    $this->aff_sour($this->FRAPPE(), $this->posyf, $this->posxf);                } else {                    $this->aff_sour($this->mobBambou + 22, $this->posyf - 25, $this->posxf);                }            } while (!$this->bouton);            // the flint is accepted            if ($this->MouseDansSuite($this->posxf, $this->posyf)) {                $this->affomb($this->mobFleches + 20, $this->AireSuite[1], $this->AireSuite[0], 2, 2, true);                return $this->valsyl();            }            $gbak = $this->curPort;            $this->CarboniseSetPortWindow();            $this->plot_ecr($this->posyf, $this->posxf, 0x700);            $this->curPort = $gbak;            if ($this->bouton & 1) {                // the force of the strike: how long the button is held                $t = $this->TickCount();                $bidon = 0;                do {                    yield;                    $this->vq_mouse($this->bouton, $bidon, $bidon);                    $forcef = ($this->TickCount() - $t) * 4;                    $this->aff_sour($this->mobBambou + 22, $this->posyf - 25 - intdiv($forcef, 8) - 2, $this->posxf + intdiv($forcef, 8) + 2);                } while ($this->bouton && $forcef < 254);                if ($forcef > 254) {                    $forcef = 254;                }                for ($i = $forcef; $i >= 0; $i -= 32) {                    $this->aff_sour($this->mobBambou + 22, $this->posyf - 25 - intdiv($i, 8), $this->posxf + intdiv($i, 8));                    yield;                }                $okbruit = $this->AppliqueImpact($this->posxf - 85, $this->posyf - 1, $forcef) & 0xFF;                $this->aff_sour($this->mobBambou + 22, $this->posyf - 25, $this->posxf);                if ($okbruit) {                    $this->bruit($okbruit);                }            }        }    }    /**     * GETNOM: types a name of $taille characters in the message line.     * @return \Generator<int, null, mixed, int> the number of characters typed (0 = cancelled)     */    private function getnom(int $mess, string &$position, int $taille): \Generator    {        $pos = 0;        while ($this->litclav()) {        }        for ($i = 0; $i < $taille; $i++) {            $this->zoneu->elements[$i] = 32;        }        if ($this->touchMode) {            // no keyboard on a phone: the name of the last character, a tap accepts it            $defaut = substr($this->nomjoueur, 0, $taille);            for ($pos = 0; $pos < strlen($defaut); $pos++) {                $this->zoneu->elements[$pos] = ord($defaut[$pos]);            }        }        $avant = 1;        while (true) {            $this->efbadi();            $this->comment($mess);            for ($i = 0; $i < $taille && $this->zoneu->elements[$i] !== 32; $i++) {                $this->affcarmess($this->zoneu->elements[$i]);            }            $this->affcarmess(2);            $this->TfrBadAss();            do {                yield;                $car = $this->litclav();                if ($this->touchMode && !$car) {                    $this->vq_mouse($this->bouton, $this->sposx, $this->sposy);                    if ($this->bouton && !$avant && $pos) {                        $car = 13;                    }                    $avant = $this->bouton;                }            } while (!$car);            if ($car === 13) {                // the names of the initiated                for ($i = 0; $i < 7; $i++) {                    $oktrap = 1;                    for ($j = 0; $j < 6; $j++) {                        if ((ord(self::TRAPS[$i][$j]) & 223) !== ($this->zoneu->elements[$j] & 223)) {                            $oktrap = 0;                        }                    }                    if ($oktrap) {                        yield from $this->trapsure($i);                        $pos = 0;                    }                }                if ($pos) {                    $position = '';                    for ($i = 0; $i < $taille; $i++) {                        $position .= chr($this->zoneu->elements[$i]);                    }                }                $this->efbadass();                $this->TfrBadAss();                return $pos;            }            if ($car > 32 && $car <= ord('z') && !($car >= ord('0') && $car <= ord('9'))) {                if ($pos < $taille) {                    $this->zoneu->elements[$pos++] = $car;                }            } elseif ($car === 8 && $pos) {                $this->zoneu->elements[--$pos] = 32;            }        }    }    /** DebutAventure: the 3D world, the title of the adventure (generator: about 60 frames) */    public function DebutAventure(bool $isFirst): \Generator    {        $this->primsap();        $this->generique(false);        if ($isFirst) {            $this->DefautDecor3d();        } else {            $this->InitDecor3d();        }        $this->CreeDecor3d(false);        $this->effvirt(0x000);        $this->effima(0x000);        $this->heure = 10;        $this->chrono(0);        $this->affich($this->dzeta);        $this->tfrvirt();        $this->flefba = 1;        $this->InitGenerique();        $this->generique(false);        $this->TfrEcr(null);        $this->Frame3d(0);        $this->efbadi();        $this->comment(67);        for ($i = 0; $i < 60; $i++) {            $posTimer = $this->sapiensTimer;            $this->chrono(1);            $this->chrono(0);            $this->chrono(0);            $this->affich($this->dzeta);            $this->tfrima();            if ($this->getkey(0) & (32 + 16)) {                $i = 111;            }            $this->flefba = 0;            if ($i === 25) {                $this->comment(68);            }            if ($i === 50) {                $this->comment(69);            }            do {                yield;            } while ($posTimer + 30 >= $this->sapiensTimer);        }        $this->comment(70);        $this->MenuFenetres(1);        $this->efbadi();        $this->comment(132);    }    /** PRIMSAP: the first definitions of the adventure */    public function primsap(): void    {        $this->scenar = $this->jour_act = 1;        $this->scenpr = 20;        $this->fdialo = $this->pos_evt = $this->presence = $this->viecopain = $this->jour_dsk = 0;        $this->dervue = self::VUE_AUCUNE;        $this->prm($this->sap2, 0);        $this->prm($this->sap3, 0);        $this->prm($this->sap4, 0);        $this->mort = 4;        $this->faim = $this->soif = $this->fatig = $this->bless = 255;        $this->xj = 0x5800;        $this->yj = 0x5400;        $this->dzeta = 768;        $this->zoom = 120;        $this->heure = 1;        $this->zj = 40;        $this->liste1->taille = 9;        for ($i = 0; $i < 9; $i++) {            $this->liste1->elements[$i] = $this->listdep[$i];        }        // IsEnreg(): a club instead of the axe        $this->liste1->elements[0] = 22;        for ($i = 0; $i < 4; $i++) {            $this->enetoi[$i] = $this->tenerv[$i];            $this->tagress[$i] = $this->agrdep[$i];        }        // the 4 chiefs come back        for ($i = 0; $i < 4; $i++) {            $this->destine[$i][0] = 1;        }        $this->liste4->taille = 0;    }    /** AireJauge: the whole gauge (ST coordinates) */    private function AireJauge(int $i): array    {        return [114, 6 + 14 * $i, 114 + intdiv($this->maxparam[$this->sexe][$i], 2) + 2, 16 + 14 * $i];    }    /** AireValJauge: the value of a gauge (ST coordinates) */    private function AireValJauge(int $i): array    {        return [115, 7 + 14 * $i, 115 + intdiv($this->param[$i], 2), 15 + 14 * $i];    }    private function AfficheCadreJauge(int $i): void    {        $r = $this->AireJauge($i);        $this->qdFore = Mac::BLACK;        $this->DoFrameRect([$r[0] * 2, $r[1] * 2, $r[2] * 2, $r[3] * 2]);    }    /** JAUGE: a gauge in debecr, copied to the window if $tfr */    private function jauge(int $i, bool $tfr): void    {        $gbak = $this->curPort;        $this->curPort = $this->debecr;        $rval = $this->AireValJauge($i);        $this->AfficheCadreJauge($i);        $this->boxf($rval[1], $rval[0], $rval[3], $rval[2], 2);        $r = $this->AireJauge($i);        if (intdiv($this->param[$i], 2) < intdiv($this->maxparam[$this->sexe][$i], 2)) {            $this->boxf($r[1] + 1, $rval[2], $r[3] - 1, $r[2] - 1, 14);            if ($this->param[$i]) {                $this->boxf($r[3] - 2, $r[0] + 1, $r[3] - 1, $rval[2] - 1, 9);                $this->boxf($r[1] + 1, $rval[2] - 1, $r[3] - 1, $rval[2], 9);            }        }        $this->curPort = $gbak;        if ($tfr) {            $this->TfrEcr([$r[0] * 2, $r[1] * 2, $r[2] * 2, $r[3] * 2]);        }    }    /** PRIMJAUGE: the gauges of the character */    public function primjauge(int $jaugeCredit): void    {        $app = $this->GetApp();        $this->dervue = self::VUE_AUCUNE;        $this->effima(0x050);        $this->tfrvirt();        for ($k = 0; $k < 5; $k++) {            $this->Gotoxy(6, 1 + 2 * $k);            $this->motlst2((string)$app[$k]);        }        if ($jaugeCredit) {            $this->Gotoxy(6, 11);            $this->motlst2((string)$app[5]);        }        $gbak = $this->curPort;        $this->curPort = $this->debecr;        for ($i = 0; $i < 5 + $jaugeCredit; $i++) {            $this->jauge($i, false);        }        $this->curPort = $gbak;        $this->TfrEcr(null);    }    /** Waits for a click, the mouse icon following the pointer */    private function gestAttendsClic(): \Generator    {        do {            yield;            $this->vq_mouse($this->bouton, $this->sposx, $this->sposy);            $this->aff_sour($this->FRAPPE(), $this->sposy, $this->sposx);        } while (!$this->bouton);    }    /** DEFSAP: a new character (name, sex, gauges, look), then a new adventure */    public function defsap(): \Generator    {        $this->sapvit = 0;        $nom = '';        $taille = yield from $this->getnom(88, $nom, 8);        if (!$taille) {            return;        }        $this->flefba = 0;        $this->nomjoueur = substr($nom, 0, $taille);        for ($i = 0; $i < 5; $i++) {            $this->param[$i] = 130;        }        $this->param[5] = 20;        $this->generique(true);        $this->comment(89);        $car = 0;        $this->reset_clav();        if ($this->touchMode) {            // no keyboard on a phone: a choice to tap            $this->TfrBadAss();            $c = -1;            while ($c < 0) {                $c = yield from $this->gestChoix($this->gestTexte('sexe'), [$this->gestTexte('homme'), $this->gestTexte('femme')]);            }            $car = $c === 0 ? $this->HOMME : $this->FEMME;        }        while ($car !== $this->HOMME && $car !== $this->FEMME) {            yield;            $car = $this->litclav() & 223;        }        $this->affcarmess($car);        $this->TfrBadAss();        if ($car === $this->HOMME) {            $this->sexe = 1;            $this->sap1->cross[self::FORME_BUSTE] = 0;            $this->sap1->cross[self::FORME_TETE] = 0;        } else {            $this->sexe = 0;            $this->sap1->cross[self::FORME_BUSTE] = 8;            $this->sap1->cross[self::FORME_TETE] = 12;        }        $this->pal_chron = $this->palhaute;        $this->palhaute = $this->pal2;        $this->primjauge(1);        $this->AfficheSuite();        $cont = 1;        $pos = 0;        $this->sposx = $this->dsposx = 160;        $this->sposy = $this->dsposy = 50;        // the gauges        while ($cont) {            yield from $this->gestAttendsClic();            if ($this->MouseDansSuite($this->sposx, $this->sposy)) {                $this->affomb($this->mobFleches + 20, $this->AireSuite[1], $this->AireSuite[0], 2, 2, true);                $cont = 0;                $this->agilite = $this->param[0] < 223 ? $this->param[0] + 32 : 255;                $this->force = $this->param[1];                $this->charisme = $this->param[2];                $this->vie = $this->param[3];                $this->resistance = $this->param[4];                while ($this->getkey(0)) {                    yield;                }            } else {                $pt = [$this->sposx, $this->sposy];                for ($i = 0; $i < 5; $i++) {                    $r = $this->AireJauge($i);                    if (self::PtInRect($pt, $r)) {                        $pos = $i;                        $this->affomb($this->MOB_DESIGNE(), $r[1] - 4, 86, 0, 0, true);                        do {                            $rval = $this->AireValJauge($pos);                            if ($this->sposx > $rval[2] && $this->param[5] && $this->param[$pos] < $this->maxparam[$this->sexe][$pos]) {                                $this->param[5]--;                                $this->param[$pos]++;                            }                            if ($this->sposx < $rval[2] && $this->param[5] < 254 && $this->param[$pos]) {                                $this->param[5]++;                                $this->param[$pos]--;                            }                            $this->vq_mouse($this->bouton, $this->sposx, $this->sposy);                            $this->jauge($pos, true);                            $this->jauge(5, true);                            $this->aff_sour($this->FRAPPE(), $this->sposy, $this->sposx);                            yield;                        } while ($this->bouton);                        $this->primjauge(1);                        $this->AfficheSuite();                        break;                    }                }            }        }        // the look        $this->eff_sour();        $cont = 1;        $pos = 0;        $this->prm($this->sap1, -1);        $this->effima(0x050);        $this->tfrvirt();        $this->tfrima();        $this->flefba = 0;        $this->eff_sour();        while ($cont) {            $this->sap1->colonne = 28;            $this->sap1->sens = 1;            $this->effima(0x050);            $this->mvtsap($this->sap1);            $this->tfrdemi();            $this->tfrvirt();            $this->AfficheSuite();            $this->affomb($this->MOB_DESIGNE(), ($pos * 2 + 6) * 7 + 1, 100, 4, 4, true);            yield from $this->gestAttendsClic();            if ($this->MouseDansSuite($this->sposx, $this->sposy)) {                $this->affomb($this->mobFleches + 20, $this->AireSuite[1], $this->AireSuite[0], 2, 2, true);                $cont = 0;            } else {                $pos = $this->sposy > 53 ? 1 : 0;                $this->dsposy = $this->sposy;                $this->dsposx = $this->sposx;                if ($this->bouton & 1) {                    if ($pos) {                        $b = $this->sap1->cross[self::FORME_BUSTE] + 2;                        if ($this->sexe) {                            if ($b >= 8) {                                $b = 0;                            }                        } elseif ($b >= 16) {                            $b = 8;                        }                        $this->sap1->cross[self::FORME_BUSTE] = $b & 0xFF;                    } else {                        $t = $this->sap1->cross[self::FORME_TETE] + 2;                        if ($this->sexe) {                            if ($t > 6) {                                $t = 0;                            }                        } elseif ($t > 22) {                            $t = 12;                        }                        $this->sap1->cross[self::FORME_TETE] = $t & 0xFF;                    }                    do {                        yield;                        $this->vq_mouse($this->bouton, $this->sposx, $this->sposy);                    } while ($this->bouton & 1);                }            }        }        $this->eff_sour();        $this->palhaute = $this->pal_chron;        yield from $this->DebutAventure(false);    }    /** AIREJAUGE3d (ST coordinates) */    private function AIREJAUGE3d(int $i): array    {        return [75, 7 + 14 * $i, 75 + self::CAR_UTILE, 15 + 14 * $i];    }    private function JAUGE3d(int $i, float $val): void    {        $r = $this->AIREJAUGE3d($i);        $v = (int)($r[0] + self::CAR_UTILE * $val);        $this->boxf($r[1] - 1, $r[0] - 1, $r[3] + 1, $r[2] + 1, 16);        $this->boxf($r[1], $r[0], $r[3], $v, 14);        $this->boxf($r[1], $v, $r[3], $r[2], 4);    }    /** JAUGES3D: the gauges of the density of the world */    private function JAUGES3D(): void    {        $gbak = $this->curPort;        $this->curPort = $this->debecr;        $this->boxf(0, 32, 101, 287, 5);        // icons: cloud, bird, rock, tree, grass        $icons = [[$this->mob3D + 7, 80, 64, 0], [$this->mob3D + 28, 120, 16, 0], [$this->mob3D + 34, 110, 26, -2], [$this->mob3D + 10, 110, 32, -4], [$this->MOB_HERBE(), 110, 32, -4]];        foreach ($icons as $i => [$mob, $left, $w, $inset]) {            $r = $this->AIREJAUGE3d($i);            $r = [$left, $r[1] * 2 + $inset, $left + $w, $r[3] * 2 - $inset];            $this->Affmob3d($mob, $r, null);        }        $this->AfficheSuite();        $this->JAUGE3d(0, $this->nbNuages / self::NB_NUAGES_MAX);        $this->JAUGE3d(1, $this->nbOiseaux / self::NB_OISEAUX_MAX);        $this->JAUGE3d(2, $this->nbRochers / self::NB_ROCHERS_MAX);        $this->JAUGE3d(3, $this->nbArbres / self::NB_ARBRES_MAX);        $this->JAUGE3d(4, $this->nbHerbes / self::NB_MAX_HERBE_AV_PLAN);        $this->TfrEcr([0, 0, 600, 400]);        $this->curPort = $gbak;    }    /** DEF3D: the density of the world (clouds, birds, rocks, trees, grass) */    public function DEF3D(): \Generator    {        $this->pal_chron = $this->palhaute;        $this->palhaute = $this->pal2;        $cont = 1;        $this->sposx = $this->dsposx = 160;        $this->sposy = $this->dsposy = 50;        while ($cont) {            $this->JAUGES3D();            yield from $this->gestAttendsClic();            if ($this->MouseDansSuite($this->sposx, $this->sposy)) {                $cont = 0;            } else {                for ($i = 0; $i < self::NB_JAUGES_3D; $i++) {                    $r = $this->AIREJAUGE3d($i);                    if ($this->sposy >= $r[1] && $this->sposy <= $r[3]) {                        do {                            $val = ($this->sposx - $r[0]) / ($r[2] - $r[0]);                            switch ($i) {                                case 0: $this->nbNuages = (int)($val * self::NB_NUAGES_MAX); break;                                case 1: $this->nbOiseaux = (int)($val * self::NB_OISEAUX_MAX); break;                                case 2: $this->nbRochers = (int)($val * self::NB_ROCHERS_MAX); break;                                case 3: $this->nbArbres = (int)($val * self::NB_ARBRES_MAX); break;                                case 4: $this->nbHerbes = (int)($val * self::NB_MAX_HERBE_AV_PLAN); break;                            }                            $this->nbNuages = max(0, min(self::NB_NUAGES_MAX, $this->nbNuages));                            $this->nbOiseaux = max(0, min(self::NB_OISEAUX_MAX, $this->nbOiseaux));                            $this->nbRochers = max(0, min(self::NB_ROCHERS_MAX, $this->nbRochers));                            $this->nbArbres = max(0, min(self::NB_ARBRES_MAX, $this->nbArbres));                            $this->nbHerbes = max(0, min(self::NB_MAX_HERBE_AV_PLAN, $this->nbHerbes));                            $this->vq_mouse($this->bouton, $this->sposx, $this->sposy);                            $this->JAUGES3D();                            $this->aff_sour($this->FRAPPE(), $this->sposy, $this->sposx);                            yield;                        } while ($this->bouton);                        break;                    }                }            }        }        $this->CreeDecor3d(true);    }    // ---- in-game dialogs (replace the Mac alerts, dialogs and file pickers) ----------------------    /** Texts of the dialogs, by language, with the accent codes of the game fonts */    private function gestTexte(string $key): string    {        $t = [            'perte' => ['Abandonner l\'aventure en cours ?', 'Abandon the current adventure?', '[Abandonar la aventura?'],            'oui' => ['Oui', 'Yes', 'Si'],            'non' => ['Non', 'No', 'No'],            'retour' => ['Retour', 'Back', 'Volver'],            'charger' => ['Charger quelle aventure ?', 'Load which adventure?', '[Cargar que aventura?'],            'sauver' => ['Sauver dans quel emplacement ?', 'Save in which slot?', '[Guardar en que lugar?'],            'vide' => ['- vide -', '- empty -', '- vacio -'],            'jour' => ['jour', 'day', 'dia'],            'prefs' => ['Pr&f&rences', 'Preferences', 'Preferencias'],            'langue' => ['Langue', 'Language', 'Idioma'],            'bruitages' => ['Bruitages', 'Sound effects', 'Efectos'],            'musique' => ['Musique', 'Music', 'Musica'],            'joueur2' => ['Second joueur', 'Second player', 'Segundo jugador'],            'autonome' => ['autonome', 'automatic', 'automatico'],            'clavier' => ['clavier', 'keyboard', 'teclado'],            'info' => ['SAPIENS 3.0.14', 'SAPIENS 3.0.14', 'SAPIENS 3.0.14'],            'sexe' => ['Homme ou femme ?', 'Man or woman?', '[Hombre o mujer?'],            'homme' => ['Homme', 'Man', 'Hombre'],            'femme' => ['Femme', 'Woman', 'Mujer'],        ];        return $t[$key][$this->langue] ?? $t[$key][1];    }    /**     * A choice drawn in the view (white on blue), chosen with the mouse / a tap or the keys 1-9.     * @param string[] $options     * @return \Generator<int, null, mixed, int> the option chosen, -1 = Escape     */    private function gestChoix(string $titre, array $options): \Generator    {        $saveDeb = new Port(640, 400);        $saveDeb->copyFrom($this->debecr, 0, 0, 640, 400, 0, 0);        $saveWin = new Port(640, 400);        $saveWin->copyFrom($this->mainWindow, 0, 0, 640, 400, 0, 0);        $this->eff_sour();        $this->effvirt(0x007);        $gbak = $this->curPort;        $this->curPort = $this->debecr;        $this->Gotoxy(7, 1);        $this->motlst2($titre);        foreach ($options as $k => $o) {            $this->Gotoxy(9, 3 + 2 * $k);            $this->motlst2(($k + 1) . '  ' . $o);        }        $this->curPort = $gbak;        $this->TfrEcr([64, 2, 574, 202]);        $this->reset_clav();        $choix = -2;        // the button of the click that opened the dialog        do {            yield;            $this->vq_mouse($this->bouton, $this->sposx, $this->sposy);        } while ($this->bouton);        while ($choix === -2) {            yield;            $car = $this->litclav();            if ($car === 27) {                $choix = -1;            } elseif ($car >= ord('1') && $car < ord('1') + count($options)) {                $choix = $car - ord('1');            }            $this->vq_mouse($this->bouton, $this->sposx, $this->sposy);            if ($this->bouton && $this->sposx >= 32 && $this->sposx < 288 && $this->sposy < 101) {                $ligne = intdiv($this->sposy, 7);                foreach ($options as $k => $o) {                    if ($ligne >= 3 + 2 * $k && $ligne <= 4 + 2 * $k) {                        $choix = $k;                    }                }            }        }        do {            yield;            $this->vq_mouse($this->bouton, $this->sposx, $this->sposy);        } while ($this->bouton);        $this->debecr->copyFrom($saveDeb, 0, 0, 640, 400, 0, 0);        $this->mainWindow->copyFrom($saveWin, 0, 0, 640, 400, 0, 0);        return $choix;    }    /** ConfirmePerteAventure (SAPWIND.C): yes / no */    private function gestConfirmePerte(): \Generator    {        $c = yield from $this->gestChoix($this->gestTexte('perte'), [$this->gestTexte('oui'), $this->gestTexte('non')]);        return $c === 0;    }    /** @return string[] the label of each save slot */    private function gestLibellesSlots(): array    {        $l = [];        for ($i = 0; $i < self::GEST_SLOTS; $i++) {            $info = $this->prefs?->get('sapiens_save_' . $i . '_info');            $l[] = is_string($info) && $info !== '' ? $info : $this->gestTexte('vide');        }        return $l;    }    /** DoPreferences (SAPWPREF.C): language, sounds, music, second player */    private function gestPreferences(): \Generator    {        $langues = ['Fran#ais', 'English', 'Castellano'];        while (true) {            $options = [                $this->gestTexte('langue') . ' : ' . $langues[$this->langue],                $this->gestTexte('bruitages') . ' : ' . $this->gestTexte($this->IsBruitages ? 'oui' : 'non'),                $this->gestTexte('musique') . ' : ' . $this->gestTexte($this->okmuse ? 'oui' : 'non'),                $this->gestTexte('joueur2') . ' : ' . $this->gestTexte($this->ModeJoueur2 === self::JOUEUR2_CLAVIER ? 'clavier' : 'autonome'),                $this->gestTexte('retour'),            ];            $c = yield from $this->gestChoix($this->gestTexte('prefs'), $options);            switch ($c) {                case 0:                    $this->langue = ($this->langue + 1) % 3;                    $this->InitAndCheckTexts();                    break;                case 1:                    $this->IsBruitages = $this->IsBruitages ? 0 : 1;                    break;                case 2:                    $this->okmuse = $this->okmuse ? 0 : 1;                    if ($this->okmuse) {                        $this->MusicInit($this->quelle_musique ?: 1);                    } elseif ($this->sound !== null && method_exists($this->sound, 'stopMusic')) {                        $this->sound->stopMusic();                    }                    break;                case 3:                    $this->ModeJoueur2 = $this->ModeJoueur2 === self::JOUEUR2_CLAVIER ? self::JOUEUR2_AUTONOME : self::JOUEUR2_CLAVIER;                    break;                default:                    $this->SauvePref();                    // the menus in the new language                    $this->lastmen = 255;                    $this->encadre($this->tclin[0], $this->tccol[0], $this->tcnlin[0], $this->tcncol[0]);                    return;            }        }    }    /** Informations: version and credits in the view, and the manual (DoAide) */    private function gestInfo(): \Generator    {        yield from $this->gestChoix($this->gestTexte('info'), [            'Didier et Olivier Guillion 1986-2017',            'Musique : Gilles Soulet',            'Port PHP / JumPHP - GPL v3',            $this->gestTexte('retour'),        ]);    }    // ---- saves -----------------------------------------------------------------------------------    /** The soma[] cells (SAPGLOB.C) of the save: 161 bytes, multi-byte values little-endian */    private function gestSomaBytes(): string    {        $b = '';        foreach (['soif', 'fatig', 'faim', 'mort', 'bless', 'zoom', 'agilite', 'force', 'resistance', 'charisme', 'vie', 'sexe', 'viecopain'] as $v) {            $b .= chr($this->$v & 0xFF);        }        foreach ($this->sap1->cross as $c) {            $b .= chr($c & 0xFF);        }        foreach ($this->sap3->cross as $c) {            $b .= chr($c & 0xFF);        }        $b .= chr($this->scenar & 0xFF) . chr($this->scenpr & 0xFF) . chr($this->ciel & 0xFF) . chr($this->terre & 0xFF);        $b .= chr($this->liste1->taille & 0xFF);        for ($i = 0; $i < 30; $i++) {            $b .= chr($this->liste1->elements[$i] & 0xFF);        }        for ($i = 0; $i < 4; $i++) {            $b .= chr($this->tagress[$i] & 0xFF) . pack('v', $this->enetoi[$i] & 0xFFFF);        }        $b .= pack('v4', $this->xj & 0xFFFF, $this->yj & 0xFFFF, $this->dzeta & 0xFFFF, $this->heure & 0xFFFF);        $b .= chr($this->sap3->vie & 0xFF);        $b .= str_pad(substr($this->nomjoueur, 0, 8), 8, "\0");        $b .= str_pad(substr($this->nomcopain, 0, 6), 6, "\0");        $b .= chr($this->present[2] & 0xFF);        $b .= pack('v', $this->jour_act & 0xFFFF);        for ($i = 0; $i < 6; $i++) {            $b .= chr($this->param[$i] & 0xFF);        }        for ($i = 0; $i < 7; $i++) {            $b .= chr($this->tabclav1[$i] & 0xFF);        }        for ($i = 0; $i < 7; $i++) {            $b .= chr($this->tabclav2[$i] & 0xFF);        }        $b .= chr($this->liste8->elements[4] & 0xFF) . chr($this->liste8->elements[5] & 0xFF);        $b .= chr($this->bidon & 0xFF);        foreach (['FreqOrage', 'DistMinFoudroie', 'nbNuages', 'nbOiseaux', 'nbRochers', 'nbHommes', 'nbArbres', 'nbHerbes', 'VitesseDeplacementNuages'] as $v) {            $b .= pack('V', self::l32($this->$v));        }        return $b;    }    private function gestSomaLoad(string $b): void    {        $p = 0;        $u8 = function () use ($b, &$p): int {            return ord($b[$p++]);        };        $u16 = function () use ($b, &$p): int {            $v = unpack('v', $b, $p)[1];            $p += 2;            return $v;        };        foreach (['soif', 'fatig', 'faim', 'mort', 'bless', 'zoom', 'agilite', 'force', 'resistance', 'charisme', 'vie', 'sexe', 'viecopain'] as $v) {            $this->$v = $u8();        }        for ($i = 0; $i < 8; $i++) {            $this->sap1->cross[$i] = $u8();        }        for ($i = 0; $i < 8; $i++) {            $this->sap3->cross[$i] = $u8();        }        $this->scenar = $u8();        $this->scenpr = self::sc($u8());        $this->ciel = $u8();        $this->terre = $u8();        $this->liste1->taille = $u8();        for ($i = 0; $i < 30; $i++) {            $this->liste1->elements[$i] = $u8();        }        for ($i = 0; $i < 4; $i++) {            $this->tagress[$i] = $u8();            $this->enetoi[$i] = $u16();        }        $this->xj = $u16();        $this->yj = $u16();        $this->dzeta = self::ss($u16());        $this->heure = $u16();        $this->sap3->vie = $u8();        $n = substr($b, $p, 8);        $p += 8;        $this->nomjoueur = explode("\0", $n)[0];        $n = substr($b, $p, 6);        $p += 6;        $this->nomcopain = explode("\0", $n)[0];        $this->present[2] = self::sc($u8());        $this->jour_act = $u16();        for ($i = 0; $i < 6; $i++) {            $this->param[$i] = $u8();        }        for ($i = 0; $i < 7; $i++) {            $this->tabclav1[$i] = $u8();        }        for ($i = 0; $i < 7; $i++) {            $this->tabclav2[$i] = $u8();        }        $this->liste8->elements[4] = $u8();        $this->liste8->elements[5] = $u8();        $this->bidon = ($this->bidon & 0xFF00) | $u8();        foreach (['FreqOrage', 'DistMinFoudroie', 'nbNuages', 'nbOiseaux', 'nbRochers', 'nbHommes', 'nbArbres', 'nbHerbes', 'VitesseDeplacementNuages'] as $v) {            $this->$v = self::l32(unpack('V', $b, $p)[1]);            $p += 4;        }    }    /** SAUVE: the adventure in a slot (one save per day of game) */    public function sauve(): \Generator    {        $this->flefba = 0;        if ($this->jour_act === $this->jour_dsk) {            // one adventure per day            $this->comment(113);            return;        }        $slot = yield from $this->gestChoix($this->gestTexte('sauver'), [...$this->gestLibellesSlots(), $this->gestTexte('retour')]);        if ($slot < 0 || $slot >= self::GEST_SLOTS || $this->prefs === null) {            return;        }        $this->gestSlot = $slot;        $clair = $this->gestSomaBytes();        $data = 'GOD';        $checksum = 111;        $this->initrnd(ord('S'), ord('A'), ord('P'));        $n = strlen($clair);        for ($i = 0; $i < $n; $i++) {            $car = ord($clair[$i]) ^ $this->rnd0();            $checksum += $car;            $data .= chr($car);        }        $data .= chr($checksum & 255);        $data .= $this->SauveEltsDecorsMobiles();        $this->prefs->set('sapiens_save_' . $slot, base64_encode($data));        $this->prefs->set('sapiens_save_' . $slot . '_info', $this->nomjoueur . ', ' . $this->gestTexte('jour') . ' ' . $this->jour_act);        $this->jour_dsk = $this->jour_act;    }    /** CHARGE: an adventure from a slot */    public function charge(): \Generator    {        $this->flefba = 0;        $slot = yield from $this->gestChoix($this->gestTexte('charger'), [...$this->gestLibellesSlots(), $this->gestTexte('retour')]);        if ($slot < 0 || $slot >= self::GEST_SLOTS) {            return;        }        $raw = $this->prefs?->get('sapiens_save_' . $slot);        $data = is_string($raw) ? base64_decode($raw, true) : false;        $this->flefba = 0;        if ($data === false || $data === '') {            $this->comment(92);            return;        }        $this->gestSlot = $slot;        $this->primsap();        $checksum = 111;        $taille = strlen($this->gestSomaBytes());        $rest = '';        $this->initrnd(ord('S'), ord('A'), ord('P'));        if (substr($data, 0, 3) === 'GOD') {            if (strlen($data) < 3 + $taille) {                $this->flefba = 0;                $this->comment(92);                return;            }            $clair = '';            for ($i = 0; $i < $taille; $i++) {                $car = ord($data[3 + $i]);                $checksum += $car;                $clair .= chr($car ^ $this->rnd0());            }            $this->gestSomaLoad($clair);            $checksum &= 255;            if ($checksum !== ord($data[3 + $taille] ?? "\0")) {                $this->flefba = 0;                $this->comment(92);                $this->primsap();                $this->mort = 0;            }            $rest = (string)substr($data, 4 + $taille);        } else {            $this->comment(92);        }        $this->CreeDecor3d(true);        $this->ChargeEltsDecorsMobiles($rest);        $this->minute = $this->zj = 0;        $this->chrono(0);        $this->jour_dsk = $this->jour_act;        $this->affich($this->dzeta);        $this->tfrima();        $this->generique(true);        if ($this->scenar === 6) {            yield from $this->sceveu(6);        }    }    /** TRGEST: the menu SAPIENS */    public function trgest(int $regb): \Generator    {        $this->MontreSouris();        switch ($regb) {            case 0:                // informations                yield from $this->gestInfo();                $this->DoAide();                break;            case 1:                if (yield from $this->gestConfirmePerte()) {                    yield from $this->charge();                    $this->inimen(8);                }                break;            case 2:                yield from $this->sauve();                break;            case 3:                // a new character: a new adventure                if (yield from $this->gestConfirmePerte()) {                    yield from $this->defsap();                }                break;            case 4:                yield from $this->gestPreferences();                break;        }    }    /** AJUSTE: MIDI settings (OLD_VERSION only in the original) */    public function ajuste(): \Generator    {        $car = 0;        yield from $this->sceveu(10);        $this->reset_clav();        while (!$car) {            yield;            $car = $this->litclav() & 223;        }    }    /** BRAVO: the result of the knapping fades in (a frame per step: the original was that slow) */    private function bravo(int $quoi): \Generator    {        $gbak = $this->curPort;        $this->curPort = $this->Ecran1;        $this->eff_sour();        $this->effima(self::COULEUR_FOND_NUCLEUS);        for ($k = 0; $k < 20; $k++) {            $coeff = $k * 0.05;            $this->GereInterface();            $map = $this->CalculePaletteAttenuee($quoi, $coeff, 999);            $this->Affmobbrt($quoi, 96, 1, 0, $map);            $this->tfrima();            yield;        }        $this->curPort = $gbak;    }    /** TRFAB: make a weapon from a flint (regb 0 spear, 1 axe) */    public function trfab(int $regb): \Generator    {        $this->flefba = 0;        $this->clrbadi();        $i = $this->liste1->taille - 1;        while ($i >= 0 && $this->liste1->elements[$i] !== 19) {            $i--;        }        if ($i < 0) {            // you have no flint            $this->comment(87);            return;        }        $this->dervue = self::VUE_AUCUNE;        $tfr = $this->ciel_degr;        $this->bruit(10);        $trace_syl = yield from $this->cursil($regb);        $this->palhaute = $this->pal2;        $this->flefba = 0;        if ($trace_syl > self::PRECISION_SILEX) {            $this->bruit(7);            yield from $this->bravo($this->MOB_SILEX_CASSE());            $this->liste1->elements[$i] = 20;            $this->comment(86);        } else {            $this->bruit(6);            if ($trace_syl < 150) {                $this->comment(110);            }            if ($regb !== 1) {                $this->liste1->elements[$i] = 23;                yield from $this->bravo($this->MOB_SILEX_SAGAIE());            } else {                $this->liste1->elements[$i] = 21;                yield from $this->bravo($this->MOB_SILEX_HACHE());            }            $this->comment(85);        }        $this->bande(0);        $this->generique(true);        $this->ciel_degr = $tfr;    }}