Skip to content

Commit 3b722f3

Browse files
starswaitforussolcloud
authored andcommitted
Stability++
1 parent dbc9b33 commit 3b722f3

41 files changed

Lines changed: 914 additions & 109 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ on:
1616
- 'www/'
1717

1818
jobs:
19-
composer-check:
19+
server-tests:
2020
runs-on: ubuntu-latest
2121
steps:
2222
- uses: actions/checkout@v4
@@ -43,7 +43,7 @@ jobs:
4343
- name: "Check code coverage min percentage"
4444
timeout-minutes: 5
4545
run: |
46-
echo '<?php preg_match("~Lines:\s+([\d.]+)%~", stream_get_contents(STDIN), $m);exit((int)((float)$m[1] < 99.0));' > cc.php
46+
echo '<?php preg_match("~Lines:\s+([\d.]+)%~", stream_get_contents(STDIN), $m);exit((int)((float)$m[1] < 99.87));' > cc.php
4747
export XDEBUG_MODE=coverage
4848
composer unit -- --stderr --no-progress --colors=never \
4949
--coverage-xml=www/coverage/coverage-xml --log-junit=www/coverage/junit.xml \
@@ -52,7 +52,7 @@ jobs:
5252
grep 'Lines: ' cc.txt | php -d error_reporting=E_ALL cc.php
5353
5454
- name: "Check infection mutation framework min percentage"
55-
timeout-minutes: 10
55+
timeout-minutes: 8
5656
run: |
5757
export XDEBUG_MODE=off
5858
grep '"timeout": 20,' infection.json5

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 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)
1+
# 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)
22

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

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"scripts": {
33
"stan": "php vendor/bin/phpstan --memory-limit=300M analyze",
44
"unit": "php vendor/bin/phpunit -d memory_limit=70M",
5-
"infection": "php -d memory_limit=180M vendor/bin/infection --only-covered --threads=6 --min-covered-msi=87",
5+
"infection": "php -d memory_limit=180M vendor/bin/infection --only-covered --threads=6 --min-covered-msi=99",
66
"infection-cache": "@infection --coverage=www/coverage/",
77
"dev": "php cli/server.php 1 8080 --debug & php cli/udp-ws-bridge.php",
88
"dev2": "php cli/server.php 2 8080 --debug & php cli/udp-ws-bridge.php & php cli/udp-ws-bridge.php 8082",

infection.json5

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,19 @@
3434
"@operator": false,
3535
"@regex": true,
3636
"@removal": true,
37+
"MatchArmRemoval": {
38+
"ignoreSourceCodeByRegex": [
39+
".+GameException::invalid\\(.+",
40+
],
41+
},
42+
"ArrayItemRemoval": {
43+
"ignore": [
44+
"cs\\Event\\*::serialize",
45+
],
46+
},
3747
"MethodCallRemoval": {
3848
"ignoreSourceCodeByRegex": [
49+
"\\$this->setActiveFloor\\(.+\\);",
3950
"\\$prevPos->setFrom\\(\\$candidate\\);",
4051
"\\$prevPos->setFrom\\(\\$newPos\\);",
4152
"\\$this->makeSound\\(.+\\);",
@@ -44,6 +55,7 @@
4455
"\\$soundEvent->setSurface\\(.+\\);",
4556
"\\$soundEvent->addExtra\\(.+\\);",
4657
"\\$this->addSoundEvent\\(.+\\);",
58+
"\\$bullet->addPlayerIdSkip\\(\\$playerId\\);",
4759
]
4860
},
4961
"@return_value": true,

server/src/Core/Game.php

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,15 @@ public function tick(int $tickId): ?GameOverEvent
108108
}
109109
}
110110
$this->backtrack->finishState();
111-
$this->checkRoundEnd($alivePlayers[0], $alivePlayers[1]);
111+
if (!$this->roundEndCoolDown) {
112+
$this->checkRoundEnd($alivePlayers[0], $alivePlayers[1]);
113+
}
112114
$this->processEvents($tickId);
113115
return null;
114116
}
115117

116118
private function checkRoundEnd(int $defendersAlive, int $attackersAlive): void
117119
{
118-
if ($this->roundEndCoolDown) {
119-
return;
120-
}
121-
122120
if ($this->playersCountAttackers > 0 && $attackersAlive === 0) {
123121
$this->roundEnd(false, RoundEndReason::ALL_ENEMIES_ELIMINATED);
124122
return;
@@ -372,10 +370,6 @@ public function bombPlanted(Player $planter): void
372370

373371
public function roundEnd(bool $attackersWins, RoundEndReason $reason): void
374372
{
375-
if ($this->roundEndCoolDown) {
376-
return;
377-
}
378-
379373
$this->roundEndCoolDown = true;
380374
$roundEndEvent = new RoundEndEvent($this, $attackersWins, $reason);
381375
$roundEndEvent->onComplete[] = fn() => $this->endRound($roundEndEvent);
@@ -467,8 +461,7 @@ private function calculateRoundMoneyAward(RoundEndEvent $roundEndEvent, Player $
467461
$amount += match ($roundEndEvent->reason) {
468462
RoundEndReason::ALL_ENEMIES_ELIMINATED => 3250,
469463
RoundEndReason::BOMB_EXPLODED => 3500,
470-
RoundEndReason::TIME_RUNS_OUT,
471-
RoundEndReason::BOMB_DEFUSED => throw new GameException('Invalid? ' . $roundEndEvent->reason->value), // @codeCoverageIgnore
464+
RoundEndReason::TIME_RUNS_OUT, RoundEndReason::BOMB_DEFUSED => GameException::invalid((string)$roundEndEvent->reason->value), // @codeCoverageIgnore
472465
};
473466
} elseif (!$player->isAlive()) {
474467
$amount += $this->score->getMoneyLossBonus(true);
@@ -482,7 +475,7 @@ private function calculateRoundMoneyAward(RoundEndEvent $roundEndEvent, Player $
482475
$amount += match ($roundEndEvent->reason) {
483476
RoundEndReason::ALL_ENEMIES_ELIMINATED, RoundEndReason::TIME_RUNS_OUT => 3250,
484477
RoundEndReason::BOMB_DEFUSED => 3500,
485-
RoundEndReason::BOMB_EXPLODED => throw new GameException('Invalid? ' . $roundEndEvent->reason->value), // @codeCoverageIgnore
478+
RoundEndReason::BOMB_EXPLODED => GameException::invalid((string)$roundEndEvent->reason->value), // @codeCoverageIgnore
486479
};
487480
} else {
488481
$amount += $this->score->getMoneyLossBonus(false);

server/src/Core/GameException.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ public static function notImplementedYet(string $msg = ''): never
1515
throw new self("Not implemented yet! " . $msg);
1616
}
1717

18+
public static function invalid(string $msg = ''): never
19+
{
20+
throw new self("This should not be called! " . $msg);
21+
}
22+
1823
}

server/src/Core/Inventory.php

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ public function reset(bool $isAttackerSide, bool $respawn): void
3939
];
4040
$this->equippedSlot = InventorySlot::SLOT_SECONDARY->value;
4141
$this->lastEquippedSlotId = InventorySlot::SLOT_KNIFE->value;
42-
$this->lastEquippedGrenadeSlots = [
43-
InventorySlot::SLOT_GRENADE_SMOKE->value, InventorySlot::SLOT_GRENADE_MOLOTOV->value, InventorySlot::SLOT_GRENADE_HE->value,
44-
InventorySlot::SLOT_GRENADE_FLASH->value, InventorySlot::SLOT_GRENADE_DECOY->value,
45-
];
42+
$this->lastEquippedGrenadeSlots = InventorySlot::getGrenadeSlotIds();
4643
} else {
4744
foreach ($this->items as $item) {
4845
$item->reset();
@@ -52,12 +49,15 @@ public function reset(bool $isAttackerSide, bool $respawn): void
5249
}
5350
}
5451

55-
$this->removeBomb();
52+
if ($this->has(InventorySlot::SLOT_BOMB->value)) {
53+
$this->removeBomb();
54+
}
5655
$this->store->reset($isAttackerSide, $this->items);
5756
}
5857

59-
private function updateEquippedSlot(): int
58+
private function updateEquippedSlot(Item $item): int
6059
{
60+
$this->tryRemoveLastEquippedGrenade($item);
6161
if (isset($this->items[$this->equippedSlot])) {
6262
return $this->equippedSlot;
6363
}
@@ -72,15 +72,29 @@ private function updateEquippedSlot(): int
7272

7373
public function removeBomb(): InventorySlot
7474
{
75-
unset($this->items[InventorySlot::SLOT_BOMB->value]);
76-
return InventorySlot::from($this->updateEquippedSlot());
75+
$bomb = $this->items[InventorySlot::SLOT_BOMB->value] ?? null;
76+
if ($bomb) {
77+
unset($this->items[InventorySlot::SLOT_BOMB->value]);
78+
return InventorySlot::from($this->updateEquippedSlot($bomb));
79+
}
80+
81+
GameException::invalid('You do not have bomb!'); // @codeCoverageIgnore
7782
}
7883

7984
public function getEquipped(): Item
8085
{
8186
return $this->items[$this->equippedSlot];
8287
}
8388

89+
private function tryRemoveLastEquippedGrenade(Item $item): void
90+
{
91+
if ($item instanceof Grenade) {
92+
$index = array_search($item->getSlot()->value, $this->lastEquippedGrenadeSlots, true);
93+
assert(is_int($index));
94+
unset($this->lastEquippedGrenadeSlots[$index]);
95+
}
96+
}
97+
8498
public function removeEquipped(): ?Item
8599
{
86100
if (!$this->getEquipped()->isUserDroppable()) {
@@ -90,10 +104,7 @@ public function removeEquipped(): ?Item
90104
$item = $this->items[$this->equippedSlot];
91105
if ($item->getQuantity() === 1) {
92106
unset($this->items[$this->equippedSlot]);
93-
if ($item instanceof Grenade) {
94-
unset($this->lastEquippedGrenadeSlots[$this->equippedSlot]);
95-
}
96-
$this->updateEquippedSlot();
107+
$this->updateEquippedSlot($item);
97108
$item->unEquip();
98109

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

118129
unset($this->items[$slot]);
119-
if ($item instanceof Grenade) {
120-
unset($this->lastEquippedGrenadeSlots[$slot]);
121-
}
122-
$this->updateEquippedSlot();
130+
$this->updateEquippedSlot($item);
123131
}
124132

125133
public function canBuy(Item $item): bool
@@ -176,7 +184,7 @@ public function equip(InventorySlot $slot): ?EquipEvent
176184
$this->lastEquippedSlotId = $this->equippedSlot;
177185
$this->equippedSlot = $slot->value;
178186
if ($item instanceof Grenade) {
179-
unset($this->lastEquippedGrenadeSlots[$slot->value]);
187+
$this->tryRemoveLastEquippedGrenade($item);
180188
array_unshift($this->lastEquippedGrenadeSlots, $slot->value);
181189
}
182190
return $item->equip();

server/src/Core/Item.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public function canPurchaseMultipleTime(self $newSlotItem): bool
123123
{
124124
return match ($this->getType()) {
125125
ItemType::TYPE_WEAPON_PRIMARY, ItemType::TYPE_WEAPON_SECONDARY => true,
126-
default => GameException::notImplementedYet('New item? ' . get_class($this)) // @codeCoverageIgnore
126+
default => GameException::invalid('New item? ' . get_class($this)) // @codeCoverageIgnore
127127
};
128128
}
129129

server/src/Core/Plane.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function getHitAntiForce(Point $point): int
4242
$hit = $point->to2D($this->axis2d);
4343
if ($hit->x < $this->point2DStart->x || $hit->x > $this->point2DEnd->x
4444
|| $hit->y < $this->point2DStart->y || $hit->y > $this->point2DEnd->y) {
45-
throw new GameException("Hit '{$hit}' out of plane boundary '{$this}'");
45+
throw new GameException("Hit '{$hit}' ({$point}) out of plane boundary '{$this}'");
4646
}
4747

4848
$margin = $this->wallBangEdgeMarginDistance;

server/src/Core/Score.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public function getPlayerStat(int $playerId): PlayerStat
146146
}
147147

148148
/**
149-
* @return array<mixed>
149+
* @return array<string,mixed>
150150
*/
151151
public function toArray(): array
152152
{

0 commit comments

Comments
 (0)