Skip to content

Commit

Permalink
Flammable grenade support + HighExplosive
Browse files Browse the repository at this point in the history
  • Loading branch information
starswaitforus authored and solcloud committed Aug 15, 2024
1 parent f62a1f2 commit 0312c3d
Show file tree
Hide file tree
Showing 39 changed files with 1,535 additions and 91 deletions.
9 changes: 6 additions & 3 deletions cli/server.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
$bindAddress = "udp://0.0.0.0:$port";
/////

$settings = new ServerSetting($playersMax); // must be first for correctly setting the global tickRate (Util::$TICK_RATE)

$logger = new ConsoleLogger();
$settings = new ServerSetting($playersMax);
$logger->info("Starting server on '{$bindAddress}', waiting maximum of '{$settings->warmupWaitSec}' sec for '{$playersMax}' player" . ($playersMax > 1 ? 's' : '') . " to connect.");
$net = new ClueSocket($bindAddress);
$logger->info("Preparing game for launch.");

$game = ($debug ? GameFactory::createDebug() : GameFactory::createDefaultCompetitive());
$game->loadMap(new Maps\DefaultMap());
$game->getWorld()->regenerateNavigationMeshes();

$logger->info("Starting server on '{$bindAddress}', waiting maximum of '{$settings->warmupWaitSec}' sec for '{$playersMax}' player" . ($playersMax > 1 ? 's' : '') . " to connect.");
$net = new ClueSocket($bindAddress);
$server = new Server($game, $settings, $net);
$server->setLogger($logger);
if ($debug) {
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"php": ">=8.1",
"ext-sockets": "*",

"actived/graphphp": "0.2.2",
"clue/socket-raw": "1.6.0",
"textalk/websocket": "1.5.8",
"psr/log": "3.0.0"
Expand Down
49 changes: 48 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
stderr="true"
colors="true"
cacheResult="false"
stopOnFailure="true"
executionOrder="random"
>
<testsuites>
Expand Down
15 changes: 15 additions & 0 deletions server/src/Core/Flame.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace cs\Core;

final class Flame
{

public readonly Point $highestPoint;

public function __construct(public readonly Point $center, public readonly int $radius, public readonly int $height)
{
$this->highestPoint = $this->center->clone()->addY($this->height);
}

}
25 changes: 23 additions & 2 deletions server/src/Core/Game.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
use cs\Enum\RoundEndReason;
use cs\Enum\SoundType;
use cs\Equipment\Bomb;
use cs\Equipment\Grenade;
use cs\Event\DropEvent;
use cs\Event\Event;
use cs\Event\GameOverEvent;
use cs\Event\GrillEvent;
use cs\Event\KillEvent;
use cs\Event\PauseEndEvent;
use cs\Event\PauseStartEvent;
Expand Down Expand Up @@ -251,22 +253,41 @@ public function addThrowEvent(ThrowEvent $event): void
$this->addEvent($event);
}

public function addGrillEvent(GrillEvent $event): void
{
$this->addEvent($event);
}

public function addDropEvent(DropEvent $event): void
{
$this->addEvent($event);
}

public function playerAttackKilledEvent(Player $playerDead, Bullet $bullet, bool $headShot): void
{
$playerCulprit = $this->players[$bullet->getOriginPlayerId()];
$this->playerKilledEvent($this->players[$bullet->getOriginPlayerId()], $playerDead, $bullet->getShootItem()->getId(), $headShot);
}

public function playerGrenadeKilledEvent(Player $playerCulprit, Player $playerDead, Grenade $item): void
{
$moneyAward = $item->getKillAward();
if ($playerCulprit->isPlayingOnAttackerSide() === $playerDead->isPlayingOnAttackerSide()) {
$moneyAward = -300;
}
$playerCulprit->getInventory()->earnMoney($moneyAward);
$this->playerKilledEvent($playerCulprit, $playerDead, $item->getId(), false);
}

private function playerKilledEvent(Player $playerCulprit, Player $playerDead, int $itemId, bool $headShot): void
{
if ($playerDead->isPlayingOnAttackerSide() === $playerCulprit->isPlayingOnAttackerSide()) { // team kill
$this->score->getPlayerStat($playerCulprit->getId())->removeKill();
} else {
$this->score->getPlayerStat($playerCulprit->getId())->addKill($headShot);
}
$this->score->getPlayerStat($playerDead->getId())->addDeath();

$this->addEvent(new KillEvent($playerDead, $playerCulprit, $bullet->getShootItem()->getId(), $headShot));
$this->addEvent(new KillEvent($playerDead, $playerCulprit, $itemId, $headShot));
$sound = new SoundEvent($playerDead->getPositionClone(), SoundType::PLAYER_DEAD);
$this->addSoundEvent($sound->setPlayer($playerDead));
}
Expand Down
20 changes: 20 additions & 0 deletions server/src/Core/Graph.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace cs\Core;

use GraphPHP\Graph\DiGraph;

final class Graph extends DiGraph
{

public function getNodesCount(): int
{
return count($this->nodes);
}

public function getEdgeCount(): int
{
return count($this->edges);
}

}
195 changes: 195 additions & 0 deletions server/src/Core/PathFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php

namespace cs\Core;

use GraphPHP\Edge\DirectedEdge;
use GraphPHP\Node\Node;
use SplQueue;

final class PathFinder
{
private Graph $graph;
/** @var array<string,bool> */
private array $visited = [];
/** @var array<int,array{int,int,int}> */
private readonly array $moves;
private readonly int $obstacleOvercomeHeight;
private int $iterationCount = 0;
public readonly int $tileSizeHalf;

public function __construct(private readonly World $world, public readonly int $tileSize, public readonly int $colliderHeight)
{
if ($this->tileSize < 3 || $tileSize % 2 !== 1) {
throw new GameException('Tile size should be odd and greater than 1.');
}

$this->tileSizeHalf = (int)ceil(($this->tileSize - 1) / 2);
$this->moves = [
90 => [+1, +0, +0],
0 => [+0, +0, +1],
270 => [-1, +0, +0],
180 => [+0, +0, -1],
];
$this->graph = new Graph();
$this->obstacleOvercomeHeight = Setting::playerObstacleOvercomeHeight();
}

protected function canFullyMoveTo(Point $candidate, int $angle, int $targetDistance, int $radius, int $height): bool
{
if ($angle % 90 !== 0) {
GameException::notImplementedYet();
}

$looseFloor = false;
for ($distance = 1; $distance <= $targetDistance; $distance++) {
$candidate->addPart(...$this->moves[$angle]);
if (!$this->canMoveTo($candidate, $angle, $radius)) {
return false;
}

if (!$looseFloor && !$this->world->findFloorSquare($candidate, $radius)) {
$looseFloor = true;
}
}

if (!$looseFloor) {
return true;
}

$fallCandidate = $candidate->clone();
foreach (range(1, 3 * $height) as $i) {
$fallCandidate->addY(-1);
if ($this->world->findFloorSquare($fallCandidate, $radius)) {
$candidate->setY($fallCandidate->y);
return true;
}
}

return false;
}

private function canMoveTo(Point $start, int $angle, int $radius): bool
{
$maxWallCeiling = $start->y + $this->obstacleOvercomeHeight;
$xWallMaxHeight = 0;
if ($angle === 90 || $angle === 270) {
$baseX = $start->clone()->addX(($angle === 90) ? $radius : -$radius);
$xWallMaxHeight = $this->world->findHighestWall($baseX, $this->colliderHeight, $radius, $maxWallCeiling, true);
}
$zWallMaxHeight = 0;
if ($angle === 0 || $angle === 180) {
$baseZ = $start->clone()->addZ(($angle === 0) ? $radius : -$radius);
$zWallMaxHeight = $this->world->findHighestWall($baseZ, $this->colliderHeight, $radius, $maxWallCeiling, false);
}
if ($xWallMaxHeight === 0 && $zWallMaxHeight === 0) { // no walls
return true;
}

// Try step over ONE low height wall
$highestWallCeiling = null;
if ($xWallMaxHeight === 0 && $zWallMaxHeight <= $maxWallCeiling) {
$highestWallCeiling = $zWallMaxHeight;
} elseif ($zWallMaxHeight === 0 && $xWallMaxHeight <= $maxWallCeiling) {
$highestWallCeiling = $xWallMaxHeight;
}
if ($highestWallCeiling === null) {
return false;
}

$floor = $this->world->findFloor($start->clone()->setY($highestWallCeiling), $radius);
if ($floor) {
$start->setY($floor->getY()); // side effect
return true;
}

return false;
}

public function tryFindClosestTile(Point $point): ?Point
{
$candidate = $point->clone();
foreach ($this->moves as $angle => $move) {
$candidate->setFrom($point);

if ($this->canFullyMoveTo($candidate, $angle, $this->tileSize, 0, $this->colliderHeight)) {
$this->convertToNavMeshNode($candidate);
if ($this->getGraph()->getNodeById($candidate->hash())) {
return $candidate;
}
}
}

return null;
}

public function convertToNavMeshNode(Point $point): float
{
if ($point->x < 1 || $point->z < 1) {
throw new GameException('World start from 1');
}

$fmodX = fmod($point->x, $this->tileSize);
$fmodZ = fmod($point->z, $this->tileSize);

$x = ((int)floor(($point->x + ($fmodX == 0 ? -1 : +0)) / $this->tileSize) * $this->tileSize) + 1 + $this->tileSizeHalf;
$point->setX($x);
$z = ((int)floor(($point->z + ($fmodZ == 0 ? -1 : +0)) / $this->tileSize) * $this->tileSize) + 1 + $this->tileSizeHalf;
$point->setZ($z);

return (abs($this->tileSizeHalf - $fmodX) + abs($this->tileSizeHalf - $fmodZ)) / 2;
}

public function buildNavigationMesh(Point $start, int $objectHeight): void
{
$startPoint = $start->clone();
$this->convertToNavMeshNode($startPoint);
if (!$this->world->findFloorSquare($startPoint, 1)) {
throw new GameException('No floor on start point');
}

/** @var SplQueue<Point> $queue */
$queue = new SplQueue();
$queue->enqueue($startPoint);
$candidate = new Point();
while (!$queue->isEmpty()) {
$current = $queue->dequeue();
$currentKey = $current->hash();
if (array_key_exists($currentKey, $this->visited)) {
continue;
}
$this->visited[$currentKey] = true;
$currentNodeOrNull = $this->graph->getNodeById($currentKey);
$currentNode = $currentNodeOrNull ?? new Node($currentKey, $current);

$hasNeighbour = false;
foreach ($this->moves as $angle => $move) {
$candidate->setFrom($current);
if (!$this->canFullyMoveTo($candidate, $angle, $this->tileSize, $this->tileSizeHalf, $objectHeight)) {
continue;
}

$hasNeighbour = true;
$newNeighbour = $candidate->clone();
$newNode = $this->graph->getNodeById($newNeighbour->hash());
if ($newNode === null) {
$newNode = new Node($newNeighbour->hash(), $newNeighbour);
$this->graph->addNode($newNode);
}
$this->graph->addEdge(new DirectedEdge($currentNode, $newNode, 1));
$queue->enqueue($newNeighbour);
}
if ($hasNeighbour && $currentNodeOrNull === null) {
$this->graph->addNode($currentNode);
}
if (++$this->iterationCount === 10_000) {
GameException::notImplementedYet('New map or bad test (no boundary box, bad starting point)?');
}
}
}

public function getGraph(): Graph
{
return $this->graph;
}

}
Loading

0 comments on commit 0312c3d

Please sign in to comment.