<?phpnamespace App\Titus;/** * OpenTitus src/player.c: handles player movement and keyboard handling. */trait PlayerTrait{ public function move_player(Level $level): \Generator { //Part 1: Check keyboard input //Part 2: Determine the player's action, and execute action dependent code //Part 3: Move the player + collision detection //Part 4: Move the throwed/carried object //Part 5: decrease the timers $player = $level->player; $pause = false; //Part 1: Check keyboard input foreach ($this->pollEvents() as [$type, $key]) { //Check all events if ($type === 'down') { if (($key === self::KEY_GODMODE) && ($this->devmode === 1)) { if ($this->GODMODE) { $this->GODMODE = false; $this->NOCLIP = false; } else { $this->GODMODE = true; } } elseif (($key === self::KEY_NOCLIP) && ($this->devmode === 1)) { if ($this->NOCLIP) { $this->NOCLIP = false; } else { $this->NOCLIP = true; $this->GODMODE = true; } //Will display debug information } elseif ($key === self::KEY_DEBUG) { $this->DISPLAYLOOPTIME = !$this->DISPLAYLOOPTIME; } elseif ($key === self::KEY_MUSIC) { $this->musicKey($key); } elseif ($key === self::KEY_P) { $pause = true; } } } if ($this->keystate(self::KEY_ESC)) { return self::TITUS_ERROR_QUIT; } if ($this->keystate(self::KEY_F1) && ($this->RESETLEVEL_FLAG === 0)) { //F1 = suicide $this->CASE_DEAD_IM($level); $this->RESETLEVEL_FLAG--; return 0; } if ($this->keystate(self::KEY_F2)) { //F2 = game over $this->GAMEOVER_FLAG = true; return 0; } if ($this->keystate(self::KEY_E)) { //E = display energy $this->BAR_FLAG = 50; } if ($this->keystate(self::KEY_SKIP) && ($this->devmode === 1)) { //S = skip to next level $this->NEWLEVEL_FLAG = true; } if ($this->keystate(self::KEY_F4)) { //F4 = view status page yield from $this->viewstatus($level, false); } // TODO: ADD! SCREEN_3(); //Test for hidden credits screen if ($pause) { $retval = yield from $this->t_pause($level); //Apply pause if ($retval < 0) { return $retval; } } //JOYSTICK(); //Handle both joystick and keyboard movement keys //Part 2: Determine the player's action, and execute action dependent code $this->X_FLAG = $this->keystate(self::KEY_LEFT) || $this->keystate(self::KEY_RIGHT); //Set if either is true $this->Y_FLAG = $this->keystate(self::KEY_UP) || $this->keystate(self::KEY_JUMP) || $this->keystate(self::KEY_DOWN); //Set if either is true if ($this->NOCLIP) { if ($this->keystate(self::KEY_LEFT)) { $player->sprite->speedX = -100; } elseif ($this->keystate(self::KEY_RIGHT)) { $player->sprite->speedX = 100; } else { $player->sprite->speedX = 0; } if ($this->keystate(self::KEY_UP) || $this->keystate(self::KEY_JUMP)) { $player->sprite->speedY = -100; } elseif ($this->keystate(self::KEY_DOWN)) { $player->sprite->speedY = 100; } else { $player->sprite->speedY = 0; } $player->sprite->x += ($player->sprite->speedX >> 4); $player->sprite->y += ($player->sprite->speedY >> 4); return 0; } if ($this->CHOC_FLAG !== 0) { $this->action = 11; //Headache } elseif ($this->KICK_FLAG !== 0) { if ($this->GRANDBRULE_FLAG) { $this->action = 13; //Hit (burn) } else { $this->action = 12; //Hit } } else { $this->GRANDBRULE_FLAG = false; if ($this->LADDER_FLAG) { $this->action = 6; //Action: climb } elseif (!$this->PRIER_FLAG && ($this->keystate(self::KEY_UP) || $this->keystate(self::KEY_JUMP)) && ($this->SAUT_FLAG === 0)) { $this->action = 2; //Action: jump if ($this->LAST_ORDER === 5) { //Test if last order was kneestanding $this->FURTIF_FLAG = 100; //If jump after kneestanding, init silent walk timer } } elseif ($this->PRIER_FLAG || (($this->SAUT_FLAG !== 6) && $this->keystate(self::KEY_DOWN))) { if ($this->X_FLAG) { //Move left or right $this->action = 3; //Action: crawling } else { $this->action = 5; //Action: kneestand } } elseif ($this->X_FLAG) { $this->action = 1; //Action: walk } else { $this->action = 0; //Action: rest (no action) } //Is space button pressed? if ($this->keystate(self::KEY_SPACE) && !$this->PRIER_FLAG) { if (!$this->DROP_FLAG) { if (($this->action === 3) || ($this->action === 5)) { //Kneestand $this->DROPREADY_FLAG = false; $this->action = 7; //Grab an object } elseif ($this->CARRY_FLAG && $this->DROPREADY_FLAG) { //Fall $this->action = 8; //Drop the object } } } else { $this->DROPREADY_FLAG = true; $this->POSEREADY_FLAG = false; } } if ($this->CARRY_FLAG) { $this->action += 16; } if (($this->CHOC_FLAG !== 0) || ($this->KICK_FLAG !== 0)) { if ($this->SENSX < 0) { // -1 $newsensX = -1; } else { // 0 or 1 $newsensX = 0; } } elseif ($this->keystate(self::KEY_LEFT)) { $newsensX = -1; } elseif ($this->keystate(self::KEY_RIGHT)) { $newsensX = 1; } elseif ($this->SENSX === -1) { $newsensX = -1; } elseif ($this->action === 0) { $newsensX = 0; } else { $newsensX = 1; } if ($this->SENSX !== $newsensX) { $this->SENSX = $newsensX; $this->ACTION_TIMER = 1; } else { if ((($this->action === 0) || ($this->action === 1)) && ($this->FURTIF_FLAG !== 0)) { //Silent walk? $this->action += 9; } if ($this->action !== $this->LAST_ORDER) { $this->ACTION_TIMER = 1; } elseif ($this->ACTION_TIMER < 0xFF) { $this->ACTION_TIMER += 1; } } $this->ACTION_PRG($level, $this->action); //call movement function based on ACTION //Part 3: Move the player + collision detection //Move the player in X if the new position doesn't exceed 8 pixels from the edges if ((($player->sprite->speedX < 0) && (($player->sprite->x + ($player->sprite->speedX >> 4)) >= 8)) || //Going left (($player->sprite->speedX > 0) && (($player->sprite->x + ($player->sprite->speedX >> 4)) <= ($level->width << 4) - 8))) { //Going right $player->sprite->x += $player->sprite->speedX >> 4; } //Move player in Y $player->sprite->y += ($player->sprite->speedY >> 4); //Test for collisions yield from $this->BRK_COLLISION($level); //Part 4: Move the throwed/carried object //Move throwed/carried object if ($this->DROP_FLAG) { //sprite2: throwed or dropped object $newX = ($player->sprite2->speedX >> 4) + $player->sprite2->x; if (($newX < ($level->width << 4)) && //Left for right level edge ($newX >= 0) && //Right for level left edge ($newX >= ($this->BITMAP_X << 4) - self::GESTION_X) && //Max 40 pixels left for screen (bug: the purpose was probably one screen left for the screen) ($newX <= ($this->BITMAP_X << 4) + (self::screen_width << 4) + self::GESTION_X)) { //Max 40 pixels right for screen $player->sprite2->x = $newX; $newY = ($player->sprite2->speedY >> 4) + $player->sprite2->y; if (($newY < ($level->height << 4)) && //Above bottom edge of level ($newY >= 0) && //Below top edge of level ($newY >= ($this->BITMAP_Y << 4) - self::GESTION_Y) && //Max 20 pixels above the screen (bug: the purpose was probably one screen above the screen) ($newY <= ($this->BITMAP_Y << 4) + (self::screen_height << 4) + self::GESTION_Y)) { //Max 20 pixels below the screen $player->sprite2->y = $newY; } else { $player->sprite2->enabled = false; $this->DROP_FLAG = false; } } else { $player->sprite2->enabled = false; $this->DROP_FLAG = false; } } elseif ($this->CARRY_FLAG) { //Place the object on top of or beside the player if (!$this->LADDER_FLAG && (($this->LAST_ORDER === 16 + 5) || ($this->LAST_ORDER === 16 + 7))) { //Kneestand or take $player->sprite2->y = $player->sprite->y - 4; if ($player->sprite->flipped) { $player->sprite2->x = $player->sprite->x - 10; } else { $player->sprite2->x = $player->sprite->x + 12; } } else { if (($player->sprite->number === 14) || //Sliding down the ladder OR ((($this->LAST_ORDER & 0x0F) !== 7) && //Not taking (($this->LAST_ORDER & 0x0F) !== 8))) { //Not throwing/dropping $player->sprite2->x = $player->sprite->x + 2; if (($player->sprite->number === 23) || //Climbing (c) ($player->sprite->number === 24)) { //Climbing (c) (2nd sprite) $player->sprite2->x -= 10; if ($player->sprite->flipped) { $player->sprite2->x += 18; } } $player->sprite2->y = $player->sprite->y - $player->sprite->spritedata->collheight + 1; } } } if ($this->SEECHOC_FLAG !== 0) { $this->SEECHOC_FLAG--; if ($this->SEECHOC_FLAG === 0) { $player->sprite2->enabled = false; } } //Part 5: decrease the timers $this->subto0($this->INVULNERABLE_FLAG); $this->subto0($this->RESETLEVEL_FLAG); $this->subto0($this->TAPISFLY_FLAG); $this->subto0($this->CROSS_FLAG); $this->subto0($this->GRAVITY_FLAG); $this->subto0($this->FURTIF_FLAG); $this->subto0($this->KICK_FLAG); if ($player->sprite->speedY === 0) { $this->subto0($this->CHOC_FLAG); } if (($player->sprite->speedX === 0) && ($player->sprite->speedY === 0)) { $this->KICK_FLAG = 0; } $this->subto0($this->FUME_FLAG); //Smoke when object hits the floor if (($this->FUME_FLAG !== 0) && (($this->FUME_FLAG & 0x03) === 0)) { //Smoke animation $this->updatesprite($level, $player->sprite2, $player->sprite2->number + 1, false); if ($player->sprite2->number === self::FIRST_OBJET + 19) { //Remove smoke $player->sprite2->enabled = false; $this->FUME_FLAG = 0; } } return 0; } public function CASE_DEAD_IM(Level $level): void { //Kill the player immediately (spikes/water/flames etc. //Sets RESET_FLAG to 2, in opposite to being killed as a result of 0 HP (then RESET_FLAG is 10) $this->DEC_LIFE($level); $this->RESETLEVEL_FLAG = 2; } public function DEC_LIFE(Level $level): void { //Kill the player, check for gameover, hide the energy bar $this->RESETLEVEL_FLAG = 10; $this->BAR_FLAG = 0; if ($level->lives === 0) { $this->GAMEOVER_FLAG = true; } } public function t_pause(Level $level): \Generator { $player = $level->player; $tmp = new Sprite(); $this->TFR_SCREENM(); //Draw tiles $this->copysprite($level, $tmp, $player->sprite); $this->updatesprite($level, $player->sprite, 29, true); //Pause tile $this->DISPLAY_SPRITES($level); //Draw sprites yield from $this->flip_screen(true); //Display it $this->copysprite($level, $player->sprite, $tmp); //Reset player sprite do { yield 0; // titus_sleep() foreach ($this->pollEvents() as [$type, $key]) { //Check all events if ($type === 'down') { if ($key === self::KEY_ESC) { return self::TITUS_ERROR_QUIT; } elseif ($key === self::KEY_MUSIC) { $this->musicKey($key); } elseif ($key === self::KEY_P) { return 0; } } } } while (true); } public function BRK_COLLISION(Level $level): \Generator { //Collision detection between player and tiles/objects/elevators //Point the foot on the block! $player = $level->player; $tileX = self::u8($player->sprite->x >> 4); $tileY = ($player->sprite->y >> 4) - 1; $initY = $tileY; //if too low then die! if (($player->sprite->y > (($level->height + 1) << 4)) && !$this->NOCLIP) { $this->CASE_DEAD_IM($level); } //Test under the feet of the hero and on his head! (In y) $this->YFALL = 0; //Find the left tile //colltest can be 0 to 15 +- 8 (-1 to -8 will change into 255 to 248) $colltest = $player->sprite->x & 0x0F; if ($colltest < self::TEST_ZONE) { $colltest += 256; $tileX = self::u8($tileX - 1); } $colltest -= self::TEST_ZONE; $left_tileX = $tileX; yield from $this->TAKE_BLK_AND_YTEST($level, $tileY, $tileX); //Test the tile for vertical blocking if ($this->YFALL === 1) { //Have the fall stopped? //No! Is it necessary to test the right tile? $colltest += self::TEST_ZONE * 2; //4 * 2// if (colltest > 255) {// colltest -= 256;// tileX++;// } elseif if ($colltest > 15) { $tileX = self::u8($tileX + 1); } if ($tileX !== $left_tileX) { yield from $this->TAKE_BLK_AND_YTEST($level, $tileY, $tileX); //Also test the left tile } if ($this->YFALL === 1) { if (($this->CROSS_FLAG === 0) && ($this->CHOC_FLAG === 0)) { $this->COLLISION_TRP($level); //Player versus elevators if ($this->YFALL === 1) { $this->COLLISION_OBJET($level); //Player versus objects if ($this->YFALL === 1) { $this->ARAB_TOMBE($level); //No wall/elevator/object under the player; fall down! } else { $player->GLISSE = 0; } } } else { $this->ARAB_TOMBE($level); //Fall down! } } } //How will the player move in X? $changeX = self::TEST_ZONE + self::MAX_X; //4 + 4 ??? + max_speed_x if ($player->sprite->speedX < 0) { $changeX = 0 - $changeX; } elseif ($player->sprite->speedX === 0) { $changeX = 0; } //Test the hero (in x) $height = $player->sprite->spritedata->collheight; if (($player->sprite->y > self::MAP_LIMIT_Y + 1) && ($initY >= 0) && ($initY < $level->height)) { $tileX = self::u8(($player->sprite->x + $changeX) >> 4); $tileY = $initY; $first = true; do { $hflag = $this->get_horizflag($level, $tileY, $tileX); if ($first) { yield from $this->BLOCK_XXPRG($level, $hflag, self::u8($tileY), $tileX); $first = false; } elseif (($hflag === self::HFLAG_CODE) || ($hflag === self::HFLAG_BONUS)) { //level code or HP yield from $this->BLOCK_XXPRG($level, $hflag, self::u8($tileY), $tileX); } if ($tileY === 0) { return; } $tileY--; $height -= 16; } while ($height > 0); } } private function TAKE_BLK_AND_YTEST(Level $level, int $tileY, int $tileX): \Generator { //Test the current tile for vertical blocking $player = $level->player; $this->POCKET_FLAG = false; $this->PRIER_FLAG = false; $this->LADDER_FLAG = false; if (($player->sprite->y <= self::MAP_LIMIT_Y) || ($tileY < -1)) { //if player is too high (<= -1), skip test $this->ARAB_TOMBE($level); $this->YFALL = 0xFF; return; } if ($tileY + 1 >= $level->height) { //if player is too low, skip test $this->ARAB_TOMBE($level); $this->YFALL = 0xFF; return; } if ($tileY === -1) { //In order to fall down in the right chamber if jumping above level 8 $tileY = 0; } $floor = $this->get_floorflag($level, $tileY + 1, $tileX); $floor_above = $this->get_floorflag($level, $tileY, $tileX); if (($this->LAST_ORDER & 0x0F) !== 2) { //2=SAUTER yield from $this->BLOCK_YYPRG($level, $floor, $floor_above, self::u8($tileY + 1), $tileX); //Player versus floor } //Test the tile on his head if (($tileY < 1) || ($player->sprite->speedY > 0)) { return; } $horiz = $this->get_horizflag($level, $tileY, $tileX); $ceil = $this->get_ceilflag($level, $tileY - 1, $tileX); $this->BLOCK_YYPRGD($level, $ceil, self::u8($tileY - 1), $tileX); //Player versus ceiling if ((($horiz === self::HFLAG_WALL) || ($horiz === self::HFLAG_DEADLY) || ($horiz === self::HFLAG_PADLOCK)) && //Step on a hard tile? ($player->sprite->y > self::MAP_LIMIT_Y + 1)) { if ($player->sprite->speedX > 0) { $change = -1; } else { $change = 1; } $tileX = self::u8($tileX + $change); $horiz = $this->get_horizflag($level, $tileY, $tileX); if ($horiz === 0) { //No wall $player->sprite->x += $change << 1; } else { $change = 0 - $change; $tileX = self::u8($tileX + $change + $change); $horiz = $this->get_horizflag($level, $tileY, $tileX); if ($horiz === 0) { $player->sprite->x += $change << 1; } } } } private function BLOCK_YYPRGD(Level $level, int $ceil, int $tileY, int $tileX): void { $player = $level->player; //Action on different ceiling flags switch ($ceil) { case 0: //No ceiling break; case 1: //Ceiling case 4: //Deadly if (($ceil === 4) && !$this->GODMODE) { $this->CASE_DEAD_IM($level); } elseif ($player->sprite->speedY !== 0) { //Stop movement $player->sprite->speedY = 0; $player->sprite->y = self::s16(($player->sprite->y & 0xFFF0) + 16); $this->SAUT_COUNT = 0xFF; } elseif (($player->sprite->number !== 10) && //10 = Free fall ($player->sprite->number !== 21) && //21 = Free fall (c) ($this->SAUT_FLAG !== 6)) { // $this->PRIER_FLAG = true; if ($this->CARRY_FLAG) { $object = $this->FORCE_POSE($level); if ($object !== null) { $tileX = self::u8($object->sprite->x >> 4); $tileY = self::u8($object->sprite->y >> 4); $hflag = $this->get_horizflag($level, $tileY, $tileX); if (($hflag === self::HFLAG_WALL) || ($hflag === self::HFLAG_DEADLY) || ($hflag === self::HFLAG_PADLOCK)) { $tileX = self::u8($tileX - 1); $hflag = $this->get_horizflag($level, $tileY, $tileX); if (($hflag === self::HFLAG_WALL) || ($hflag === self::HFLAG_DEADLY) || ($hflag === self::HFLAG_PADLOCK)) { $object->sprite->x += 16; } else { $object->sprite->x -= 16; } } } } } break; case 2: //Ladder if (($player->sprite->speedY < 0) && ($player->sprite->speedX === 0)) { $this->SAUT_COUNT = 10; $this->LADDER_FLAG = true; } break; case 3: //Padlock $this->CASE_SECU($level, $tileY, $tileX); break; //case 4: on case 1 } } private function BLOCK_XXPRG(Level $level, int $horiz, int $tileY, int $tileX): \Generator { //Action on different horizontal flags switch ($horiz) { case 0: //No wall break; case 1: //Wall $this->ARAB_BLOCKX($level); break; case 2: //HP $this->CASE_BONUS($level, $tileY, $tileX); break; case 3: //Unknown, looks deadly if (!$this->GODMODE) { $this->CASE_DEAD_IM($level); } else { $this->ARAB_BLOCKX($level); //Godmode: wall } break; case 4: //Level code yield from $this->CASE_PASS($level, $level->levelnumber, $tileY, $tileX); break; case 5: //Padlock $this->CASE_SECU($level, $tileY, $tileX); break; case 6: //Level 14 code yield from $this->CASE_PASS($level, 14 - 1, $tileY, $tileX); break; } } public function ARAB_BLOCKX(Level $level): void { $player = $level->player; //Horizontal hit (wall), stop the player $player->sprite->x -= $player->sprite->speedX >> 4; $player->sprite->speedX = 0; if (($this->KICK_FLAG !== 0) && ($this->SAUT_FLAG !== 6)) { $this->CHOC_FLAG = 20; $this->KICK_FLAG = 0; } } public function FORCE_POSE(Level $level): ?TitusObject { //Move object from sprite2 to TMPFP $sprite2 = $level->player->sprite2; if (($sprite2->enabled) && ($this->CARRY_FLAG)) { //Drop the carried object $i = 0; do { // C: (i > level->objectcount); at i == objectcount the C code reads past the array if ($i >= $level->objectcount) { // printf("ERROR: FORCE_POSE returned NULL\n"); return null; } if (!($level->object[$i]->sprite->enabled)) { break; } $i++; } while (true); $object = $level->object[$i]; $this->updateobjectsprite($level, $object, $sprite2->number, true); $sprite2->enabled = false; $object->sprite->killing = false; if ($object->sprite->number < self::FIRST_NMI) { $object->sprite->droptobottom = false; } else { $object->sprite->droptobottom = true; } $object->sprite->x = $sprite2->x; $object->sprite->y = $sprite2->y; $object->mass = 0; $object->sprite->speedY = 0; $object->sprite->speedX = 0; $object->sprite->UNDER = 0; $object->sprite->ONTOP = null; $this->POSEREADY_FLAG = true; $this->GRAVITY_FLAG = 4; $this->CARRY_FLAG = false; return $object; } return null; } public function ARAB_TOMBE(Level $level): void { //No wall under the player; fall down! $player = $level->player; $this->SAUT_FLAG = 6; if ($this->KICK_FLAG !== 0) { return; } $this->XACCELERATION($player, self::MAX_X * 16); $this->YACCELERATION($player, self::MAX_Y * 16); if ($this->CHOC_FLAG !== 0) { $this->updatesprite($level, $player->sprite, 15, true); //sprite when hit } elseif (!$this->CARRY_FLAG) { $this->updatesprite($level, $player->sprite, 10, true); //position while falling (jump sprite?) } else { $this->updatesprite($level, $player->sprite, 21, true); //position falling and carry (jump and carry sprite?) } $player->sprite->flipped = ($this->SENSX < 0); } private function XACCELERATION(Player $player, int $maxspeed): void { //Sideway acceleration if ($this->X_FLAG) { $changeX = ($this->SENSX << 4) >> $player->GLISSE; } else { $changeX = 0; } if ($player->sprite->speedX + $changeX >= $maxspeed) { $player->sprite->speedX = $maxspeed; } elseif ($player->sprite->speedX + $changeX <= 0 - $maxspeed) { $player->sprite->speedX = 0 - $maxspeed; } else { $player->sprite->speedX += $changeX; } } private function YACCELERATION(Player $player, int $maxspeed): void { //Accelerate downwards if (($player->sprite->speedY + intdiv(32, 2)) < $maxspeed) { $player->sprite->speedY = $player->sprite->speedY + intdiv(32, 2); } else { $player->sprite->speedY = $maxspeed; } } private function BLOCK_YYPRG(Level $level, int $floor, int $floor_above, int $tileY, int $tileX): \Generator { //Action on different floor flags $player = $level->player; switch ($floor) { case 0: //No floor $this->ARAB_TOMBE_F(); break; case 1: //Floor $this->ARAB_BLOCK_YU($player); break; case 2: //Slightly slippery floor $this->ARAB_BLOCK_YU($player); $player->GLISSE = 1; break; case 3: //Slippery floor $this->ARAB_BLOCK_YU($player); $player->GLISSE = 2; break; case 4: //Very slippery floor $this->ARAB_BLOCK_YU($player); $player->GLISSE = 3; break; case 5: //Drop-through if kneestanding $player->GLISSE = 0; if ($this->CROSS_FLAG === 0) { $this->ARAB_BLOCK_YU($player); } else { $this->ARAB_TOMBE_F(); //Drop through! } break; case 6: //Ladder //Fall if hit //Skip if walking/crawling if ($this->CHOC_FLAG !== 0) { $this->ARAB_TOMBE_F(); //Free fall return; } $order = $this->LAST_ORDER & 0x0F; if (($order === 1) || ($order === 3) || ($order === 7) || ($order === 8)) { $this->ARAB_BLOCK_YU($player); //Stop fall return; } if ($order === 5) { // action baisse $this->ARAB_TOMBE_F(); //Free fall $this->updatesprite($level, $player->sprite, 14, true); //sprite: start climbing down $player->sprite->y += 8; } if ($floor_above !== 6) { //ladder if ($order === 0) { //action repos $this->ARAB_BLOCK_YU($player); //Stop fall return; } if (($this->keystate(self::KEY_UP) || $this->keystate(self::KEY_JUMP)) && ($order === 6)) { //action UP + climb ladder $this->ARAB_BLOCK_YU($player); //Stop fall return; } } $this->subto0($this->SAUT_FLAG); $this->SAUT_COUNT = 0; $this->YFALL = 2; $this->LADDER_FLAG = true; break; case 7: $this->CASE_BONUS($level, $tileY, $tileX); break; case 8: case 9: case 10: if (!$this->GODMODE) { $this->CASE_DEAD_IM($level); } else { $this->ARAB_BLOCK_YU($player); //If godmode; ordinary floor } break; case 11: yield from $this->CASE_PASS($level, $level->levelnumber, $tileY, $tileX); break; case 12: $this->CASE_SECU($level, $tileY, $tileX); break; case 13: yield from $this->CASE_PASS($level, 14 - 1, $tileY, $tileX); break; } } public function ARAB_TOMBE_F(): void { //Player free fall (doesn't touch floor) $this->YFALL = $this->YFALL | 0x01; } public function ARAB_BLOCK_YU(Player $player): void { //Floor; the player will not fall through $this->POCKET_FLAG = true; $player->GLISSE = 0; if ($player->sprite->speedY < 0) { $this->YFALL = $this->YFALL | 0x01; return; } $player->sprite->y = self::s16($player->sprite->y & 0xFFF0); $player->sprite->speedY = 0; $this->subto0($this->SAUT_FLAG); $this->SAUT_COUNT = 0; $this->YFALL = 2; } private function CASE_BONUS(Level $level, int $tileY, int $tileX): bool { //Handle bonuses. Increase energy if HP, and change the bonus tile to normal tile $i = 0; do { //Is the bonus in the list? if ($i >= self::MAXIMUM_BONUS) { return false; //Bonus not found } $i++; } while (($level->bonus[$i - 1]->x !== $tileX) || ($level->bonus[$i - 1]->y !== $tileY)); $i--; //If the bonus is 253-255, it's HP. Increase life! if ($level->bonus[$i]->bonustile >= 255 - 2) { //Increase HP if tile number is >= 253 $this->SELECT_MUSIC(6); $this->INC_ENERGY($level); } //Return the original tile underneath the bonus $level->tilemap[$tileY][$tileX] = $level->bonus[$i]->replacetile; $this->DISPLAY_CHAR($level, $level->bonus[$i]->replacetile, $tileY % self::screen_height, $tileX % self::screen_width); $this->GRAVITY_FLAG = 4; $this->PERMUT_FLAG = true; return true; //No problems, bonus handling done correctly! } private function CASE_PASS(Level $level, int $level_index, int $tileY, int $tileX): \Generator { //Codelamp $this->SELECT_MUSIC(7); if ($this->CASE_BONUS($level, $tileY, $tileX)) { //if the bonus is found in the bonus list yield from $this->view_password($level, $level_index); } } private function CASE_SECU(Level $level, int $tileY, int $tileX): void { $player = $level->player; //Padlock, store X/Y coordinates $this->SELECT_MUSIC(5); if ($this->CASE_BONUS($level, $tileY, $tileX)) { //if the bonus is found in the bonus list $player->initX = $player->sprite->x; $player->initY = $player->sprite->y; if (($player->sprite2->number === self::FIRST_OBJET + 26) || ($player->sprite2->number === self::FIRST_OBJET + 27)) { //If carrying the cage, save the position $player->cageX = $player->sprite->x; $player->cageY = $player->sprite->y; } } } public function INC_ENERGY(Level $level): void { $player = $level->player; $this->BAR_FLAG = 50; $player->hp++; if ($player->hp > self::MAXIMUM_ENERGY) { $player->hp = self::MAXIMUM_ENERGY; $level->extrabonus++; } } public function DEC_ENERGY(Level $level): void { $player = $level->player; $this->BAR_FLAG = 50; if ($this->RESETLEVEL_FLAG === 0) { $player->hp--; if ($player->hp < 0) { $player->hp = 0; $this->DEC_LIFE($level); } } } private function ACTION_PRG(Level $level, int $action): void { //Action dependent code $player = $level->player; switch ($action) { case 0: case 9: case 16: //Rest. Handle deacceleration and slide $this->LAST_ORDER = $action; $this->DECELERATION($player); if ((abs($player->sprite->speedX) >= 1 * 16) && ($player->sprite->flipped === ($player->sprite->speedX < 0))) { $player->sprite->setAnimation($this->anim_player[4 + $this->add_carry()]); } else { $player->sprite->setAnimation($this->anim_player[$action]); } $this->updatesprite($level, $player->sprite, $player->sprite->animValue(), true); $player->sprite->flipped = ($this->SENSX < 0); break; case 1: case 17: case 19: //Handle walking $this->XACCELERATION($player, self::MAX_X * 16); $this->NEW_FORM($player, $action); //Update last order and action (animation) $this->GET_IMAGE($level); //Update player sprite break; case 2: case 18: //Handle a jump if ($this->SAUT_COUNT >= 3) { $this->SAUT_FLAG = 6; //Stop jump animation and acceleration } else { $this->SAUT_COUNT++; $this->YACCELERATION_NEG($player, intdiv(self::MAX_Y * 16, 4)); $this->XACCELERATION($player, self::MAX_X * 16); $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); } break; case 3: //Handle crawling $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); $this->XACCELERATION($player, intdiv(self::MAX_X * 16, 2)); if (abs($player->sprite->speedX) < (2 * 16)) { $this->updatesprite($level, $player->sprite, 6, true); //Crawling but not moving $player->sprite->flipped = ($this->SENSX < 0); } break; case 4: case 14: case 15: case 20: case 25: case 26: //No action? break; case 5: //Kneestand $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); $this->DECELERATION($player); if ($this->ACTION_TIMER === 15) { $this->CROSS_FLAG = 6; $player->sprite->speedY = 0; } break; case 6: case 22: //Climb a ladder if ($this->X_FLAG) { $this->XACCELERATION($player, self::MAX_X * 16); } else { $this->DECELERATION($player); } if ($this->ACTION_TIMER <= 1) { if (!$this->CARRY_FLAG) { $this->updatesprite($level, $player->sprite, 12, true); //Last climb sprite } else { $this->updatesprite($level, $player->sprite, 23, true); //First climb sprite (c) } } if ($this->Y_FLAG) { $this->NEW_FORM($player, 6 + $this->add_carry()); //Action: Climb $this->GET_IMAGE($level); //Place the player on the ladder $player->sprite->x = self::s16(($player->sprite->x & 0xFFF0) + 8); $tileX = self::u8(($player->sprite->x) >> 4); $tileY = self::u8(($player->sprite->y & 0xFFF0) >> 4); if ($this->get_floorflag($level, $tileY, $tileX) !== self::FFLAG_LADDER) { if ($this->get_floorflag($level, $tileY, $tileX - 1) === self::FFLAG_LADDER) { $player->sprite->x -= 16; } elseif ($this->get_floorflag($level, $tileY, $tileX + 1) === self::FFLAG_LADDER) { $player->sprite->x += 16; } } if (!$this->keystate(self::KEY_UP) && !$this->keystate(self::KEY_JUMP)) { $player->sprite->speedY = 4 * 16; } else { $player->sprite->speedY = 0 - (4 * 16); } } else { $player->sprite->speedY = 0; } break; case 7: case 23: //Take a box $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); $this->DECELERATION($player); if (!$this->POSEREADY_FLAG) { if (($this->ACTION_TIMER === 1) && ($this->CARRY_FLAG)) { //If the object is placed in a block, fix a speedX $object = $this->FORCE_POSE($level); if ($object !== null) { $tileX = self::u8($object->sprite->x >> 4); $tileY = self::u8($object->sprite->y >> 4); $fflag = $this->get_floorflag($level, $tileY, $tileX); if (($fflag !== self::FFLAG_NOFLOOR) && ($fflag !== self::FFLAG_WATER)) { //this test is really floor flag & 0x07 $tileX = self::u8($tileX + 1); $fflag = $this->get_floorflag($level, $tileY, $tileX); if (($fflag !== self::FFLAG_NOFLOOR) && ($fflag !== self::FFLAG_WATER)) { $object->sprite->speedX = 16 * 3; } else { $object->sprite->speedX = 0 - (16 * 3); } } } } else { if (!$this->CARRY_FLAG) { $tmpY = $player->sprite->y; $tmpX = $player->sprite->x; for ($i = 0; $i < $level->objectcount; $i++) { //First do a quick test if (!($level->object[$i]->sprite->enabled) || (abs($player->sprite->y - $level->object[$i]->sprite->y) >= 20)) { continue; } $diffX = $player->sprite->x - $level->object[$i]->sprite->x; if (!$player->sprite->flipped) { $diffX = 0 - $diffX; } //Moktar if ($diffX >= 25) { continue; } //The ordinary test //X/* if (player->sprite.x > player->sprite.x) { //Bug: the first expression should have been level->object[i] if (player->sprite.x + 32 < player->sprite.x) { //This will never execute continue; } } else { if (player->sprite.x + level->object[i].sprite.spritedata->collwidth < player->sprite.x) { continue; //This should not execute } }*/ //What it probably should have been if ($level->object[$i]->sprite->x > $player->sprite->x) { //The object is right if ($level->object[$i]->sprite->x > $player->sprite->x + 32) { continue; //The object is too far right } } else { //The object is left if ($level->object[$i]->sprite->x + $level->object[$i]->sprite->spritedata->collwidth < $player->sprite->x) { continue; //The object is too far left } } //Y if ($level->object[$i]->sprite->y < $player->sprite->y) { //The object is above if ($level->object[$i]->sprite->y <= $player->sprite->y - 10) { continue; } } else { //The object is below if ($level->object[$i]->sprite->y - $level->object[$i]->sprite->spritedata->collheight + 1 >= $player->sprite->y) { continue; } } //Take the object $this->FX_START(9); //Sound effect $this->FUME_FLAG = 0; $level->object[$i]->sprite->speedY = 0; $level->object[$i]->sprite->speedX = 0; $this->GRAVITY_FLAG = 4; $this->copysprite($level, $player->sprite2, $level->object[$i]->sprite); $level->object[$i]->sprite->enabled = false; $this->CARRY_FLAG = true; $this->SEECHOC_FLAG = 0; if ($player->sprite2->number === self::FIRST_OBJET + 19) { //flying carpet $this->TAPISWAIT_FLAG = 0; } $player->sprite2->y = $player->sprite->y - 4; if ($player->sprite->flipped) { $player->sprite2->x = $player->sprite->x - 10; } else { $player->sprite2->x = $player->sprite->x + 12; } break; } if (!$this->CARRY_FLAG) { //No objects taken, check if he picks up an enemy! for ($i = 0; $i < $level->enemycount; $i++) { //First do a quick test if ((!$level->enemy[$i]->sprite->enabled) || (abs($player->sprite->y - $level->enemy[$i]->sprite->y) >= 20)) { continue; } $diffX = $player->sprite->x - $level->enemy[$i]->sprite->x; if (!$player->sprite->flipped) { $diffX = 0 - $diffX; } //Moktar if ($diffX >= 25) { continue; } //The ordinary test //X if ($level->enemy[$i]->carry_sprite === -1) { //Test if the enemy can be picked up from behind continue; } // if (player->x > player->x) { //Bug: the first expression should have been enemy[i] // if (player->x + 32 < player->x) { //This will never execute // continue; // } // } else { // if (player->x + enemy[i].sprite.spritedata->collwidth < player->x) { // continue; //This should not execute // } // } //What it probably should have been if ($level->enemy[$i]->sprite->x > $player->sprite->x) { //The enemy is right if ($level->enemy[$i]->sprite->x > $player->sprite->x + 32) { continue; //The enemy is too far right } } else { //The object is left if ($level->enemy[$i]->sprite->x + $level->enemy[$i]->sprite->spritedata->collwidth < $player->sprite->x) { continue; //The enemy is too far left } } //Y if ($level->enemy[$i]->sprite->y < $player->sprite->y) { //The enemy is above if ($level->enemy[$i]->sprite->y <= $player->sprite->y - 10) { continue; } } else { //The enemy is below if ($level->enemy[$i]->sprite->y - $level->enemy[$i]->sprite->spritedata->collheight - 1 >= $player->sprite->y) { continue; } } if ($level->enemy[$i]->sprite->number >= self::FIRST_NMI) { $diffX = $player->sprite->x - $level->enemy[$i]->sprite->x; if ($level->enemy[$i]->sprite->flipped) { $diffX = 0 - $diffX; } if ($diffX < 0) { continue; } } $this->FX_START(9); //Sound effect $this->FUME_FLAG = 0; $level->enemy[$i]->sprite->speedY = 0; $level->enemy[$i]->sprite->speedX = 0; $this->GRAVITY_FLAG = 4; $player->sprite2->flipped = $level->enemy[$i]->sprite->flipped; $player->sprite2->flash = $level->enemy[$i]->sprite->flash; $player->sprite2->visible = $level->enemy[$i]->sprite->visible; $this->updatesprite($level, $player->sprite2, $level->enemy[$i]->carry_sprite, false); $level->enemy[$i]->sprite->enabled = false; $this->CARRY_FLAG = true; $this->SEECHOC_FLAG = 0; $player->sprite2->y = $player->sprite->y - 4; if ($player->sprite->flipped) { $player->sprite2->x = $player->sprite->x - 10; } else { $player->sprite2->x = $player->sprite->x + 12; } break; } //for loop, enemy } //condition (!CARRY_FLAG), check for enemy pickup } //condition (!CARRY_FLAG), check for object/enemy pickup } //condition ((ACTION_TIMER == 1) && (CARRY_FLAG)), } //condition (POSEREADY_FLAG == 0) $this->POSEREADY_FLAG = true; break; case 8: case 24: //Throw $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); $this->DECELERATION($player); if ($this->CARRY_FLAG) { if (!$this->keystate(self::KEY_UP) && !$this->keystate(self::KEY_JUMP)) { //Ordinary throw $speedX = 0x0E * 16; $speedY = 0; if ($player->sprite->flipped) { $speedX = 0 - $speedX; } $player->sprite2->y = $player->sprite->y - 16; } else { //Throw up $speedX = 0; $speedY = 0 - 0x0A * 16; } if ($speedY !== 0) { //Throw up $object = $this->FORCE_POSE($level); if ($object !== null) { $object->sprite->speedY = $speedY; $object->sprite->speedX = $speedX - ($speedX >> 2); } } else { if ($player->sprite2->number < self::FIRST_NMI) { //level->objectdata can only be tested against an object sprite if ($level->objectdata[$player->sprite2->number - self::FIRST_OBJET]->gravity) { //Gravity throw $object = $this->FORCE_POSE($level); if ($object !== null) { $object->sprite->speedY = $speedY; $object->sprite->speedX = $speedX - ($speedX >> 2); } } else { //Ordinary throw $this->DROP_FLAG = true; $player->sprite2->speedX = $speedX; $player->sprite2->speedY = $speedY; $this->FX_START(3); //Sound effect } } else { //Ordinary throw $this->DROP_FLAG = true; $player->sprite2->speedX = $speedX; $player->sprite2->speedY = $speedY; $this->FX_START(3); //Sound effect } } $this->updatesprite($level, $player->sprite, 10, true); //The same as in free fall $player->sprite->flipped = ($this->SENSX < 0); $this->CARRY_FLAG = false; } break; //case 9: same as case 0 case 10: //Silent walk? $this->XACCELERATION($player, (self::MAX_X - 1) * 16); $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); break; case 11: //Headache $player->sprite->speedX = 0; $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); break; case 12: case 13: case 28: case 29: //Hit $this->YACCELERATION($player, self::MAX_Y * 16); $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); break; //case 14, 15: //No action //case 16: same as case 0 //case 17: same as case 1 //case 18: same as case 2 //case 19: same as case 1 //case 20: //No action case 21: //Kneestand with box $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); $this->DECELERATION($player); break; //case 22: same as case 6 //case 23: same as case 7 //case 24: same as case 8 //case 25, 26: //No action case 27: //Headache (c) $this->FORCE_POSE($level); $player->sprite->speedX = 0; $this->NEW_FORM($player, $action); $this->GET_IMAGE($level); break; //case 28, 29: same sa case 12 } } public function DECELERATION(Player $player): void { //Stop acceleration $friction = self::u8((3 * 4) >> $player->GLISSE); if ($player->sprite->speedX < 0) { $speed = $player->sprite->speedX + $friction; if ($speed > 0) { $speed = 0; } } else { $speed = $player->sprite->speedX - $friction; if ($speed < 0) { $speed = 0; } } $player->sprite->speedX = $speed; } private function NEW_FORM(Player $player, int $action): void { //if the order is changed, change player animation if (($this->LAST_ORDER !== $action) || ($player->sprite->animTable === null)) { $this->LAST_ORDER = $action; $player->sprite->setAnimation($this->anim_player[$action]); } } private function GET_IMAGE(Level $level): void { $player = $level->player; //animate the player sprite $frame = $player->sprite->animValue(); if ($frame < 0) { //frame is a negative number, telling how many bytes to jump back $player->sprite->anim += intdiv($frame, 2); //jump back to first frame of animation $frame = $player->sprite->animValue(); } $this->updatesprite($level, $player->sprite, $frame, true); $player->sprite->flipped = ($this->SENSX < 0); $player->sprite->anim++; //Advance in the animation } private function YACCELERATION_NEG(Player $player, int $maxspeed): void { //Accelerate upwards $maxspeed = 0 - $maxspeed; //maxspeed is negative $speed = $player->sprite->speedY - 32; if ($speed >= $maxspeed) { $speed = $maxspeed; } $player->sprite->speedY = $speed; } public function add_carry(): int { if ($this->CARRY_FLAG) { return 16; } else { return 0; } } public function COLLISION_TRP(Level $level): void { //Player versus elevators //Change player's location according to the elevator $player = $level->player; $elevator = $level->elevator; if (($player->sprite->speedY >= 0) && ($this->CROSS_FLAG === 0)) { for ($i = 0; $i < $level->elevatorcount; $i++) { //Quick test if (!($elevator[$i]->enabled) || !($elevator[$i]->sprite->visible) || (abs($elevator[$i]->sprite->x - $player->sprite->x) >= 64) || (abs($elevator[$i]->sprite->y - $player->sprite->y) >= 16)) { continue; } //Real test if ($player->sprite->x - $level->spritedata[0]->refwidth < $elevator[$i]->sprite->x) { //The elevator is right if ($player->sprite->x - $level->spritedata[0]->refwidth + $level->spritedata[0]->collwidth <= $elevator[$i]->sprite->x) { //player->sprite must be 0 continue; //The elevator is too far right } } else { //The elevator is left if ($player->sprite->x - $level->spritedata[0]->refwidth >= $elevator[$i]->sprite->x + $elevator[$i]->sprite->spritedata->collwidth) { continue; //The elevator is too far left } } if ($player->sprite->y - 6 < $elevator[$i]->sprite->y) { //The elevator is below if ($player->sprite->y - 6 + 8 <= $elevator[$i]->sprite->y) { continue; //The elevator is too far below } } else { //The elevator is above if ($player->sprite->y - 6 >= $elevator[$i]->sprite->y + $elevator[$i]->sprite->spritedata->collheight) { continue; //The elevator is too far above } } //Skip fall-through-tile action (ACTION_TIMER == 15) if ($this->ACTION_TIMER === 14) { $this->ACTION_TIMER = 16; } $this->YFALL = 0; $player->sprite->y = $elevator[$i]->sprite->y; $player->sprite->speedY = 0; $this->subto0($this->SAUT_FLAG); $this->SAUT_COUNT = 0; $this->YFALL = 2; $player->sprite->x += $elevator[$i]->sprite->speedX; if ($elevator[$i]->sprite->speedY > 0) { //Going down $player->sprite->y += $elevator[$i]->sprite->speedY; } return; } } } public function COLLISION_OBJET(Level $level): void { //Player versus objects //Collision, spring state, speed up carpet/scooter/skateboard, bounce bouncy objects $player = $level->player; $off_object = null; if ($player->sprite->speedY < 0) { return; } //Collision with a sprite if (!($this->SPRITES_VS_SPRITES($level, $player->sprite, $level->spritedata[0], $off_object))) { //check if player stands on an object, use sprite[0] (rest) as collision size (first player tile) return; } $player->sprite->y = $off_object->sprite->y - $off_object->sprite->spritedata->collheight; //If the foot is placed on a spring, it must be soft! if (($off_object->sprite->number === self::FIRST_OBJET + 24) || ($off_object->sprite->number === self::FIRST_OBJET + 25)) { $off_object->sprite->UNDER = $off_object->sprite->UNDER | 0x02; $off_object->sprite->ONTOP = $player->sprite; } //If we jump on the flying carpet, let it fly if (($off_object->sprite->number === self::FIRST_OBJET + 21) || ($off_object->sprite->number === self::FIRST_OBJET + 22)) { //Flying carpet if (!($player->sprite->flipped)) { $off_object->sprite->speedX = 6 * 16; } else { $off_object->sprite->speedX = 0 - 6 * 16; } $off_object->sprite->flipped = $player->sprite->flipped; $this->GRAVITY_FLAG = 4; $this->TAPISWAIT_FLAG = 0; } elseif (($this->ACTION_TIMER > 10) && //delay (($this->LAST_ORDER & 0x0F) === 0) && //Player rests ($player->sprite->speedY === 0) && //stable Y (($off_object->sprite->number === 83) || //scooter ($off_object->sprite->number === 94))) { //skateboard //If you put your foot on a scooter or a skateboard if (!($player->sprite->flipped)) { $off_object->sprite->speedX = 16 * 3; } else { $off_object->sprite->speedX = 0 - 16 * 3; } $off_object->sprite->flipped = $player->sprite->flipped; $this->GRAVITY_FLAG = 4; } if ($off_object->sprite->speedX < 0) { $player->sprite->speedX = $off_object->sprite->speedX; } elseif ($off_object->sprite->speedX > 0) { $player->sprite->speedX = $off_object->sprite->speedX + 16; } //If we want to CROSS (cross) it does not bounce if (($this->CROSS_FLAG === 0) && //No long kneestand ($player->sprite->speedY > (16 * 3)) && ($off_object->objectdata->bounce)) { //Bounce on a ball if no long kneestand (down key) if ($this->keystate(self::KEY_DOWN)) { $player->sprite->speedY = 0; } else { if ($this->keystate(self::KEY_UP) || $this->keystate(self::KEY_JUMP)) { $player->sprite->speedY += 16 * 3; //increase speed } else { $player->sprite->speedY -= 16; //reduce speed } $player->sprite->speedY = 0 - $player->sprite->speedY; if ($player->sprite->speedY > 0) { $player->sprite->speedY = 0; } } $this->ACTION_TIMER = 0; //If the ball lies on the ground if ($off_object->sprite->speedY === 0) { $this->FX_START(12); //Sound effect $off_object->sprite->speedY = 0 - $player->sprite->speedY; $off_object->sprite->y -= $off_object->sprite->speedY >> 4; $this->GRAVITY_FLAG = 4; } } else { if ($off_object->sprite->speedY !== 0) { $player->sprite->speedY = $off_object->sprite->speedY; } else { $player->sprite->speedY = 0; } $this->subto0($this->SAUT_FLAG); $this->SAUT_COUNT = 0; $this->YFALL = 2; } }}