SAPIENS

by davidnurdin · Remakes
not rated yet
▶ Play Source Edit GitHub
. / src / Sapiens / World3dTrait.php · commit b1b5787
<?phpnamespace App\Sapiens;/** * SAP3D.C: the objects of the world (shapes "Forme", objects "Mont" in the list aigoual), the world * generation (always seeded with 'M','Y','R'), the landscape loop (clav3d), lightning, the final * animation (Final). * * A Mont is an array ['posx', 'posy', 'ign', 'forme' => index in $this->formes, 'distance'] and a Forme an * array ['name', 'nbrdisk', 'relief', 'hauteur', 'rayon', 'num_mob', 'numMob3d'] ($this->formeIdx: name => * index): `$this->formes[$this->aigoual[$i]['forme']]['num_mob']` is `(*aigoual)[i].ptrforme->num_mob`. * The shapes are shared: changing numMob3d of one changes it for every object of that shape, like in C. * * Speed: the small static objects (trees, rocks...) are in a grid of cells (gridCell), so that a frame * only looks at the cells around the player (ChaineObjet); the counts of a new adventure are capped. */trait World3dTrait{    public const NB_NUAGES = 100;    public const NB_OISEAUX = 20;    public const NB_ROCHERS = 300;    public const NB_ARBRES = 800;    public const NB_HOMMES = 0;    public const FREQ_ORAGE = 9990;    public const DIST_MIN_FOUDROIE = 250;    public const NB_HERBE_AV_PLAN = 20;    private const NB_MAX_HERBE_AV_PLAN = 100;    /** caps of the random counts of InitDecor3d (the original goes up to 2000 / 400 / 50000 / 100000) */    public const CAP_NUAGES = 300;    public const CAP_OISEAUX = 100;    public const CAP_ROCHERS = 2000;    public const CAP_ARBRES = 3000;    private const VITESSE_DEPLACEMENT_OISEAUX = 10;    private const MOB_OISEAU = 28;    private const MOB_HOMME = 35;    private const NB_OBJ_REF = 223;    private const GRID = 6144;    /** name => [nbrdisk, relief, hauteur, rayon, num_mob, numMob3d] */    private const FORMES = [        'forme1' => [5, 0, 0, 900, 0, 1],        'forme2' => [8, 2, 0, 1900, 1, 2],        'forme4' => [9, 1, 0, 5000, 2, 4],        'forme6' => [14, 1, 0, 10000, 3, 3],        'forme7' => [16, 1, 0, 8000, 4, 5],        'forme8' => [12, 1, 0, 2000, 5, 30],        'nuage1' => [8, 3, 6000, 3500, -1, 7],        'nuage2' => [9, 3, 4000, 3500, -1, 8],        'nuage3' => [5, 3, 3000, 3500, -1, 9],        'eclair' => [10, 3, 100, 500, -1, 36],        'toucheEclair' => [10, 4, 2, 60, 6, 37],        'foudroye' => [10, 4, 1, 60, 6, 38],        'peupli' => [10, 4, 1, 60, 6, 10],        'chene' => [10, 4, 1, 40, 6, 11],        'noiset' => [10, 4, 1, 40, 6, 12],        'fontaine' => [1, 4, 2, 40, -1, 24],        'totem' => [10, 4, 2, 40, -1, 6],        'hutte' => [1, 4, 2, 40, -1, 32],        'oiseau' => [10, 4, 500, 40, -1, 28],        'genevr' => [10, 4, 1, 40, 6, 13],        'hetre' => [10, 4, 1, 40, 6, 14],        'roc1' => [8, 1, 0, 210, 7, 15],        'roc2' => [7, 1, 0, 600, 7, 16],        'roc3' => [8, 1, 0, 600, 7, 17],        'fumee' => [9, 3, 2, 40, 8, 18],        'foyer' => [1, 4, 2, 20, -1, 33],        'homme' => [1, 4, 1, 20, -1, 35],        'building' => [12, 1, 0, 100, 5, 35],        'rocher' => [1, 4, 1, 40, -1, 34],    ];    /** ObjetsRef: the fixed objects [posx, posy, ign, shape] */    private const OBJETS_REF = [        [20000, 1590, 20, 'forme1'],        [5120, 53760, 20, 'forme2'],        [26000, 16000, 100, 'forme4'],        [9984, 16860, 140, 'forme6'],        [36133, 53402, 200, 'forme7'],        [50435, 17637, 80, 'forme8'],        [22656, 21632, 80, 'fumee'],        [22656, 21632, 15, 'foyer'],        [22704, 21632, 100, 'hutte'],        [22640, 21632, 100, 'hutte'],        [39680, 14336, 60, 'fumee'],        [39680, 14336, 15, 'foyer'],        [39744, 14400, 140, 'hutte'],        [39808, 14432, 100, 'hutte'],        [39872, 14432, 100, 'hutte'],        [39808, 14592, 100, 'hutte'],        [39552, 14592, 100, 'hutte'],        [39552, 14080, 100, 'hutte'],        [39424, 14400, 100, 'hutte'],        [39424, 14848, 100, 'hutte'],        [12032, 52992, 60, 'fumee'],        [12032, 52992, 15, 'foyer'],        [11892, 53082, 100, 'hutte'],        [11992, 53162, 100, 'hutte'],        [11792, 52992, 100, 'hutte'],        [49920, 43520, 70, 'fumee'],        [49920, 43520, 15, 'foyer'],        [50120, 43720, 100, 'hutte'],        [50220, 43720, 100, 'hutte'],        [50220, 43820, 100, 'hutte'],        [49952, 43520, 15, 'totem'],        [4000, 4000, 20, 'forme2'],        [9000, 5000, 33, 'forme2'],        [9000, 5000, 30, 'forme2'],        [13000, 3500, 16, 'forme2'],        [8000, 1800, 26, 'forme2'],        [40000, 5000, 33, 'forme2'],        [25000, 52000, 30, 'forme2'],        [9000, 5000, 30, 'forme2'],        [37000, 30000, 16, 'forme2'],        [32000, 32000, 33, 'forme2'],        [43000, 42000, 25, 'forme2'],        [4000, 50000, 46, 'forme2'],        [33000, 8000, 120, 'forme4'],        [55000, 5000, 100, 'forme4'],        [38000, 23500, 140, 'forme7'],        [28000, 26000, 100, 'forme4'],        [13000, 37000, 120, 'forme7'],        [37000, 37000, 100, 'forme4'],        [47000, 35000, 100, 'forme4'],        [33000, 8000, 120, 'forme4'],        [55000, 42000, 120, 'forme4'],        [5000, 32000, 140, 'forme4'],        [20000, 30000, 120, 'forme4'],        [25000, 40000, 120, 'forme7'],        [22656, 21280, 8, 'chene'],        [22272, 20992, 8, 'genevr'],        [22784, 20992, 8, 'peupli'],        [44000, 45800, 20, 'totem'],        [44000, 45900, 20, 'totem'],        [43900, 45800, 20, 'totem'],        [25000, 22500, 10, 'chene'],        [19000, 24000, 8, 'chene'],        [13000, 42500, 10, 'chene'],        [3000, 30000, 12, 'chene'],        [4500, 46000, 4, 'chene'],        [17000, 44000, 15, 'chene'],        [17500, 49000, 9, 'chene'],        [32000, 45000, 10, 'chene'],        [19200, 47200, 12, 'chene'],        [9000, 45000, 7, 'chene'],        [32100, 15050, 15, 'chene'],        [32200, 15200, 12, 'chene'],        [32300, 15400, 10, 'chene'],        [32400, 15410, 10, 'chene'],        [32000, 15600, 10, 'chene'],        [32050, 15700, 7, 'chene'],        [12000, 50000, 7, 'chene'],        [32000, 15800, 10, 'chene'],        [31900, 15900, 15, 'chene'],        [32050, 15700, 7, 'chene'],        [33000, 16000, 17, 'chene'],        [33500, 16000, 10, 'chene'],        [31900, 15900, 15, 'chene'],        [33000, 13500, 10, 'chene'],        [38000, 12000, 7, 'chene'],        [32000, 15000, 7, 'chene'],        [32800, 18100, 10, 'chene'],        [33050, 16500, 9, 'chene'],        [32800, 18200, 10, 'chene'],        [35000, 16000, 7, 'chene'],        [25000, 21500, 10, 'chene'],        [10900, 28000, 12, 'chene'],        [13000, 31000, 10, 'chene'],        [15000, 26000, 10, 'chene'],        [29000, 20500, 9, 'chene'],        [21700, 8500, 12, 'chene'],        [23800, 6000, 9, 'chene'],        [20000, 5500, 9, 'chene'],        [24000, 5000, 9, 'chene'],        [26000, 10000, 9, 'chene'],        [20000, 9000, 9, 'chene'],        [15000, 6000, 9, 'chene'],        [15500, 47000, 15, 'peupli'],        [24000, 48500, 20, 'peupli'],        [20500, 18000, 12, 'peupli'],        [40000, 200, 18, 'peupli'],        [34000, 2000, 16, 'peupli'],        [32000, 1000, 12, 'peupli'],        [100, 41000, 12, 'peupli'],        [22000, 25000, 18, 'peupli'],        [21000, 21000, 20, 'peupli'],        [13500, 44000, 12, 'peupli'],        [33500, 19000, 12, 'peupli'],        [42000, 17000, 20, 'peupli'],        [34000, 17000, 12, 'peupli'],        [38000, 16000, 9, 'peupli'],        [39050, 14050, 12, 'peupli'],        [21000, 23000, 14, 'peupli'],        [12100, 29050, 12, 'peupli'],        [11000, 30000, 20, 'peupli'],        [13000, 46000, 10, 'hetre'],        [17500, 45000, 15, 'hetre'],        [19000, 47000, 10, 'hetre'],        [19000, 48500, 18, 'hetre'],        [22000, 23000, 10, 'hetre'],        [13000, 42500, 15, 'hetre'],        [3500, 47000, 10, 'hetre'],        [7000, 41000, 11, 'hetre'],        [32900, 17000, 15, 'hetre'],        [40000, 14000, 11, 'hetre'],        [40000, 15000, 8, 'hetre'],        [39000, 12200, 11, 'hetre'],        [32800, 18000, 15, 'hetre'],        [42000, 15000, 18, 'hetre'],        [41500, 14000, 15, 'hetre'],        [42500, 16000, 15, 'hetre'],        [31500, 22000, 15, 'hetre'],        [12000, 29000, 17, 'hetre'],        [11900, 29100, 15, 'hetre'],        [12500, 30000, 15, 'hetre'],        [5000, 39000, 10, 'genevr'],        [19250, 49200, 15, 'genevr'],        [35000, 43000, 4, 'genevr'],        [41000, 2000, 2, 'genevr'],        [4000, 40000, 6, 'genevr'],        [14000, 45000, 2, 'genevr'],        [14000, 45000, 12, 'genevr'],        [19100, 47050, 10, 'genevr'],        [31500, 14500, 10, 'genevr'],        [34000, 15000, 10, 'genevr'],        [36000, 17000, 10, 'genevr'],        [40000, 18000, 10, 'genevr'],        [31000, 19000, 12, 'genevr'],        [14000, 30000, 10, 'genevr'],        [10000, 29500, 10, 'genevr'],        [13000, 27000, 10, 'genevr'],        [40000, 42000, 10, 'genevr'],        [41000, 44000, 10, 'genevr'],        [47000, 45000, 10, 'genevr'],        [48000, 44000, 10, 'genevr'],        [48000, 48000, 10, 'genevr'],        [21000, 20000, 10, 'noiset'],        [21000, 20000, 10, 'fontaine'],        [12400, 28500, 10, 'noiset'],        [12400, 28500, 10, 'fontaine'],        [48000, 42000, 10, 'noiset'],        [48000, 42000, 10, 'fontaine'],        [2000, 2000, 10, 'noiset'],        [2000, 2000, 10, 'fontaine'],        [39000, 800, 10, 'noiset'],        [39000, 800, 10, 'fontaine'],        [16000, 42000, 10, 'noiset'],        [16000, 42000, 10, 'fontaine'],        [32000, 16000, 10, 'noiset'],        [32000, 16000, 10, 'fontaine'],        [45000, 25500, 10, 'noiset'],        [45000, 25500, 10, 'fontaine'],        [6000, 45000, 10, 'noiset'],        [6000, 45000, 10, 'fontaine'],        [50000, 50000, 10, 'noiset'],        [50000, 50000, 5, 'fontaine'],        [24320, 18944, 10, 'noiset'],        [24320, 18944, 10, 'fontaine'],        [32768, 17408, 10, 'noiset'],        [32768, 17408, 5, 'fontaine'],        [40192, 14336, 10, 'noiset'],        [40192, 14336, 5, 'fontaine'],        [43776, 16896, 10, 'noiset'],        [43776, 16896, 5, 'fontaine'],        [100, 39000, 100, 'roc1'],        [15000, 46000, 45, 'roc2'],        [42000, 16000, 75, 'roc3'],        [33050, 17000, 95, 'roc1'],        [5000, 38000, 100, 'roc2'],        [7000, 39000, 150, 'roc3'],        [4000, 43000, 110, 'roc1'],        [7000, 44000, 100, 'roc2'],        [10000, 42000, 150, 'roc1'],        [15000, 43000, 100, 'roc2'],        [15000, 45000, 140, 'roc1'],        [18000, 47000, 98, 'roc2'],        [19000, 50000, 100, 'roc1'],        [42000, 46000, 100, 'roc1'],        [44000, 45000, 200, 'roc2'],        [46000, 42000, 100, 'roc2'],        [38000, 44000, 90, 'roc1'],        [35000, 1000, 110, 'roc2'],        [37000, 3000, 100, 'roc2'],        [36000, 15000, 110, 'roc2'],        [37000, 3000, 100, 'roc2'],        [39000, 13000, 100, 'roc2'],        [41000, 13000, 100, 'roc2'],        [36000, 18000, 100, 'roc2'],        [39000, 17000, 100, 'roc2'],        [38000, 12000, 132, 'roc2'],        [42000, 15000, 100, 'roc2'],        [20000, 22000, 100, 'roc2'],        [13000, 28000, 100, 'roc2'],        [11000, 32000, 100, 'roc2'],        [46000, 47500, 100, 'roc2'],        [9500, 50000, 100, 'roc3'],        [1, 1, 10, 'chene'],    ];    /** @var array<int, array<string, mixed>> the shapes (struct Forme) */    public array $formes = [];    /** @var array<string, int> name of a shape => index in $formes */    public array $formeIdx = [];    /** @var array<int, array{posx:int, posy:int, ign:int, forme:int, distance:int}> MontHandle aigoual: the objects of the world */    public array $aigoual = [];    public int $nbreobj = 0;    public int $nbNuages = 0;    public int $nbEclairs = 0;    public int $nbOiseaux = 0;    public int $nbRochers = 0;    public int $nbHommes = 0;    public int $nbArbres = 0;    public int $FreqOrage = 0;    public int $FreqEclair = 0;    public int $DistMinFoudroie = 0;    public int $VitesseDeplacementNuages = 0;    private int $PremEclair = 0;    private int $PremArbre = 0;    private int $PremRocher = 0;    /** index of the tree struck (-1: none) */    private int $ArbreFoudroye = -1;    private int $okcarte = 0;    /** @var int[] indexes of the objects of height 0 (mountains: altitude, EstDansMontagne) */    private array $montagnes = [];    /** @var array<int, int[]> grid cell => indexes of small static objects */    private array $grid = [];    /** @var int[] the other objects (looked at every frame) */    private array $horsGrille = [];    private bool $gridDirty = true;    private function initFormes(): void    {        if ($this->formes !== []) {            return;        }        foreach (self::FORMES as $name => [$nb, $relief, $hauteur, $rayon, $numMob, $mob3d]) {            $this->formeIdx[$name] = count($this->formes);            $this->formes[] = [                'name' => $name, 'nbrdisk' => $nb, 'relief' => $relief, 'hauteur' => $hauteur,                'rayon' => $rayon, 'num_mob' => $numMob, 'numMob3d' => $mob3d,            ];        }    }    private function newMont(int $x, int $y, int $ign, int $forme): array    {        return ['posx' => $x & 0xFFFF, 'posy' => $y & 0xFFFF, 'ign' => $ign & 0xFFFF, 'forme' => $forme, 'distance' => 0];    }    /** AjouteObjet3d: adds $nbObj objects, returns the index of the first one */    public function AjouteObjet3d(int $nbObj): int    {        $this->nbreobj += $nbObj;        $this->gridDirty = true;        return $this->nbreobj - $nbObj;    }    /** Sets the object $index (and keeps the list of the mountains) */    private function poseObjet(int $index, array $mont): void    {        $this->aigoual[$index] = $mont;        if ($this->formes[$mont['forme']]['hauteur'] === 0) {            $this->montagnes[] = $index;        }    }    /** EstDansMontagne: the position is inside a mountain */    private function EstDansMontagne(int $x, int $y): bool    {        foreach ($this->montagnes as $i) {            $m = $this->aigoual[$i];            if ($this->formes[$m['forme']]['hauteur'] === 0) {                $dist = $this->distance(abs($m['posx'] - $x), abs($m['posy'] - $y));                if ($dist < $this->formes[$m['forme']]['rayon']) {                    return true;                }            }        }        return false;    }    /** DefautDecor3d: the world of the first adventure */    public function DefautDecor3d(): void    {        $this->FreqOrage = self::FREQ_ORAGE;        $this->DistMinFoudroie = self::DIST_MIN_FOUDROIE;        $this->nbNuages = self::NB_NUAGES;        $this->nbOiseaux = self::NB_OISEAUX;        $this->nbRochers = self::NB_ROCHERS;        $this->nbHommes = self::NB_HOMMES;        $this->nbArbres = self::NB_ARBRES;        $this->nbHerbes = self::NB_HERBE_AV_PLAN;        $this->FreqEclair = 1000;        $this->VitesseDeplacementNuages = 20;    }    /** InitDecor3d: random counts for each new adventure (capped, see CAP_*) */    public function InitDecor3d(): void    {        $this->FreqOrage = $this->myRandom(9900, 10000);        $this->DistMinFoudroie = $this->myRandom(20, 500);        $this->nbNuages = min(self::CAP_NUAGES, $this->myRandom(self::NB_NUAGES, self::NB_NUAGES * 20));        $this->nbOiseaux = min(self::CAP_OISEAUX, $this->myRandom(self::NB_OISEAUX, self::NB_OISEAUX * 20));        $this->nbRochers = min(self::CAP_ROCHERS, $this->myRandom(self::NB_ROCHERS, 50000));        $this->nbArbres = min(self::CAP_ARBRES, $this->myRandom(self::NB_ARBRES, 100000));        $this->nbHerbes = $this->myRandom(self::NB_HERBE_AV_PLAN, self::NB_MAX_HERBE_AV_PLAN);        $this->nbHommes = 0;        $this->FreqEclair = 1000;        $this->VitesseDeplacementNuages = $this->myRandom(1, 120);    }    /** Applies the caps to counts read from a saved game */    public function capDecor3d(): void    {        $this->nbNuages = max(0, min(self::CAP_NUAGES, $this->nbNuages));        $this->nbOiseaux = max(0, min(self::CAP_OISEAUX, $this->nbOiseaux));        $this->nbRochers = max(0, min(self::CAP_ROCHERS, $this->nbRochers));        $this->nbArbres = max(0, min(self::CAP_ARBRES, $this->nbArbres));        $this->nbHommes = 0;    }    /** CreeDecor3d: builds the list of the objects of the world */    public function CreeDecor3d(bool $display): void    {        $this->initFormes();        if ($display) {            $this->flefba = 0;            $this->comment(137);        }        $this->capDecor3d();        $this->initrnd(ord('M'), ord('Y'), ord('R'));        $this->nbEclairs = $this->nbNuages;        $this->aigoual = [];        $this->montagnes = [];        $this->PremEclair = 0;        $this->PremArbre = 0;        $this->PremRocher = 0;        $this->ArbreFoudroye = -1;        $this->nbreobj = 0;        $f = $this->formeIdx;        for ($i = 0; $i < $this->nbNuages; $i++) {            $x = $this->myRandom(0, 32767) * 2;            $y = $this->myRandom(0, 32767) * 2;            $ign = $this->myRandom(100, 200);            $forme = match ($this->myRandom(0, 3)) {                0 => $f['nuage1'],                1 => $f['nuage2'],                default => $f['nuage3'],            };            $this->poseObjet($this->AjouteObjet3d(1), $this->newMont($x, $y, $ign, $forme));        }        // a lightning for each cloud        for ($i = 0; $i < $this->nbEclairs; $i++) {            $n = $this->aigoual[$i];            $half = intdiv($this->formes[$n['forme']]['rayon'], 2);            $index = $this->AjouteObjet3d(1);            $this->poseObjet($index, $this->newMont($n['posx'] + $half, $n['posy'] + $half, 0, $f['eclair']));            if ($this->PremEclair === 0) {                $this->PremEclair = $index;            }        }        // the birds        for ($i = 0; $i < $this->nbOiseaux; $i++) {            $x = $this->myRandom(0, 32767) * 2;            $y = $this->myRandom(0, 32767) * 2;            $this->poseObjet($this->AjouteObjet3d(1), $this->newMont($x, $y, 10, $f['oiseau']));        }        $index = $this->AjouteObjet3d(self::NB_OBJ_REF);        foreach (self::OBJETS_REF as $k => [$x, $y, $ign, $name]) {            $this->poseObjet($index + $k, $this->newMont($x, $y, $ign, $f[$name]));        }        // the trees        for ($i = 0; $i < $this->nbArbres; $i++) {            do {                $x = $this->myRandom(0, 32767) * 2;                $y = $this->myRandom(0, 32767) * 2;            } while ($this->EstDansMontagne($x, $y));            $ign = $this->myRandom(5, 20);            $forme = match ($this->myRandom(0, 3)) {                0 => $f['genevr'],                1 => $f['chene'],                default => $f['peupli'],            };            $index = $this->AjouteObjet3d(1);            if ($this->PremArbre === 0) {                $this->PremArbre = $index;            }            $this->poseObjet($index, $this->newMont($x, $y, $ign, $forme));        }        // the rocks        for ($i = 0; $i < $this->nbRochers; $i++) {            do {                $x = $this->myRandom(0, 32767) * 2;                $y = $this->myRandom(0, 32767) * 2;            } while ($this->EstDansMontagne($x, $y));            $ign = $this->myRandom(15, 60);            $index = $this->AjouteObjet3d(1);            if ($this->PremRocher === 0) {                $this->PremRocher = $index;            }            $this->poseObjet($index, $this->newMont($x, $y, $ign, $f['rocher']));        }        $this->gridDirty = true;    }    /** The grid of the small static objects (rebuilt when objects were moved / added) */    private function construitGrille(): void    {        $this->grid = [];        $this->horsGrille = [];        $mobiles = $this->nbNuages + $this->nbEclairs + $this->nbOiseaux;        for ($i = 1; $i < $this->nbreobj; $i++) {            $m = $this->aigoual[$i];            $fo = $this->formes[$m['forme']];            // farthest distance at which ChaineObjet keeps it            $max = min(intdiv(256 * $fo['nbrdisk'] * 65535, 4), intdiv(256 * $fo['rayon'], 3));            if ($i < $mobiles || $max > self::GRID) {                $this->horsGrille[] = $i;            } else {                $this->grid[intdiv($m['posx'], self::GRID) * 16 + intdiv($m['posy'], self::GRID)][] = $i;            }        }        $this->gridDirty = false;    }    /**     * The objects that can be within $r of (x, y): all those outside the grid and those of the     * cells around, in increasing index order.     * @return int[]     */    private function objetsProches(int $x, int $y, int $r): array    {        if ($this->gridDirty) {            $this->construitGrille();        }        $list = $this->horsGrille;        $cx0 = intdiv(max(0, $x - $r), self::GRID);        $cx1 = intdiv(min(65535, $x + $r), self::GRID);        $cy0 = intdiv(max(0, $y - $r), self::GRID);        $cy1 = intdiv(min(65535, $y + $r), self::GRID);        for ($cx = $cx0; $cx <= $cx1; $cx++) {            for ($cy = $cy0; $cy <= $cy1; $cy++) {                foreach ($this->grid[$cx * 16 + $cy] ?? [] as $i) {                    $list[] = $i;                }            }        }        sort($list);        return $list;    }    /** WaitTick: waits $nb 1/60 s */    public function WaitTick(int $nb): \Generator    {        $t = $this->TickCount();        while ($this->TickCount() < $t + $nb) {            yield;        }    }    /** One frame of the final animation: the landscape, then the time passes */    private function finalImage(int $wait, bool $chrono): \Generator    {        $this->affich($this->dzeta);        $this->tfrima();        yield from $this->delai(0);        if ($chrono) {            $this->chrono(1);        }        yield from $this->WaitTick($wait);    }    /** Final: the end of the game (the time passes over the land of the tribe) */    public function Final(): \Generator    {        $this->DefautDecor3d();        $this->heure = 0;        while ($this->heure < 40) {            $this->chrono(1);        }        $this->jour_dsk = 0;        $this->xj = 0x5800;        $this->yj = 0x5400;        $this->dzeta = 768;        $this->zj = 40;        $this->clrbadi();        $this->comment(133);        $a = &$this->aigoual;        for ($n = 0; $n < 5; $n++) {            // the trees grow in number            for ($t = 0; $t < 15; $t++) {                for ($i = 0; $i < $this->nbNuages; $i++) {                    $a[$i]['posx'] = ($this->xj + $this->myRandom(-5000, 5000)) & 0xFFFF;                    $a[$i]['posy'] = ($this->yj + $this->myRandom(-5000, 5000)) & 0xFFFF;                }                $this->GereInterface();                for ($i = $this->PremArbre; $i < $this->PremArbre + $this->nbArbres; $i++) {                    $dist = $this->distance(abs($a[$i]['posx'] - $this->xj), abs($a[$i]['posy'] - $this->yj));                    if ($dist > 2000 && $this->myRandom(0, 1000) > 900) {                        $a[$i]['posx'] = ($this->xj + $this->myRandom(-750, 750)) & 0xFFFF;                        $a[$i]['posy'] = ($this->yj + $this->myRandom(-5000, -500)) & 0xFFFF;                        $dist = $this->distance(abs($a[$i]['posx'] - $this->xj), abs($a[$i]['posy'] - $this->yj));                        if ($dist < 50) {                            $a[$i]['posx'] = 65000;                            $a[$i]['posy'] = 65000;                        }                    }                }                $this->gridDirty = true;                $this->AvanceSol(1);                $this->heure = $this->myRandom(0, 90);                yield from $this->finalImage(5, false);            }            // erosion of the mountains            for ($i = 0; $i < $this->nbreobj; $i++) {                if ($this->formes[$a[$i]['forme']]['hauteur'] === 0 && $a[$i]['ign'] > 10) {                    $a[$i]['ign'] -= 10;                }            }            for ($i = $this->PremRocher; $i < $this->PremRocher + $this->nbRochers; $i++) {                $dist = $this->distance(abs($a[$i]['posx'] - $this->xj), abs($a[$i]['posy'] - $this->yj));                if ($dist > 2000 && $this->myRandom(0, 1000) > 900) {                    $a[$i]['posx'] = ($this->xj + $this->myRandom(-1000, 1000)) & 0xFFFF;                    $a[$i]['posy'] = ($this->yj + $this->myRandom(-1000, -100)) & 0xFFFF;                    $dist = $this->distance(abs($a[$i]['posx'] - $this->xj), abs($a[$i]['posy'] - $this->yj));                    if ($dist < 50) {                        $a[$i]['posx'] = 65000;                        $a[$i]['posy'] = 65000;                    }                }            }            $this->gridDirty = true;            // the trees decrease in number            for ($t = 0; $t < 15; $t++) {                for ($i = 0; $i < $this->nbNuages; $i++) {                    $a[$i]['posx'] = ($this->xj + $this->myRandom(-5000, 5000)) & 0xFFFF;                    $a[$i]['posy'] = ($this->yj + $this->myRandom(-5000, 5000)) & 0xFFFF;                }                $this->GereInterface();                for ($i = $this->PremArbre; $i < $this->PremArbre + $this->nbArbres; $i++) {                    $dist = $this->distance(abs($a[$i]['posx'] - $this->xj), abs($a[$i]['posy'] - $this->yj));                    if ($dist < 3000 && $this->myRandom(0, 1000) > 900) {                        $a[$i]['posx'] = 65000;                        $a[$i]['posy'] = 65000;                    }                }                $this->gridDirty = true;                $this->heure = $this->myRandom(0, 90);                $this->AvanceSol(1);                yield from $this->finalImage(5, false);            }        }        for ($i = 0; $i < $this->nbNuages; $i++) {            $a[$i]['posx'] = ($this->xj + $this->myRandom(-2000, 2000)) & 0xFFFF;            $a[$i]['posy'] = ($this->yj + $this->myRandom(-2000, 2000)) & 0xFFFF;        }        for ($n = 0; $n < 100; $n++) {            $this->FreqEclair = 900;            for ($i = $this->PremRocher; $i < $this->PremRocher + $this->nbRochers; $i++) {                if ($this->myRandom(0, 1000) > 900) {                    $a[$i]['posx'] = 65000;                    $a[$i]['posy'] = 65000;                }            }            for ($i = $this->PremArbre; $i < $this->PremArbre + $this->nbArbres; $i++) {                if ($this->myRandom(0, 1000) > 900) {                    $a[$i]['posx'] = 65000;                    $a[$i]['posy'] = 65000;                }            }            // buildings appear            $ign = $this->myRandom(15, 60);            $x = $this->xj + $this->myRandom(-750, 750);            $y = $this->yj + $this->myRandom(-10000, -500);            $this->poseObjet($this->AjouteObjet3d(1), $this->newMont($x, $y, $ign, $this->formeIdx['building']));            $this->heure = $this->myRandom(0, 90);            yield from $this->finalImage(5, true);        }        unset($a);        $this->heure = 512;        for ($n = 0; $n < 400; $n++) {            $this->yj = ($this->yj - 30) & 0xFFFF;            yield from $this->finalImage(2, true);        }        // THE END, in the font of the game four times bigger, from black to white        $chaine = 'THE END';        $w = 0;        for ($k = 0; $k < strlen($chaine); $k++) {            $w += $this->TailleCar(ord($chaine[$k]), 14) * 4;        }        $dx = intdiv(512 - $w, 2);        for ($n = 0; $n < 65535; $n += 256) {            $this->chrono(1);            $this->chrono(1);            $color = chr(Mac::color2Index($n, $n, $n));            $this->affich($this->dzeta);            yield from $this->delai(0);            $gbak = $this->curPort;            $this->curPort = $this->Ecran1;            $x = $dx;            for ($k = 0; $k < strlen($chaine); $k++) {                $c = ord($chaine[$k]);                $num = $this->Fonte14 + $c - 31;                $mob = $this->TabMob[$num] ?? null;                if ($mob !== null && $c !== 32) {                    $this->Affmob3d($num, [$x, 64 - 4 * $mob->nbligne, $x + 4 * $mob->nbcol, 64], str_repeat($color, 256));                }                $x += $this->TailleCar($c, 14) * 4;            }            $this->curPort = $gbak;            $this->tfrima();            yield from $this->WaitTick(1);        }        $this->CarboniseSetPortWindow();    }    /** FoudroieJoueur: the player is struck if the lightning falls near him */    private function FoudroieJoueur(int $x, int $y): void    {        if ($this->distance($this->xj - $x, $this->yj - $y) < $this->DistMinFoudroie) {            $this->clrbadi();            $this->comment(134);            // the original flashes 100 times without waiting: the last image stays            $this->effima(0x077);            $this->tfrima();            $this->effima(0x000);            $this->tfrima();            $this->mort = 0;        }    }    /** FoudroieArbre: the lightning falls at (x, y): on the first tree near, and maybe on the player */    private function FoudroieArbre(int $x, int $y): int    {        $this->FoudroieJoueur($x, $y);        // object 0 is not in the lists of objetsProches        $liste = $this->objetsProches($x, $y, max(0, $this->DistMinFoudroie));        if ($this->nbreobj > 0) {            array_unshift($liste, 0);        }        foreach ($liste as $i) {            $mont = $this->aigoual[$i];            if ($this->formes[$mont['forme']]['hauteur'] === 1 && $this->distance($mont['posx'] - $x, $mont['posy'] - $y) < $this->DistMinFoudroie) {                $this->aigoual[$i]['forme'] = $this->formeIdx['toucheEclair'];                $this->FoudroieJoueur($mont['posx'], $mont['posy']);                return $i;            }        }        return -1;    }    /**     * ChargeEltsDecorsMobiles: the variable parts of the world from the end of a saved game     * ("MAGIC" then "Foudroye x y" lines): the trees struck by lightning.     */    public function ChargeEltsDecorsMobiles(string $text): void    {        $words = preg_split('/\s+/', trim($text)) ?: [];        if (($words[0] ?? '') !== 'MAGIC') {            return;        }        $n = count($words);        for ($k = 1; $k < $n; $k++) {            if ($words[$k] === 'Foudroye' && $k + 2 < $n) {                $x = (int)$words[$k + 1];                $y = (int)$words[$k + 2];                $k += 2;                foreach ($this->aigoual as $i => $mont) {                    if ($this->formes[$mont['forme']]['hauteur'] === 1 && $mont['posx'] === $x && $mont['posy'] === $y) {                        $this->aigoual[$i]['forme'] = $this->formeIdx['foudroye'];                    }                }            }        }        $this->gridDirty = true;    }    /** SauveEltsDecorsMobiles: the text written at the end of a saved game */    public function SauveEltsDecorsMobiles(): string    {        $s = "\nMAGIC\n";        $foudroye = $this->formeIdx['foudroye'] ?? -1;        foreach ($this->aigoual as $mont) {            if ($mont['forme'] === $foudroye) {                $s .= 'Foudroye ' . $mont['posx'] . ' ' . $mont['posy'] . "\n";            }        }        return $s;    }    /** AFFICH: the landscape seen with the angle $tfr, drawn on Ecran1 */    public function affich(int $tfr): void    {        $this->initFormes();        $dzetabak = $this->dzeta;        $this->dzeta = $tfr;        $this->eff3d($this->ciel, $this->terre, $this->metpos, ($tfr + 128 - $this->angsol) & 0xFFFF, ($this->heure & 0x200) === 0x200 ? 1 : 0);        // smoke, flames, birds        $f = $this->formes;        $fi = $this->formeIdx;        $f = &$this->formes;        $f[$fi['fumee']]['numMob3d'] = $this->myRandom(18, 20);        if (++$f[$fi['forme8']]['numMob3d'] === 32) {            $f[$fi['forme8']]['numMob3d'] = 30;        }        if (++$f[$fi['oiseau']]['numMob3d'] === self::MOB_OISEAU + 2) {            $f[$fi['oiseau']]['numMob3d'] = self::MOB_OISEAU;        }        if (++$f[$fi['homme']]['numMob3d'] === self::MOB_HOMME + 2) {            $f[$fi['homme']]['numMob3d'] = self::MOB_HOMME;        }        unset($f);        $a = &$this->aigoual;        // the clouds go west        for ($i = 0; $i < $this->nbNuages; $i++) {            $a[$i]['posx'] = ($a[$i]['posx'] + $this->VitesseDeplacementNuages) & 0xFFFF;        }        if ($this->FreqEclair > 1000) {            // no storm: a new one?            if ($this->myRandom(0, 10000) > $this->FreqOrage) {                $this->FreqEclair = 0;            }        } else {            $this->FreqEclair += 4;        }        if ($this->ArbreFoudroye >= 0) {            $this->aigoual[$this->ArbreFoudroye]['forme'] = $fi['foudroye'];            $this->ArbreFoudroye = -1;        }        for ($i = 0; $i < $this->nbEclairs; $i++) {            $s = &$a[$this->PremEclair + $i];            if ($s['ign']) {                // end of the lightning                if ($this->ArbreFoudroye < 0) {                    $this->ArbreFoudroye = $this->FoudroieArbre($s['posx'], $s['posy']);                }                $s['ign'] = 0;            } elseif ($this->myRandom(0, 1000) > $this->FreqEclair && $this->myRandom(0, 1000) > 910) {                $s['ign'] = 300;            }            // the lightning stays under its cloud            $s['posx'] = ($s['posx'] + $this->VitesseDeplacementNuages) & 0xFFFF;            unset($s);        }        // the birds go south        for ($i = 0; $i < $this->nbOiseaux; $i++) {            $a[$this->nbNuages + $i]['posy'] = ($a[$this->nbNuages + $i]['posy'] + self::VITESSE_DEPLACEMENT_OISEAUX) & 0xFFFF;        }        unset($a);        // sort the visible objects (far to near) and draw them        $this->visu($this->ChaineObjets());        $this->Pluie();        $this->dzeta = $dzetabak;    }    /** CLAV3D: the landscape view, until a presence, the death or the exit (generator) */    public function clav3d(): \Generator    {        $cptemps = 1;        $cpt_cart = 0;        $cont = true;        $this->dismin = 0;        // only the column of the mouse is reset        do {            $this->vq_mouse($this->bouton, $this->dsposx, $this->sposy);            if ($this->bouton) {                yield;            }        } while ($this->bouton);        do {            $posTimer = $this->sapiensTimer;            $this->affich($this->dzeta);            $this->tfrima();            if ($this->okcarte) {                if (!($cpt_cart--)) {                    $cpt_cart = 5;                    yield from $this->carte();                }            }            $this->sapdep($this->dzeta, $this->sapvit);            $cptemps = ($cptemps - 1) & 0xFF;            if (!$cptemps) {                $cptemps = 5;                $this->chrono(0);            } else {                $this->chrono(1);            }            yield from $this->zoninf();            $this->PosePluie();            $car = $this->getkey3d();            if ($car) {                if (($car & 1) && $this->sapvit < 20) {                    $this->icone = $this->mobBambou + 23;                    $this->sapvit += 2;                }                if (($car & 2) && $this->sapvit) {                    $this->icone = $this->mobBambou + 24;                    if ($this->sapvit < 4) {                        $this->sapvit = 0;                    } else {                        $this->sapvit -= 4;                    }                }                if ($car & 8) {                    $this->icone = $this->mobBambou + 26;                    $this->dzeta = ($this->dzeta - 8) & 0x3FF;                }                if ($car & 4) {                    $this->icone = $this->mobBambou + 25;                    $this->dzeta = ($this->dzeta + 8) & 0x3FF;                }                if ($car & 32) {                    // anti repeat                    while ($this->getkey3d() & 32) {                        yield;                    }                    $this->okcarte ^= 1;                    if ($this->okcarte) {                        $cpt_cart = 0;                    } else {                        $this->affomb($this->DEBFLECH() + 17, 180, 280, 5, 5, true);                    }                }                if ($car & 64) {                    $cont = false;                }            } else {                $this->icone = $this->mobBambou + 20;            }            // frame synchronisation (at least 9 ticks of 11 ms)            do {                yield;            } while ($posTimer > $this->sapiensTimer - 9);        } while (!$this->presence && $this->mort && $cont);        $this->tfrvirt();        if ($this->presence <= 4 && $this->presence) {            // the menu 'say' by default            $this->InverseOption(0, 4);            $this->s_action = 5;            $this->inimen(2);        }    }}