initial commit
This commit is contained in:
commit
e8c3f9862a
193 files changed
+29795
No files matched your search
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Game;
|
||||
|
||||
use App\Game\Entities\Boss;
|
||||
use App\Game\Entities\Leaper;
|
||||
use App\Game\Entities\MegaTank;
|
||||
use App\Game\Entities\MegaWitch;
|
||||
use App\Game\Entities\Pickup;
|
||||
use App\Game\Entities\Witch;
|
||||
use App\Game\Entities\Zombie;
|
||||
|
||||
final class Spawner
|
||||
{
|
||||
public function spawnInterval(float $elapsed): float
|
||||
{
|
||||
return max(0.35, 1.2 - $elapsed * 0.008);
|
||||
}
|
||||
|
||||
public function speedMultiplier(float $elapsed): float
|
||||
{
|
||||
return min(2.2, 1.0 + $elapsed * 0.004);
|
||||
}
|
||||
|
||||
private function tankChance(float $elapsed): float
|
||||
{
|
||||
return min(0.5, 0.15 + $elapsed * 0.002);
|
||||
}
|
||||
|
||||
private function megaTankChance(float $elapsed): float
|
||||
{
|
||||
return min(0.22, $elapsed * 0.0015);
|
||||
}
|
||||
|
||||
private function runnerChance(float $elapsed): float
|
||||
{
|
||||
return min(0.4, 0.2 + $elapsed * 0.001);
|
||||
}
|
||||
|
||||
private function leaperChance(float $elapsed): float
|
||||
{
|
||||
return min(0.12, 0.04 + $elapsed * 0.06);
|
||||
}
|
||||
|
||||
private function witchChance(float $elapsed): float
|
||||
{
|
||||
return min(0.3, 0.1 + $elapsed * 0.002);
|
||||
}
|
||||
|
||||
private function megaWitchChance(float $elapsed): float
|
||||
{
|
||||
return $elapsed < 45.0 ? 0.0 : min(0.12, ($elapsed - 45.0) * 0.0008);
|
||||
}
|
||||
|
||||
public function spawnZombie(Room $room): void
|
||||
{
|
||||
$elapsed = $room->elapsed;
|
||||
$width = $room->width;
|
||||
$height = $room->height;
|
||||
$speedMultiplier = $this->speedMultiplier($elapsed);
|
||||
|
||||
$isMegaTank = $this->chance($this->megaTankChance($elapsed));
|
||||
$isMegaWitch = ! $isMegaTank && $this->chance($this->megaWitchChance($elapsed));
|
||||
$isWitch = ! $isMegaTank && ! $isMegaWitch && $this->chance($this->witchChance($elapsed));
|
||||
$isRunner = ! $isMegaTank && ! $isMegaWitch && ! $isWitch && $this->chance($this->runnerChance($elapsed));
|
||||
$isLeaper = ! $isMegaTank && ! $isMegaWitch && ! $isWitch && ! $isRunner && $this->chance($this->leaperChance($elapsed));
|
||||
$isTank = ! $isMegaTank && ! $isMegaWitch && ! $isWitch && ! $isRunner && ! $isLeaper && $this->chance($this->tankChance($elapsed));
|
||||
|
||||
$size = $isMegaWitch ? 52.0 : ($isWitch ? 30.0 : ($isRunner ? 14.0 : ($isLeaper ? 28.0 : ($isTank ? 36.0 : 24.0))));
|
||||
|
||||
[$x, $y] = $this->edgePosition($room, $size);
|
||||
|
||||
if ($isMegaTank) {
|
||||
$room->entities->append(new MegaTank($x, $y, $room->entities, $room, $this->initialTarget($room), [
|
||||
'speed' => 28.0 + $this->rand() * 12.0,
|
||||
'size' => 64.0,
|
||||
'health' => 18,
|
||||
'knockback' => 90.0,
|
||||
'scoreValue' => 15,
|
||||
'spawnCount' => 3,
|
||||
'color' => [0.55, 0.28, 0.12, 1.0],
|
||||
'spawnOnDeath' => function (float $sx, float $sy, EntityList $container, Room $sroom, ?Player $starget): void {
|
||||
$container->append(new Zombie($sx, $sy, $container, $sroom, [
|
||||
'speed' => (70.0 + $this->rand() * 60.0) * $this->speedMultiplier($sroom->elapsed),
|
||||
'size' => 24.0,
|
||||
'health' => 1,
|
||||
'knockback' => 260.0,
|
||||
'scoreValue' => 1,
|
||||
'color' => [0.15, 0.65, 0.2, 1.0],
|
||||
'emergeTime' => 0.8,
|
||||
], fn (Zombie $z) => $z->target = $starget));
|
||||
$sroom->pushEvent('portal', ['x' => $sx, 'y' => $sy, 'color' => [0.15, 0.65, 0.2, 1.0], 'scale' => 0.8]);
|
||||
},
|
||||
], null));
|
||||
$room->pushEvent('portal', ['x' => $x, 'y' => $y, 'color' => [0.55, 0.28, 0.12, 1.0], 'scale' => 2.2]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($isMegaWitch) {
|
||||
$megaPower = min(1.0, max(0.0, ($elapsed - 45.0) / 90.0));
|
||||
$this->spawnMegaWitch($room, $x, $y, $speedMultiplier, $megaPower);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($isWitch) {
|
||||
$this->spawnWitch($room, $x, $y, $speedMultiplier);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($isRunner) {
|
||||
$room->entities->append(new Zombie($x, $y, $room->entities, $room, [
|
||||
'speed' => (180.0 + $this->rand() * 60.0) * $speedMultiplier,
|
||||
'size' => $size,
|
||||
'health' => 1,
|
||||
'knockback' => 380.0,
|
||||
'scoreValue' => 2,
|
||||
'color' => [0.85, 0.1, 0.2, 1.0],
|
||||
], fn (Zombie $z) => $z->target = $this->initialTarget($room)));
|
||||
$room->pushEvent('portal', ['x' => $x, 'y' => $y, 'color' => [0.85, 0.1, 0.2, 1.0], 'scale' => 0.9]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($isLeaper) {
|
||||
$this->spawnLeaper($room, $x, $y, $speedMultiplier);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$zombieColor = $isTank ? [0.3, 0.35, 0.15, 1.0] : [0.15, 0.65, 0.2, 1.0];
|
||||
$room->entities->append(new Zombie($x, $y, $room->entities, $room, [
|
||||
'speed' => ($isTank ? 45.0 : 80.0 + $this->rand() * 60.0) * $speedMultiplier,
|
||||
'size' => $size,
|
||||
'health' => $isTank ? 4 : 1,
|
||||
'knockback' => $isTank ? 160.0 : 260.0,
|
||||
'scoreValue' => $isTank ? 3 : 1,
|
||||
'color' => $zombieColor,
|
||||
], fn (Zombie $z) => $z->target = $this->initialTarget($room)));
|
||||
$room->pushEvent('portal', ['x' => $x, 'y' => $y, 'color' => $zombieColor, 'scale' => $isTank ? 1.5 : 1.0]);
|
||||
}
|
||||
|
||||
public function spawnWitch(Room $room, float $x, float $y, float $speedMultiplier = 1.0, int $emergeTime = 0): void
|
||||
{
|
||||
$room->entities->append(new Witch($x, $y, $room->entities, $room, $this->initialTarget($room), [
|
||||
'speed' => (70.0 + $this->rand() * 40.0) * $speedMultiplier,
|
||||
'size' => 30.0,
|
||||
'health' => 5,
|
||||
'knockback' => 180.0,
|
||||
'scoreValue' => 4,
|
||||
'color' => [0.55, 0.2, 0.8, 1.0],
|
||||
'preferredDistance' => 220.0,
|
||||
'fireInterval' => 2.2,
|
||||
'projectileSpeed' => 220.0,
|
||||
'emergeTime' => $emergeTime,
|
||||
], fn (Zombie $z) => $z->target = $this->initialTarget($room)));
|
||||
$room->pushEvent('portal', ['x' => $x, 'y' => $y, 'color' => [0.55, 0.2, 0.8, 1.0], 'scale' => 1.2]);
|
||||
}
|
||||
|
||||
public function spawnMegaWitch(Room $room, float $x, float $y, float $speedMultiplier = 1.0, float $power = 1.0): void
|
||||
{
|
||||
$room->entities->append(new MegaWitch($x, $y, $room->entities, $room, $this->initialTarget($room), [
|
||||
'speed' => 55.0 * $speedMultiplier,
|
||||
'size' => 52.0,
|
||||
'health' => (int) round(8.0 + 14.0 * $power),
|
||||
'knockback' => 130.0,
|
||||
'scoreValue' => 12,
|
||||
'color' => [0.8, 0.15, 0.7, 1.0],
|
||||
'preferredDistance' => 240.0,
|
||||
'fireInterval' => 2.0,
|
||||
'projectileSpeed' => 240.0,
|
||||
'projectileCount' => 3,
|
||||
'projectileSpread' => 0.35,
|
||||
'projectileColor' => [1.0, 0.3, 0.3, 1.0],
|
||||
'novaInterval' => 6.0 - $power,
|
||||
'novaCount' => (int) round(10.0 + 6.0 * $power),
|
||||
'novaProjectileSpeed' => 210.0 + 40.0 * $power,
|
||||
'spawnCount' => 3,
|
||||
'spawnOnDeath' => function (float $sx, float $sy, EntityList $container, Room $sroom, ?Player $starget): void {
|
||||
$container->append(new Witch($sx, $sy, $container, $sroom, $starget, [
|
||||
'speed' => 70.0 + $this->rand() * 40.0,
|
||||
'size' => 30.0,
|
||||
'health' => 5,
|
||||
'knockback' => 180.0,
|
||||
'scoreValue' => 4,
|
||||
'color' => [0.55, 0.2, 0.8, 1.0],
|
||||
'preferredDistance' => 220.0,
|
||||
'fireInterval' => 2.2,
|
||||
'projectileSpeed' => 220.0,
|
||||
'emergeTime' => 1.1,
|
||||
], fn (Zombie $z) => $z->target = $starget));
|
||||
$sroom->pushEvent('portal', ['x' => $sx, 'y' => $sy, 'color' => [0.55, 0.2, 0.8, 1.0], 'scale' => 1.2]);
|
||||
},
|
||||
], null));
|
||||
$room->pushEvent('portal', ['x' => $x, 'y' => $y, 'color' => [0.8, 0.15, 0.7, 1.0], 'scale' => 2.2]);
|
||||
}
|
||||
|
||||
public function spawnLeaper(Room $room, float $x, float $y, float $speedMultiplier = 1.0, int $emergeTime = 0): void
|
||||
{
|
||||
$room->entities->append(new Leaper($x, $y, $room->entities, $room, $this->initialTarget($room), [
|
||||
'speed' => (80.0 + $this->rand() * 40.0) * $speedMultiplier,
|
||||
'size' => 28.0,
|
||||
'health' => 3,
|
||||
'knockback' => 240.0,
|
||||
'scoreValue' => 6,
|
||||
'color' => [0.15, 0.25, 0.75, 1.0],
|
||||
'dashRange' => 300.0,
|
||||
'windupTime' => 0.35,
|
||||
'dashTime' => 0.28,
|
||||
'dashSpeed' => 680.0,
|
||||
'dashCooldown' => 1.8,
|
||||
'emergeTime' => $emergeTime,
|
||||
], fn (Zombie $z) => $z->target = $this->initialTarget($room)));
|
||||
$room->pushEvent('portal', ['x' => $x, 'y' => $y, 'color' => [0.15, 0.25, 0.75, 1.0], 'scale' => 1.2]);
|
||||
}
|
||||
|
||||
public function spawnBoss(Room $room): void
|
||||
{
|
||||
$width = $room->width;
|
||||
$witchCount = min(4, 2 ** $room->bossCount);
|
||||
$room->bossCount += 1;
|
||||
$room->entities->append(new Boss($width / 2.0, -80.0, $room->entities, $room, $this->initialTarget($room), [
|
||||
'speed' => 36.0 + $this->rand() * 10.0,
|
||||
'size' => 90.0,
|
||||
'health' => 80,
|
||||
'knockback' => 40.0,
|
||||
'scoreValue' => 50,
|
||||
'color' => [0.45, 0.05, 0.1, 1.0],
|
||||
'spawnInterval' => 5.0,
|
||||
'spawnCount' => $witchCount,
|
||||
'spawnZombie' => function (float $sx, float $sy, EntityList $container, Room $sroom, ?Player $starget): void {
|
||||
if ($this->chance(0.5)) {
|
||||
$container->append(new Witch($sx, $sy, $container, $sroom, $starget, [
|
||||
'speed' => (70.0 + $this->rand() * 40.0) * $this->speedMultiplier($sroom->elapsed),
|
||||
'size' => 30.0,
|
||||
'health' => 5,
|
||||
'knockback' => 180.0,
|
||||
'scoreValue' => 4,
|
||||
'color' => [0.55, 0.2, 0.8, 1.0],
|
||||
'preferredDistance' => 220.0,
|
||||
'fireInterval' => 2.2,
|
||||
'projectileSpeed' => 220.0,
|
||||
'emergeTime' => 0.9,
|
||||
], fn (Zombie $z) => $z->target = $starget));
|
||||
} else {
|
||||
$container->append(new Leaper($sx, $sy, $container, $sroom, $starget, [
|
||||
'speed' => (80.0 + $this->rand() * 40.0) * $this->speedMultiplier($sroom->elapsed),
|
||||
'size' => 28.0,
|
||||
'health' => 3,
|
||||
'knockback' => 240.0,
|
||||
'scoreValue' => 6,
|
||||
'color' => [0.15, 0.25, 0.75, 1.0],
|
||||
'dashRange' => 300.0,
|
||||
'windupTime' => 0.35,
|
||||
'dashTime' => 0.28,
|
||||
'dashSpeed' => 680.0,
|
||||
'dashCooldown' => 1.8,
|
||||
'emergeTime' => 0.9,
|
||||
], fn (Zombie $z) => $z->target = $starget));
|
||||
}
|
||||
},
|
||||
], fn (Zombie $z) => $z->target = $this->initialTarget($room)));
|
||||
$room->pushEvent('portal', ['x' => $width / 2.0, 'y' => -80.0, 'color' => [0.45, 0.05, 0.1, 1.0], 'scale' => 3.5]);
|
||||
$room->pushEvent('boss', [
|
||||
'x' => $width / 2.0,
|
||||
'y' => $room->height / 2.0,
|
||||
'title' => 'BOSS APPROACHES',
|
||||
]);
|
||||
}
|
||||
|
||||
public function spawnPickup(Room $room): void
|
||||
{
|
||||
$available = [];
|
||||
foreach (array_keys(Pickup::TYPES) as $type) {
|
||||
$needed = false;
|
||||
foreach ($room->alivePlayers() as $player) {
|
||||
if (($player->upgrades[$type] ?? 0) < $player->maxUpgradeLevel) {
|
||||
$needed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($needed) {
|
||||
$available[] = $type;
|
||||
}
|
||||
}
|
||||
if ($available === []) {
|
||||
return;
|
||||
}
|
||||
$margin = 60.0;
|
||||
$x = $margin + $this->rand() * ($room->width - $margin * 2.0);
|
||||
$y = $margin + $this->rand() * ($room->height - $margin * 2.0);
|
||||
$type = $available[(int) floor($this->rand() * count($available))];
|
||||
$room->entities->append(new Pickup($x, $y, $type, $room->entities, $room));
|
||||
}
|
||||
|
||||
/** @return array{0:float, 1:float} */
|
||||
private function edgePosition(Room $room, float $size): array
|
||||
{
|
||||
$width = $room->width;
|
||||
$height = $room->height;
|
||||
$edge = (int) floor($this->rand() * 4.0);
|
||||
|
||||
return match ($edge) {
|
||||
0 => [$this->rand() * $width, -$size],
|
||||
1 => [$this->rand() * $width, $height + $size],
|
||||
2 => [-$size, $this->rand() * $height],
|
||||
default => [$width + $size, $this->rand() * $height],
|
||||
};
|
||||
}
|
||||
|
||||
private function initialTarget(Room $room): ?Player
|
||||
{
|
||||
$alive = $room->alivePlayers();
|
||||
|
||||
return $alive === [] ? null : $alive[array_rand($alive)];
|
||||
}
|
||||
|
||||
private function chance(float $probability): bool
|
||||
{
|
||||
return $this->rand() < $probability;
|
||||
}
|
||||
|
||||
private function rand(): float
|
||||
{
|
||||
return random_int(0, 1000000) / 1000000.0;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user