Skip to content

Commit

Permalink
Stability++
Browse files Browse the repository at this point in the history
  • Loading branch information
starswaitforus authored and solcloud committed Sep 22, 2024
1 parent dbc9b33 commit 3b722f3
Show file tree
Hide file tree
Showing 41 changed files with 914 additions and 109 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
- 'www/'

jobs:
composer-check:
server-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -43,7 +43,7 @@ jobs:
- name: "Check code coverage min percentage"
timeout-minutes: 5
run: |
echo '<?php preg_match("~Lines:\s+([\d.]+)%~", stream_get_contents(STDIN), $m);exit((int)((float)$m[1] < 99.0));' > cc.php
echo '<?php preg_match("~Lines:\s+([\d.]+)%~", stream_get_contents(STDIN), $m);exit((int)((float)$m[1] < 99.87));' > cc.php
export XDEBUG_MODE=coverage
composer unit -- --stderr --no-progress --colors=never \
--coverage-xml=www/coverage/coverage-xml --log-junit=www/coverage/junit.xml \
Expand All @@ -52,7 +52,7 @@ jobs:
grep 'Lines: ' cc.txt | php -d error_reporting=E_ALL cc.php
- name: "Check infection mutation framework min percentage"
timeout-minutes: 10
timeout-minutes: 8
run: |
export XDEBUG_MODE=off
grep '"timeout": 20,' infection.json5
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Counter-Strike: Football [![Tests](https://github.com/solcloud/Counter-Strike/actions/workflows/test.yml/badge.svg)](https://github.com/solcloud/Counter-Strike/actions/workflows/test.yml)
# Counter-Strike: Football [![Tests](https://github.com/solcloud/Counter-Strike/actions/workflows/test.yml/badge.svg)](https://github.com/solcloud/Counter-Strike/actions/workflows/test.yml) [![Code coverage](https://img.shields.io/badge/Code%20coverage-100%25-green?style=flat)](https://github.com/solcloud/Counter-Strike/actions/workflows/test.yml)

Competitive multiplayer FPS game where two football fan teams fight with the goal of winning more rounds than the opponent team.

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"scripts": {
"stan": "php vendor/bin/phpstan --memory-limit=300M analyze",
"unit": "php vendor/bin/phpunit -d memory_limit=70M",
"infection": "php -d memory_limit=180M vendor/bin/infection --only-covered --threads=6 --min-covered-msi=87",
"infection": "php -d memory_limit=180M vendor/bin/infection --only-covered --threads=6 --min-covered-msi=99",
"infection-cache": "@infection --coverage=www/coverage/",
"dev": "php cli/server.php 1 8080 --debug & php cli/udp-ws-bridge.php",
"dev2": "php cli/server.php 2 8080 --debug & php cli/udp-ws-bridge.php & php cli/udp-ws-bridge.php 8082",
Expand Down
12 changes: 12 additions & 0 deletions infection.json5
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,19 @@
"@operator": false,
"@regex": true,
"@removal": true,
"MatchArmRemoval": {
"ignoreSourceCodeByRegex": [
".+GameException::invalid\\(.+",
],
},
"ArrayItemRemoval": {
"ignore": [
"cs\\Event\\*::serialize",
],
},
"MethodCallRemoval": {
"ignoreSourceCodeByRegex": [
"\\$this->setActiveFloor\\(.+\\);",
"\\$prevPos->setFrom\\(\\$candidate\\);",
"\\$prevPos->setFrom\\(\\$newPos\\);",
"\\$this->makeSound\\(.+\\);",
Expand All @@ -44,6 +55,7 @@
"\\$soundEvent->setSurface\\(.+\\);",
"\\$soundEvent->addExtra\\(.+\\);",
"\\$this->addSoundEvent\\(.+\\);",
"\\$bullet->addPlayerIdSkip\\(\\$playerId\\);",
]
},
"@return_value": true,
Expand Down
17 changes: 5 additions & 12 deletions server/src/Core/Game.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,15 @@ public function tick(int $tickId): ?GameOverEvent
}
}
$this->backtrack->finishState();
$this->checkRoundEnd($alivePlayers[0], $alivePlayers[1]);
if (!$this->roundEndCoolDown) {
$this->checkRoundEnd($alivePlayers[0], $alivePlayers[1]);
}
$this->processEvents($tickId);
return null;
}

private function checkRoundEnd(int $defendersAlive, int $attackersAlive): void
{
if ($this->roundEndCoolDown) {
return;
}

if ($this->playersCountAttackers > 0 && $attackersAlive === 0) {
$this->roundEnd(false, RoundEndReason::ALL_ENEMIES_ELIMINATED);
return;
Expand Down Expand Up @@ -372,10 +370,6 @@ public function bombPlanted(Player $planter): void

public function roundEnd(bool $attackersWins, RoundEndReason $reason): void
{
if ($this->roundEndCoolDown) {
return;
}

$this->roundEndCoolDown = true;
$roundEndEvent = new RoundEndEvent($this, $attackersWins, $reason);
$roundEndEvent->onComplete[] = fn() => $this->endRound($roundEndEvent);
Expand Down Expand Up @@ -467,8 +461,7 @@ private function calculateRoundMoneyAward(RoundEndEvent $roundEndEvent, Player $
$amount += match ($roundEndEvent->reason) {
RoundEndReason::ALL_ENEMIES_ELIMINATED => 3250,
RoundEndReason::BOMB_EXPLODED => 3500,
RoundEndReason::TIME_RUNS_OUT,
RoundEndReason::BOMB_DEFUSED => throw new GameException('Invalid? ' . $roundEndEvent->reason->value), // @codeCoverageIgnore
RoundEndReason::TIME_RUNS_OUT, RoundEndReason::BOMB_DEFUSED => GameException::invalid((string)$roundEndEvent->reason->value), // @codeCoverageIgnore
};
} elseif (!$player->isAlive()) {
$amount += $this->score->getMoneyLossBonus(true);
Expand All @@ -482,7 +475,7 @@ private function calculateRoundMoneyAward(RoundEndEvent $roundEndEvent, Player $
$amount += match ($roundEndEvent->reason) {
RoundEndReason::ALL_ENEMIES_ELIMINATED, RoundEndReason::TIME_RUNS_OUT => 3250,
RoundEndReason::BOMB_DEFUSED => 3500,
RoundEndReason::BOMB_EXPLODED => throw new GameException('Invalid? ' . $roundEndEvent->reason->value), // @codeCoverageIgnore
RoundEndReason::BOMB_EXPLODED => GameException::invalid((string)$roundEndEvent->reason->value), // @codeCoverageIgnore
};
} else {
$amount += $this->score->getMoneyLossBonus(false);
Expand Down
5 changes: 5 additions & 0 deletions server/src/Core/GameException.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ public static function notImplementedYet(string $msg = ''): never
throw new self("Not implemented yet! " . $msg);
}

public static function invalid(string $msg = ''): never
{
throw new self("This should not be called! " . $msg);
}

}
42 changes: 25 additions & 17 deletions server/src/Core/Inventory.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ public function reset(bool $isAttackerSide, bool $respawn): void
];
$this->equippedSlot = InventorySlot::SLOT_SECONDARY->value;
$this->lastEquippedSlotId = InventorySlot::SLOT_KNIFE->value;
$this->lastEquippedGrenadeSlots = [
InventorySlot::SLOT_GRENADE_SMOKE->value, InventorySlot::SLOT_GRENADE_MOLOTOV->value, InventorySlot::SLOT_GRENADE_HE->value,
InventorySlot::SLOT_GRENADE_FLASH->value, InventorySlot::SLOT_GRENADE_DECOY->value,
];
$this->lastEquippedGrenadeSlots = InventorySlot::getGrenadeSlotIds();
} else {
foreach ($this->items as $item) {
$item->reset();
Expand All @@ -52,12 +49,15 @@ public function reset(bool $isAttackerSide, bool $respawn): void
}
}

$this->removeBomb();
if ($this->has(InventorySlot::SLOT_BOMB->value)) {
$this->removeBomb();
}
$this->store->reset($isAttackerSide, $this->items);
}

private function updateEquippedSlot(): int
private function updateEquippedSlot(Item $item): int
{
$this->tryRemoveLastEquippedGrenade($item);
if (isset($this->items[$this->equippedSlot])) {
return $this->equippedSlot;
}
Expand All @@ -72,15 +72,29 @@ private function updateEquippedSlot(): int

public function removeBomb(): InventorySlot
{
unset($this->items[InventorySlot::SLOT_BOMB->value]);
return InventorySlot::from($this->updateEquippedSlot());
$bomb = $this->items[InventorySlot::SLOT_BOMB->value] ?? null;
if ($bomb) {
unset($this->items[InventorySlot::SLOT_BOMB->value]);
return InventorySlot::from($this->updateEquippedSlot($bomb));
}

GameException::invalid('You do not have bomb!'); // @codeCoverageIgnore
}

public function getEquipped(): Item
{
return $this->items[$this->equippedSlot];
}

private function tryRemoveLastEquippedGrenade(Item $item): void
{
if ($item instanceof Grenade) {
$index = array_search($item->getSlot()->value, $this->lastEquippedGrenadeSlots, true);
assert(is_int($index));
unset($this->lastEquippedGrenadeSlots[$index]);
}
}

public function removeEquipped(): ?Item
{
if (!$this->getEquipped()->isUserDroppable()) {
Expand All @@ -90,10 +104,7 @@ public function removeEquipped(): ?Item
$item = $this->items[$this->equippedSlot];
if ($item->getQuantity() === 1) {
unset($this->items[$this->equippedSlot]);
if ($item instanceof Grenade) {
unset($this->lastEquippedGrenadeSlots[$this->equippedSlot]);
}
$this->updateEquippedSlot();
$this->updateEquippedSlot($item);
$item->unEquip();

return $item;
Expand All @@ -116,10 +127,7 @@ public function removeSlot(int $slot): void
}

unset($this->items[$slot]);
if ($item instanceof Grenade) {
unset($this->lastEquippedGrenadeSlots[$slot]);
}
$this->updateEquippedSlot();
$this->updateEquippedSlot($item);
}

public function canBuy(Item $item): bool
Expand Down Expand Up @@ -176,7 +184,7 @@ public function equip(InventorySlot $slot): ?EquipEvent
$this->lastEquippedSlotId = $this->equippedSlot;
$this->equippedSlot = $slot->value;
if ($item instanceof Grenade) {
unset($this->lastEquippedGrenadeSlots[$slot->value]);
$this->tryRemoveLastEquippedGrenade($item);
array_unshift($this->lastEquippedGrenadeSlots, $slot->value);
}
return $item->equip();
Expand Down
2 changes: 1 addition & 1 deletion server/src/Core/Item.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public function canPurchaseMultipleTime(self $newSlotItem): bool
{
return match ($this->getType()) {
ItemType::TYPE_WEAPON_PRIMARY, ItemType::TYPE_WEAPON_SECONDARY => true,
default => GameException::notImplementedYet('New item? ' . get_class($this)) // @codeCoverageIgnore
default => GameException::invalid('New item? ' . get_class($this)) // @codeCoverageIgnore
};
}

Expand Down
2 changes: 1 addition & 1 deletion server/src/Core/Plane.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function getHitAntiForce(Point $point): int
$hit = $point->to2D($this->axis2d);
if ($hit->x < $this->point2DStart->x || $hit->x > $this->point2DEnd->x
|| $hit->y < $this->point2DStart->y || $hit->y > $this->point2DEnd->y) {
throw new GameException("Hit '{$hit}' out of plane boundary '{$this}'");
throw new GameException("Hit '{$hit}' ({$point}) out of plane boundary '{$this}'");
}

$margin = $this->wallBangEdgeMarginDistance;
Expand Down
2 changes: 1 addition & 1 deletion server/src/Core/Score.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public function getPlayerStat(int $playerId): PlayerStat
}

/**
* @return array<mixed>
* @return array<string,mixed>
*/
public function toArray(): array
{
Expand Down
38 changes: 18 additions & 20 deletions server/src/Core/World.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ final class World
private Bomb $bomb;
private int $lastBombActionTick = -1;
private int $lastBombPlayerId = -1;
private int $bombActionTickBuffer = 1;
private int $playerPotentialDistanceSquared;
private ?PathFinder $grenadeNavMesh = null;

Expand Down Expand Up @@ -314,20 +315,21 @@ public function playerUse(Player $player): void
&& $this->canBeSeen($player, $this->bomb->getPosition(), self::BOMB_RADIUS, self::BOMB_DEFUSE_MAX_DISTANCE)
) {
$bomb = $this->bomb;
if ($this->lastBombActionTick + Util::millisecondsToFrames(50) < $this->getTickId()) {
$bomb->reset();
$tickId = $this->getTickId();
$playerId = $player->getId();
if ($playerId !== $this->lastBombPlayerId || $this->lastBombActionTick + $this->bombActionTickBuffer < $tickId) {
$player->stop();
$bomb->startDefusing($tickId, $player->hasDefuseKit());
$soundEvent = new SoundEvent($player->getPositionClone()->addY(10), SoundType::BOMB_DEFUSING);
$this->makeSound($soundEvent->setPlayer($player)->setItem($bomb));
}
$this->lastBombActionTick = $this->getTickId();
$this->lastBombPlayerId = $player->getId();
$this->lastBombActionTick = $tickId;
$this->lastBombPlayerId = $playerId;

$defused = $this->bomb->defuse($player->hasDefuseKit());
if ($defused) {
$this->game->bombDefused($player);
if ($bomb->isDefused($tickId)) {
$this->lastBombActionTick = -1;
$this->lastBombPlayerId = -1;
$this->game->bombDefused($player);
}
return;
}
Expand Down Expand Up @@ -827,25 +829,24 @@ private function playerHit(Point $hitPoint, Player $playerHit, Player $playerCul
}
}

public function tryPlantBomb(Player $player): void
public function tryPlantBomb(Player $player, Bomb $bomb): void
{
if (!$this->canPlant($player)) {
return;
}

/** @var Bomb $bomb */
$bomb = $player->getEquippedItem();
if ($this->lastBombActionTick + Util::millisecondsToFrames(200) < $this->getTickId()) {
$bomb->reset();
$tickId = $this->getTickId();
$playerId = $player->getId();
if ($playerId !== $this->lastBombPlayerId || $this->lastBombActionTick + $this->bombActionTickBuffer < $tickId) {
$player->stop();
$bomb->startPlanting($tickId);
$soundEvent = new SoundEvent($player->getPositionClone()->addY(10), SoundType::BOMB_PLANTING);
$this->makeSound($soundEvent->setPlayer($player)->setItem($bomb));
}
$this->lastBombActionTick = $this->getTickId();
$this->lastBombPlayerId = $player->getId();
$this->lastBombPlayerId = $playerId;

$planted = $bomb->plant();
if ($planted) {
if ($bomb->isPlanted($tickId)) {
$player->equip($player->getInventory()->removeBomb());
$bomb->setPosition($player->getPositionClone());
$this->game->bombPlanted($player);
Expand All @@ -858,10 +859,7 @@ public function tryPlantBomb(Player $player): void

public function isPlantingOrDefusing(Player $player): bool
{
return (
$this->lastBombPlayerId === $player->getId() &&
($this->lastBombActionTick === $this->getTickId() || $this->lastBombActionTick + 1 === $this->getTickId())
);
return ($this->lastBombPlayerId === $player->getId() && $this->bombActionTickBuffer >= $this->getTickId() - $this->lastBombActionTick);
}

public function isWallOrFloorCollision(Point $start, Point $candidate, int $radius): bool
Expand Down Expand Up @@ -896,7 +894,7 @@ public function isCollisionWithOtherPlayers(int $playerIdSkip, Point $point, int
}

if ($collider->collide($point, $radius, $height)) {
return $this->game->getPlayer($collider->playerId);
return $collider->getPlayer();
}
}

Expand Down
12 changes: 12 additions & 0 deletions server/src/Enum/InventorySlot.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,16 @@ enum InventorySlot: int
case SLOT_KEVLAR = 10;
case SLOT_KIT = 11;

/** @return list<int> */
public static function getGrenadeSlotIds(): array
{
return [
self::SLOT_GRENADE_SMOKE->value,
self::SLOT_GRENADE_MOLOTOV->value,
self::SLOT_GRENADE_HE->value,
self::SLOT_GRENADE_FLASH->value,
self::SLOT_GRENADE_DECOY->value,
];
}

}
Loading

0 comments on commit 3b722f3

Please sign in to comment.