initial commit
This commit is contained in:
commit
e8c3f9862a
193 files changed
+29795
No files matched your search
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Game;
|
||||
|
||||
use App\Game\Entities\Bullet;
|
||||
use App\Game\Entities\Zombie;
|
||||
|
||||
final class Player extends GameObject
|
||||
{
|
||||
public InputState $input;
|
||||
|
||||
private EntityList $container;
|
||||
|
||||
private Room $room;
|
||||
|
||||
public float $moveSpeed;
|
||||
|
||||
public float $fireRate;
|
||||
|
||||
public float $recoilSpeed;
|
||||
|
||||
public float $recoilDamping;
|
||||
|
||||
public float $bulletSpeed;
|
||||
|
||||
public float $bulletSize;
|
||||
|
||||
public int $bulletCount;
|
||||
|
||||
public float $bulletSpread;
|
||||
|
||||
public int $damage;
|
||||
|
||||
private float $baseFireRate;
|
||||
|
||||
private int $baseDamage;
|
||||
|
||||
private int $baseBulletCount;
|
||||
|
||||
private float $baseBulletSpeed;
|
||||
|
||||
public int $maxUpgradeLevel;
|
||||
|
||||
/** @var array<string, int> */
|
||||
public array $upgrades = ['rapid' => 0, 'damage' => 0, 'multishot' => 0, 'speed' => 0];
|
||||
|
||||
public float $size;
|
||||
|
||||
public string $name;
|
||||
|
||||
public string $id;
|
||||
|
||||
public int $health;
|
||||
|
||||
public int $maxHealth;
|
||||
|
||||
public int $score = 0;
|
||||
|
||||
public bool $dead = false;
|
||||
|
||||
public bool $autoAim = false;
|
||||
|
||||
private float $cooldown = 0.0;
|
||||
|
||||
private Vector2 $aimDirection;
|
||||
|
||||
private Vector2 $recoil;
|
||||
|
||||
private float $damageCooldown = 0.0;
|
||||
|
||||
private float $time = 0.0;
|
||||
|
||||
public function __construct(
|
||||
string $playerId,
|
||||
string $name,
|
||||
InputState $input,
|
||||
EntityList $container,
|
||||
Room $room,
|
||||
array $config = [],
|
||||
) {
|
||||
parent::__construct(
|
||||
$config['x'] ?? $room->width / 2.0,
|
||||
$config['y'] ?? $room->height / 2.0,
|
||||
);
|
||||
$this->input = $input;
|
||||
$this->container = $container;
|
||||
$this->room = $room;
|
||||
$this->id = $playerId;
|
||||
$this->name = $name;
|
||||
$this->moveSpeed = $config['moveSpeed'] ?? 420.0;
|
||||
$this->fireRate = $config['fireRate'] ?? 0.2;
|
||||
$this->recoilSpeed = $config['recoilSpeed'] ?? 100.0;
|
||||
$this->recoilDamping = $config['recoilDamping'] ?? 15.0;
|
||||
$this->bulletSpeed = $config['bulletSpeed'] ?? 800.0;
|
||||
$this->bulletSize = $config['bulletSize'] ?? 8.0;
|
||||
$this->bulletCount = $config['bulletCount'] ?? 1;
|
||||
$this->bulletSpread = $config['bulletSpread'] ?? 0.09;
|
||||
$this->damage = $config['damage'] ?? 1;
|
||||
$this->baseFireRate = $this->fireRate;
|
||||
$this->baseDamage = $this->damage;
|
||||
$this->baseBulletCount = $this->bulletCount;
|
||||
$this->baseBulletSpeed = $this->bulletSpeed;
|
||||
$this->maxUpgradeLevel = $config['maxUpgradeLevel'] ?? 3;
|
||||
$this->health = $config['health'] ?? 3;
|
||||
$this->maxHealth = $this->health;
|
||||
$this->size = $config['size'] ?? 24.0;
|
||||
$this->width = $this->size;
|
||||
$this->height = $this->size;
|
||||
$this->aimDirection = new Vector2(1.0, 0.0);
|
||||
$this->recoil = Vector2::zero();
|
||||
}
|
||||
|
||||
public function kind(): string
|
||||
{
|
||||
return 'player';
|
||||
}
|
||||
|
||||
public function update(float $dt): void
|
||||
{
|
||||
$this->time += $dt;
|
||||
|
||||
$movement = new Vector2(0.0, 0.0);
|
||||
$keys = $this->input->keys;
|
||||
if (in_array('w', $keys, true) || in_array('arrowup', $keys, true)) {
|
||||
$movement = new Vector2($movement->x, $movement->y - 1.0);
|
||||
}
|
||||
if (in_array('s', $keys, true) || in_array('arrowdown', $keys, true)) {
|
||||
$movement = new Vector2($movement->x, $movement->y + 1.0);
|
||||
}
|
||||
if (in_array('a', $keys, true) || in_array('arrowleft', $keys, true)) {
|
||||
$movement = new Vector2($movement->x - 1.0, $movement->y);
|
||||
}
|
||||
if (in_array('d', $keys, true) || in_array('arrowright', $keys, true)) {
|
||||
$movement = new Vector2($movement->x + 1.0, $movement->y);
|
||||
}
|
||||
|
||||
$moveVelocity = Vector2::zero();
|
||||
if ($movement->lengthSquared() > 0.0) {
|
||||
$moveVelocity = $movement->normalize()->mulS($this->moveSpeed);
|
||||
}
|
||||
|
||||
$this->velocity = $this->recoil->lengthSquared() > 1.0
|
||||
? $moveVelocity->addV($this->recoil)
|
||||
: $moveVelocity;
|
||||
parent::update($dt);
|
||||
|
||||
$maxX = max($this->width / 2.0, 0.0);
|
||||
$maxY = max($this->height / 2.0, 0.0);
|
||||
$this->pos = new Vector2(
|
||||
min(max($this->pos->x, $maxX), $this->room->width - $maxX),
|
||||
min(max($this->pos->y, $maxY), $this->room->height - $maxY),
|
||||
);
|
||||
|
||||
$aim = $this->input->aim;
|
||||
$autoAimActive = $this->input->autoAim || $this->autoAim;
|
||||
$hasTarget = false;
|
||||
if ($autoAimActive) {
|
||||
$nearest = $this->nearestZombie();
|
||||
if ($nearest !== null) {
|
||||
$hasTarget = true;
|
||||
$toEnemy = $nearest->pos->subV($this->pos);
|
||||
if ($toEnemy->lengthSquared() > 1.0) {
|
||||
$aim = $toEnemy;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($aim->lengthSquared() > 1.0) {
|
||||
$this->aimDirection = $aim->divS($aim->length());
|
||||
} elseif ($aim->lengthSquared() > 0.01 && $this->input->aim !== null && ! $autoAimActive) {
|
||||
// Raw normalized direction from the client when not auto-aiming.
|
||||
$this->aimDirection = $aim->normalize();
|
||||
}
|
||||
|
||||
$this->recoil = $this->recoil->mulS(max(0.0, 1 - $dt * $this->recoilDamping));
|
||||
$this->damageCooldown = max(0.0, $this->damageCooldown - $dt);
|
||||
|
||||
$this->cooldown -= $dt;
|
||||
if ($this->cooldown <= 0.0 && ($this->input->fire || ($autoAimActive && $hasTarget))) {
|
||||
$this->cooldown = $this->fireRate;
|
||||
$this->shoot();
|
||||
}
|
||||
}
|
||||
|
||||
public function nearestZombie(): ?Zombie
|
||||
{
|
||||
$best = null;
|
||||
$bestDistSq = INF;
|
||||
foreach ($this->container as $obj) {
|
||||
if (! $obj instanceof Zombie) {
|
||||
continue;
|
||||
}
|
||||
$delta = $obj->pos->subV($this->pos);
|
||||
$distSq = $delta->lengthSquared();
|
||||
if ($distSq < $bestDistSq) {
|
||||
$bestDistSq = $distSq;
|
||||
$best = $obj;
|
||||
}
|
||||
}
|
||||
|
||||
return $best;
|
||||
}
|
||||
|
||||
public function shoot(): void
|
||||
{
|
||||
$aimAngle = atan2($this->aimDirection->y, $this->aimDirection->x);
|
||||
$this->recoil = $this->aimDirection->mulS(-$this->recoilSpeed);
|
||||
|
||||
$offset = (($this->bulletCount - 1) / 2.0) * $this->bulletSpread;
|
||||
for ($i = 0; $i < $this->bulletCount; $i++) {
|
||||
$angle = $aimAngle - $offset + $i * $this->bulletSpread;
|
||||
$direction = new Vector2(cos($angle), sin($angle));
|
||||
$bullet = new Bullet(
|
||||
$this->pos->x,
|
||||
$this->pos->y,
|
||||
$direction,
|
||||
$this->container,
|
||||
$this->room,
|
||||
$this,
|
||||
$this->bulletSpeed,
|
||||
$this->bulletSize,
|
||||
$this->damage,
|
||||
);
|
||||
$this->container->append($bullet);
|
||||
}
|
||||
}
|
||||
|
||||
public function applyUpgrade(string $type): bool
|
||||
{
|
||||
if (! array_key_exists($type, $this->upgrades)) {
|
||||
return false;
|
||||
}
|
||||
if ($this->upgrades[$type] >= $this->maxUpgradeLevel) {
|
||||
return false;
|
||||
}
|
||||
$this->upgrades[$type] += 1;
|
||||
switch ($type) {
|
||||
case 'rapid':
|
||||
$this->fireRate = max(0.06, $this->fireRate * 0.8);
|
||||
break;
|
||||
case 'damage':
|
||||
$this->damage += 1;
|
||||
break;
|
||||
case 'multishot':
|
||||
$this->bulletCount = min(7, $this->bulletCount + 1);
|
||||
break;
|
||||
case 'speed':
|
||||
$this->bulletSpeed = min(1800.0, $this->bulletSpeed * 1.15);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function toSnapshot(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'x' => round($this->pos->x, 1),
|
||||
'y' => round($this->pos->y, 1),
|
||||
'aimX' => round($this->aimDirection->x, 3),
|
||||
'aimY' => round($this->aimDirection->y, 3),
|
||||
'hp' => $this->health,
|
||||
'mhp' => $this->maxHealth,
|
||||
'score' => $this->score,
|
||||
'dead' => $this->dead,
|
||||
'host' => $this->room->hostId === $this->id,
|
||||
'up' => $this->upgrades,
|
||||
];
|
||||
}
|
||||
|
||||
/** Reset per-round state back to lobby defaults (keeps id/name/host). */
|
||||
public function resetForRound(): void
|
||||
{
|
||||
$this->pos = new Vector2($this->room->width / 2.0, $this->room->height / 2.0);
|
||||
$this->velocity = Vector2::zero();
|
||||
$this->recoil = Vector2::zero();
|
||||
$this->aimDirection = new Vector2(1.0, 0.0);
|
||||
$this->health = $this->maxHealth;
|
||||
$this->dead = false;
|
||||
$this->score = 0;
|
||||
$this->upgrades = ['rapid' => 0, 'damage' => 0, 'multishot' => 0, 'speed' => 0];
|
||||
$this->fireRate = $this->baseFireRate;
|
||||
$this->bulletSpeed = $this->baseBulletSpeed;
|
||||
$this->bulletCount = $this->baseBulletCount;
|
||||
$this->damage = $this->baseDamage;
|
||||
$this->cooldown = 0.0;
|
||||
$this->damageCooldown = 0.0;
|
||||
$this->time = 0.0;
|
||||
}
|
||||
|
||||
public function takeDamage(): void
|
||||
{
|
||||
if ($this->damageCooldown > 0.0) {
|
||||
return;
|
||||
}
|
||||
$this->damageCooldown = 0.5;
|
||||
$this->health -= 1;
|
||||
if ($this->health <= 0 && ! $this->dead) {
|
||||
$this->dead = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function addScore(int $points): void
|
||||
{
|
||||
$this->score += $points;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user