entities, $room, [ 'x' => 100.0, 'y' => 100.0, 'health' => 3, ]); } #[Test] public function movement_keys_affect_velocity(): void { $room = new Room('test'); $input = new InputState(['d']); $player = new Player('p1', 'Tester', $input, $room->entities, $room, ['x' => 100.0, 'y' => 100.0]); $player->update(1.0 / 60.0); $this->assertGreaterThan(100.0, $player->pos->x); } #[Test] public function fire_spawns_bullets(): void { $room = new Room('test'); $input = new InputState(fire: true); $player = new Player('p1', 'Tester', $input, $room->entities, $room, ['x' => 100.0, 'y' => 100.0]); $room->addPlayer($player); $player->update(1.0 / 60.0); $bullets = $room->entities->findByKind('bullet'); $this->assertNotEmpty($bullets); } #[Test] public function auto_aim_fires_at_nearest_zombie_without_mouse(): void { $room = new Room('test'); $room->state = Room::STATE_PLAYING; $input = new InputState(fire: false); $player = new Player('p1', 'Tester', $input, $room->entities, $room, ['x' => 100.0, 'y' => 100.0]); $room->addPlayer($player); $zombie = new Zombie(300.0, 100.0, $room->entities, $room, ['emergeTime' => 0.0]); $zombie->target = $player; $room->entities->append($zombie); $player->update(1.0 / 60.0); $bullets = $room->entities->findByKind('bullet'); $this->assertNotEmpty($bullets, 'auto-aim should fire at a zombie without the mouse held'); $direction = $bullets[0]->velocity->normalize(); $this->assertGreaterThan(0.9, $direction->x, 'bullet should fly toward the zombie to the right'); } #[Test] public function auto_aim_does_not_fire_without_target(): void { $room = new Room('test'); $input = new InputState(fire: false); $player = new Player('p1', 'Tester', $input, $room->entities, $room, ['x' => 100.0, 'y' => 100.0]); $room->addPlayer($player); $player->update(1.0 / 60.0); $this->assertEmpty($room->entities->findByKind('bullet'), 'no auto-fire when no zombie is in range'); } #[Test] public function take_damage_decreases_health_and_cooldown_prevents_double_hit(): void { $room = new Room('test'); $player = $this->makePlayer($room); $player->takeDamage(); $this->assertSame(2, $player->health); $this->assertFalse($player->dead); $player->takeDamage(); $this->assertSame(2, $player->health, 'damage cooldown prevented second hit'); } #[Test] public function take_damage_kills_at_zero_health(): void { $room = new Room('test'); $player = $this->makePlayer($room); $player->takeDamage(); $player->update(0.6); $player->takeDamage(); $player->update(0.6); $player->takeDamage(); $this->assertSame(0, $player->health); $this->assertTrue($player->dead); } #[Test] public function apply_upgrade_increases_level_and_effects(): void { $room = new Room('test'); $player = $this->makePlayer($room); $originalFireRate = $player->fireRate; $this->assertTrue($player->applyUpgrade('rapid')); $this->assertSame(1, $player->upgrades['rapid']); $this->assertLessThan($originalFireRate, $player->fireRate); } #[Test] public function apply_upgrade_returns_false_when_maxed(): void { $room = new Room('test'); $player = $this->makePlayer($room); for ($i = 0; $i < $player->maxUpgradeLevel; $i++) { $this->assertTrue($player->applyUpgrade('damage')); } $this->assertFalse($player->applyUpgrade('damage')); $this->assertSame($player->maxUpgradeLevel, $player->upgrades['damage']); } #[Test] public function apply_upgrade_unknown_type_returns_false(): void { $player = $this->makePlayer(); $this->assertFalse($player->applyUpgrade('nonexistent')); } #[Test] public function reset_for_round_restores_derived_stats(): void { $room = new Room('test'); $input = new InputState; $player = new Player('p1', 'Tester', $input, $room->entities, $room, [ 'x' => 100.0, 'y' => 100.0, 'fireRate' => 0.5, 'damage' => 2, 'bulletCount' => 3, 'bulletSpeed' => 900.0, ]); $player->applyUpgrade('rapid'); $player->applyUpgrade('damage'); $player->applyUpgrade('multishot'); $player->applyUpgrade('speed'); $player->resetForRound(); $this->assertSame(['rapid' => 0, 'damage' => 0, 'multishot' => 0, 'speed' => 0], $player->upgrades); $this->assertSame(0.5, $player->fireRate); $this->assertSame(2, $player->damage); $this->assertSame(3, $player->bulletCount); $this->assertSame(900.0, $player->bulletSpeed); } #[Test] public function auto_aim_tracks_nearest_zombie(): void { $room = new Room('test'); $input = new InputState(fire: true); $player = new Player('p1', 'Tester', $input, $room->entities, $room, ['x' => 100.0, 'y' => 100.0]); $room->addPlayer($player); $zombie = new Zombie(200.0, 100.0, $room->entities, $room, ['emergeTime' => 0.0]); $room->entities->append($zombie); $player->update(1.0 / 60.0); $bullets = $room->entities->findByKind('bullet'); $this->assertNotEmpty($bullets); } }