176 lines
6.0 KiB
PHP
176 lines
6.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Game;
|
|
|
|
use App\Game\Entities\Zombie;
|
|
use App\Game\InputState;
|
|
use App\Game\Player;
|
|
use App\Game\Room;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class PlayerTest extends TestCase
|
|
{
|
|
private function makePlayer(?Room $room = null): Player
|
|
{
|
|
$room = $room ?? new Room('test');
|
|
$input = new InputState;
|
|
|
|
return new Player('p1', 'Tester', $input, $room->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, autoAim: true);
|
|
$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, autoAim: 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);
|
|
$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, autoAim: 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);
|
|
}
|
|
}
|