diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b862e61..db5f6bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,7 @@ jobs: run: | sudo apt-get update && sudo apt-get install -y \ composer + sudo sh -c 'echo "zend.assertions = 1" >> /etc/php/8.1/cli/php.ini' - name: "Install Composer dependencies" timeout-minutes: 1 diff --git a/composer.json b/composer.json index e7d7564..ad60f2b 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,10 @@ "@composer dumpautoload --optimize --classmap-authoritative", "@stan", "@unit" + ], + "checkfull": [ + "@check", + "@infection" ] }, "require": { diff --git a/infection.json5 b/infection.json5 index f618b55..36ba7a8 100644 --- a/infection.json5 +++ b/infection.json5 @@ -17,7 +17,6 @@ "@default": true, "@conditional_boundary": false, "@conditional_negotiation": false, - "Break_": false, "CastInt": false, "Continue_": false, "DecrementInteger": false, @@ -25,20 +24,14 @@ "IfNegation": false, "Increment": false, "IncrementInteger": false, - "InstanceOf_": false, - "IntegerNegation": false, "LogicalAnd": false, "LogicalAndAllSubExprNegation": false, - "LogicalAndNegation": false, - "LogicalAndSingleSubExprNegation": false, - "LogicalOrAllSubExprNegation": false, "LogicalOr": false, + "LogicalOrAllSubExprNegation": false, "Minus": false, "Modulus": false, - "MulEqual": false, "Multiplication": false, "Plus": false, - "PlusEqual": false, "RoundingFamily": false, "TrueValue": false, "ArrayItem": { @@ -51,6 +44,11 @@ "cs\\Event\\*::serialize", ], }, + "Break_": { + "ignore": [ + "cs\\Traits\\Player\\JumpTrait::jump", + ], + }, "Coalesce": { "ignoreSourceCodeByRegex": [ ".+\\(\\$skipPlayerIds\\[\\$playerId\\].+", @@ -58,10 +56,20 @@ ], }, "Division": { - ignoreSourceCodeByRegex: [ + "ignoreSourceCodeByRegex": [ ".+rand\\(.+", ], }, + "InstanceOf_": { + "ignoreSourceCodeByRegex": [ + "if\\s*\\(\\$this->ball->getResolutionAngleVertical\\(\\) > 0 && \\(.+", + ], + }, + "LogicalAndNegation": { + "ignoreSourceCodeByRegex": [ + "if\\s*\\(\\$count > 10 && \\$count % 2 === 0\\)\\s\\{", + ], + }, "MatchArmRemoval": { "ignoreSourceCodeByRegex": [ ".+GameException::invalid\\(.+", @@ -80,13 +88,13 @@ "\\$this->addSoundEvent\\(.+\\);", "\\$bullet->addPlayerIdSkip\\(\\$playerId\\);", "\\$this->convertToNavMeshNode\\(\\$navmesh\\);", - ] + ], }, "Ternary": { "ignore": [ "cs\\Core\\Player::serialize", ], - ignoreSourceCodeByRegex: [ + "ignoreSourceCodeByRegex": [ ".+rand\\(.+", ], }, diff --git a/phpstan.neon b/phpstan.neon index d32e502..e6f2d6a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,3 +8,4 @@ parameters: - server/src/ - test/ level: max + checkMissingCallableSignature: true diff --git a/server/src/Core/Bullet.php b/server/src/Core/Bullet.php index a7d1497..a2e711d 100644 --- a/server/src/Core/Bullet.php +++ b/server/src/Core/Bullet.php @@ -47,7 +47,8 @@ public function getOrigin(): Point public function lowerDamage(int $amount): void { - $this->damage -= abs($amount); + assert($amount >= 0); + $this->damage -= $amount; } public function getDamage(): int diff --git a/server/src/Core/Collision.php b/server/src/Core/Collision.php index 0bf5cae..42565fd 100644 --- a/server/src/Core/Collision.php +++ b/server/src/Core/Collision.php @@ -179,6 +179,10 @@ public static function pointWithBox(Point $point, Box $box): bool public static function pointWithBoxBoundary(Point $point, Point $boxMin, Point $boxMax): bool { + assert($boxMin->x <= $boxMax->x); + assert($boxMin->y <= $boxMax->y); + assert($boxMin->z <= $boxMax->z); + if ($point->y > $boxMax->y || $point->y < $boxMin->y) { return false; } @@ -194,20 +198,24 @@ public static function pointWithBoxBoundary(Point $point, Point $boxMin, Point $ public static function boxWithBox(Point $boundaryAMin, Point $boundaryAMax, Point $boundaryBMin, Point $boundaryBMax): bool { + assert($boundaryAMin->x <= $boundaryAMax->x); + assert($boundaryAMin->y <= $boundaryAMax->y); + assert($boundaryAMin->z <= $boundaryAMax->z); + + assert($boundaryBMin->x <= $boundaryBMax->x); + assert($boundaryBMin->y <= $boundaryBMax->y); + assert($boundaryBMin->z <= $boundaryBMax->z); + + if ($boundaryAMin->x > $boundaryBMax->x || $boundaryBMin->x > $boundaryAMax->x) { + return false; + } if ($boundaryAMin->y > $boundaryBMax->y || $boundaryBMin->y > $boundaryAMax->y) { return false; } - - if ( - $boundaryAMax->x >= $boundaryBMin->x - && $boundaryAMin->x <= $boundaryBMax->x - && $boundaryAMax->z >= $boundaryBMin->z - && $boundaryAMin->z <= $boundaryBMax->z - ) { - return true; + if ($boundaryAMin->z > $boundaryBMax->z || $boundaryBMin->z > $boundaryAMax->z) { + return false; } - - return false; + return true; } } diff --git a/server/src/Core/Game.php b/server/src/Core/Game.php index b3940b0..75ce422 100644 --- a/server/src/Core/Game.php +++ b/server/src/Core/Game.php @@ -42,7 +42,7 @@ class Game private array $players = []; /** @var Event[] */ private array $events = []; - /** @var Event[] */ + /** @var list */ private array $tickEvents = []; protected int $tick = 0; @@ -228,7 +228,7 @@ public function getWorld(): World } /** - * @return Event[] + * @return list */ public function consumeTickEvents(): array { diff --git a/server/src/Core/PathFinder.php b/server/src/Core/PathFinder.php index 65eb497..f22579a 100644 --- a/server/src/Core/PathFinder.php +++ b/server/src/Core/PathFinder.php @@ -146,7 +146,7 @@ public function findTile(Point $pointOnFloor, int $radius): Point for ($distance = 1; $distance <= $maxDistance; $distance++) { $candidate->addPart(...$this->moves[$angle]); - if (!$this->canFullyMoveTo($candidate, $angle, 1, $radius, $this->colliderHeight)) { + if (!$this->canFullyMoveTo($candidate, $angle, 1, $radius, $this->colliderHeight)) { // @infection-ignore-all break; } diff --git a/server/src/Core/Player.php b/server/src/Core/Player.php index d75f249..dff3eb9 100644 --- a/server/src/Core/Player.php +++ b/server/src/Core/Player.php @@ -212,8 +212,13 @@ public function lowerArmor(int $armorDamage): void public function lowerHealth(int $healthDamage): void { + assert($healthDamage >= 0); + if ($healthDamage <= 0) { + return; + } + $this->addEvent(new TimeoutEvent(null, 70), $this->eventIdShotSlowdown); - $this->health -= abs($healthDamage); + $this->health -= $healthDamage; if ($this->health <= 0) { $this->health = 0; $this->onPlayerDied(); diff --git a/server/src/Core/Setting.php b/server/src/Core/Setting.php index 9c63d37..6c01668 100644 --- a/server/src/Core/Setting.php +++ b/server/src/Core/Setting.php @@ -17,7 +17,7 @@ final class Setting 'flyingMovementSpeedMultiplier' => 0.8, 'throwSpeed' => 40, - 'playerVelocity' => 0, + 'playerVelocity' => 100, 'playerHeadRadius' => 10, 'playerBoundingRadius' => 60, 'playerJumpHeight' => 150, @@ -52,6 +52,8 @@ public static function loadConstants(array $constants): void */ private static function fixBackwardCompatible(array &$constants): void { + // BC code + $constants['playerVelocity'] = ($constants['playerVelocity'] ?? 0); foreach (self::defaultConstant as $key => $defaultValue) { if (isset($constants[$key])) { continue; diff --git a/server/src/Core/World.php b/server/src/Core/World.php index a41b246..2e32fbc 100644 --- a/server/src/Core/World.php +++ b/server/src/Core/World.php @@ -719,9 +719,7 @@ public function playerDiedToFallDamage(Player $playerDead): void private function playerDiedToFlame(Player $playerCulprit, Player $playerDead, Flammable $item): void { - if (false === ($item instanceof Grenade)) { - throw new GameException("New flammable non grenade type?"); // @codeCoverageIgnore - } + assert($item instanceof Grenade, "New flammable non grenade type?"); $this->game->playerGrenadeKilledEvent($playerCulprit, $playerDead, $item); } diff --git a/server/src/Equipment/Kevlar.php b/server/src/Equipment/Kevlar.php index 76220cb..bfbe859 100644 --- a/server/src/Equipment/Kevlar.php +++ b/server/src/Equipment/Kevlar.php @@ -31,7 +31,8 @@ public function repairArmor(): void public function lowerArmor(int $armorDamage): void { - $this->armor -= abs($armorDamage); + assert($armorDamage >= 0); + $this->armor -= $armorDamage; if ($this->armor <= 0) { $this->armor = 0; $this->type = ArmorType::NONE; diff --git a/server/src/Event/CallbackEvent.php b/server/src/Event/CallbackEvent.php index 2c910e9..fd6524d 100644 --- a/server/src/Event/CallbackEvent.php +++ b/server/src/Event/CallbackEvent.php @@ -6,7 +6,8 @@ class CallbackEvent extends Event { - public function __construct(private Closure $callback) + /** @param Closure(static,int):void $callback */ + public function __construct(private readonly Closure $callback) { } diff --git a/server/src/Event/CrouchEvent.php b/server/src/Event/CrouchEvent.php index 571a36b..a08d325 100644 --- a/server/src/Event/CrouchEvent.php +++ b/server/src/Event/CrouchEvent.php @@ -9,6 +9,7 @@ final class CrouchEvent extends TickEvent { public readonly int $moveOffset; + /** @param Closure(static,int):void $callback */ public function __construct(public bool $directionDown, Closure $callback) { parent::__construct($callback, Setting::tickCountCrouch()); diff --git a/server/src/Event/Event.php b/server/src/Event/Event.php index f0f56db..275efd3 100644 --- a/server/src/Event/Event.php +++ b/server/src/Event/Event.php @@ -10,7 +10,7 @@ abstract class Event implements NetSerializable { protected int $tickCount = 0; public int $customId = 0; - /** @var Closure[] function(Event $event):void{} */ + /** @var list function(Event $event):void{} */ public array $onComplete = []; abstract public function process(int $tick): void; diff --git a/server/src/Event/GameOverEvent.php b/server/src/Event/GameOverEvent.php index cacb14c..24d957e 100644 --- a/server/src/Event/GameOverEvent.php +++ b/server/src/Event/GameOverEvent.php @@ -4,7 +4,7 @@ use cs\Enum\GameOverReason; -final class GameOverEvent extends TickEvent +final class GameOverEvent extends NoTickEvent { public function __construct(public readonly GameOverReason $reason) diff --git a/server/src/Event/GameStartEvent.php b/server/src/Event/GameStartEvent.php index 2125e22..16bda2e 100644 --- a/server/src/Event/GameStartEvent.php +++ b/server/src/Event/GameStartEvent.php @@ -6,13 +6,13 @@ use cs\Core\Player; use cs\Net\ServerSetting; -final class GameStartEvent extends TickEvent +final class GameStartEvent extends NoTickEvent { public function __construct( - private Player $player, - private ServerSetting $setting, - private GameProperty $gameSetting, + private readonly Player $player, + private readonly ServerSetting $setting, + private readonly GameProperty $gameSetting, ) { } diff --git a/server/src/Event/KillEvent.php b/server/src/Event/KillEvent.php index c6bba76..fa91bff 100644 --- a/server/src/Event/KillEvent.php +++ b/server/src/Event/KillEvent.php @@ -14,6 +14,7 @@ public function __construct( private bool $headShot, ) { + parent::__construct(); } public function getPlayerDead(): Player diff --git a/server/src/Event/NoTickEvent.php b/server/src/Event/NoTickEvent.php new file mode 100644 index 0000000..deee2a9 --- /dev/null +++ b/server/src/Event/NoTickEvent.php @@ -0,0 +1,15 @@ +roundNumberEnded = $game->getRoundNumber(); } diff --git a/server/src/Event/RoundStartEvent.php b/server/src/Event/RoundStartEvent.php index 4ae2527..6227387 100644 --- a/server/src/Event/RoundStartEvent.php +++ b/server/src/Event/RoundStartEvent.php @@ -7,6 +7,7 @@ final class RoundStartEvent extends TickEvent { + /** @param Closure(static,int):void $callback */ public function __construct(private int $aliveAttackers, private int $aliveDefenders, Closure $callback) { parent::__construct($callback); diff --git a/server/src/Event/SoundEvent.php b/server/src/Event/SoundEvent.php index a6591a1..ce04634 100644 --- a/server/src/Event/SoundEvent.php +++ b/server/src/Event/SoundEvent.php @@ -19,6 +19,7 @@ final class SoundEvent extends TickEvent public function __construct(public readonly Point $position, public readonly SoundType $type) { + parent::__construct(); } public function setItem(?Item $item): self diff --git a/server/src/Event/TickEvent.php b/server/src/Event/TickEvent.php index 4ff067a..e1ba15f 100644 --- a/server/src/Event/TickEvent.php +++ b/server/src/Event/TickEvent.php @@ -6,13 +6,10 @@ class TickEvent extends Event { - protected ?Closure $callback = null; - protected int $maxTickCount = 1; - public function __construct(?Closure $callback = null, int $maxTickCount = 1) + /** @param ?Closure(static,int):void $callback */ + public function __construct(protected ?Closure $callback = null, protected int $maxTickCount = 1) { - $this->callback = $callback; - $this->maxTickCount = $maxTickCount; } final public function process(int $tick): void diff --git a/server/src/Event/TimeoutEvent.php b/server/src/Event/TimeoutEvent.php index 1723832..9b808e5 100644 --- a/server/src/Event/TimeoutEvent.php +++ b/server/src/Event/TimeoutEvent.php @@ -6,12 +6,11 @@ class TimeoutEvent extends Event { - protected int $tickCountTimeout = 0; - protected ?Closure $callback = null; + protected int $tickCountTimeout; - public function __construct(?Closure $callback, protected int $timeoutMs) + /** @param ?Closure(static,int):void $callback */ + public function __construct(protected ?Closure $callback, protected int $timeoutMs) { - $this->callback = $callback; $this->tickCountTimeout = $this->timeMsToTick($this->timeoutMs); } diff --git a/server/src/Net/Server.php b/server/src/Net/Server.php index 35190df..2c831f9 100644 --- a/server/src/Net/Server.php +++ b/server/src/Net/Server.php @@ -19,7 +19,7 @@ final class Server /** @var Client[] [playerId => Client] */ private array $clients = []; - /** @var array [playerId => fn(PlayerControl $control):void {} */ + /** @var array [playerId => fn(PlayerControl $control):void {} */ private array $tickCommands = []; private LoggerInterface $logger; diff --git a/server/src/Traits/Player/AttackTrait.php b/server/src/Traits/Player/AttackTrait.php index c16192b..bea3146 100644 --- a/server/src/Traits/Player/AttackTrait.php +++ b/server/src/Traits/Player/AttackTrait.php @@ -60,13 +60,6 @@ public function attack(): ?AttackResult public function attackSecondary(): ?AttackResult { $item = $this->getEquippedItem(); - if ($item instanceof ScopeItem) { - $item->scope(); - return null; - } - if (!($item instanceof AttackEnable)) { - return null; // @codeCoverageIgnore - } if ($item instanceof Knife || $item instanceof Grenade) { $result = $item->attackSecondary($this->createAttackEvent($item)); @@ -75,6 +68,13 @@ public function attackSecondary(): ?AttackResult $this->world->makeSound($soundEvent->setPlayer($this)->setItem($item)); return $this->processAttackResult($result); } + + return null; + } + + if ($item instanceof ScopeItem) { + $item->scope(); + return null; } return null; @@ -165,7 +165,7 @@ private function applyMovementRecoil(AttackEvent $event): void public function reload(): void { $item = $this->getEquippedItem(); - if (!($item instanceof Reloadable)) { + if (!($item instanceof Reloadable)) { // @infection-ignore-all return; } diff --git a/server/src/Traits/Player/JumpTrait.php b/server/src/Traits/Player/JumpTrait.php index 6b9e274..4aa473c 100644 --- a/server/src/Traits/Player/JumpTrait.php +++ b/server/src/Traits/Player/JumpTrait.php @@ -3,6 +3,7 @@ namespace cs\Traits\Player; use cs\Core\Setting; +use cs\Event\Event; use cs\Event\JumpEvent; trait JumpTrait @@ -22,24 +23,26 @@ public function jump(): void return; } - $event = new JumpEvent(function (JumpEvent $jumpEvent): void { + $event = new JumpEvent(function (Event $jumpEvent): void { + assert($jumpEvent instanceof JumpEvent); $targetYPosition = min($jumpEvent->maxYPosition, $this->position->y + Setting::jumpDistancePerTick()); - $candidate = $this->position->clone(); - for ($y = $this->position->y + 1; $y <= $targetYPosition; $y++) { - $floorCandidate = $this->world->findFloorSquare($candidate->setY($y), $this->playerBoundingRadius); + $candidate = $this->position->clone()->addY($this->headHeight); + for ($y = $this->position->y; $y < $targetYPosition; $y++) { + $candidate->addY(1); + $floorCandidate = $this->world->findFloorSquare($candidate, $this->playerBoundingRadius); if ($floorCandidate) { - $targetYPosition = $y - 1; + $this->removeEvent($this->eventIdJump); break; } - if ($this->world->isCollisionWithOtherPlayers($this->id, $candidate, $this->playerBoundingRadius, $this->headHeight)) { - $targetYPosition = $y - 1; + if ($this->world->isCollisionWithOtherPlayers($this->id, $candidate, $this->playerBoundingRadius, 2)) { + $this->removeEvent($this->eventIdJump); break; } } - if ($this->position->y !== $targetYPosition) { + if ($this->position->y !== $y) { $this->setActiveFloor(null); - $this->position->setY($targetYPosition); + $this->position->setY($y); } }, Setting::tickCountJump()); $event->maxYPosition = $this->position->y + Setting::playerJumpHeight(); diff --git a/server/src/Traits/Player/MovementTrait.php b/server/src/Traits/Player/MovementTrait.php index fe9792f..2382f6f 100644 --- a/server/src/Traits/Player/MovementTrait.php +++ b/server/src/Traits/Player/MovementTrait.php @@ -143,7 +143,7 @@ private function getMoveAngle(): float $angle += $moveX * (45 * 3); } } else { // single direction move - if ($moveZ === -1) { + if ($moveZ === -1) { // @infection-ignore-all $angle += 180; } elseif ($moveX === 1) { $angle += 90; @@ -176,7 +176,7 @@ private function getMoveSpeed(): int if ($equippedItem instanceof ScopeItem && $equippedItem->isScopedIn()) { $speed *= .5; } - if ($this->isJumping()) { + if ($this->isJumping()) { // @infection-ignore-all $speed *= Setting::jumpMovementSpeedMultiplier(); } elseif ($this->isFlying()) { $speed *= Setting::flyingMovementSpeedMultiplier(); diff --git a/test/og/BaseTestCase.php b/test/og/BaseTestCase.php index bc528f3..ec89ed1 100644 --- a/test/og/BaseTestCase.php +++ b/test/og/BaseTestCase.php @@ -46,7 +46,7 @@ protected function setUp(): void } /** - * @param array $commands + * @param list $commands * @param array $gameProperties */ protected function simulateGame(array $commands, array $gameProperties = []): TestGame @@ -138,7 +138,7 @@ protected function createGame(array $gameProperties = []): TestGame } /** - * @param array $commands + * @param list $commands */ protected function playPlayerDebug(TestGame $game, array $commands, int $playerId = 1): void { @@ -146,7 +146,7 @@ protected function playPlayerDebug(TestGame $game, array $commands, int $playerI } /** - * @param array $commands + * @param list $commands */ protected function playPlayer(TestGame $game, array $commands, int $playerId = 1, bool $debug = false): void { diff --git a/test/og/Movement/MouseAngleTest.php b/test/og/Movement/MouseAngleTest.php index aa126b8..1eb4c2c 100644 --- a/test/og/Movement/MouseAngleTest.php +++ b/test/og/Movement/MouseAngleTest.php @@ -11,6 +11,7 @@ class MouseAngleTest extends BaseTestCase { + /** @param Closure(Player):mixed $moveCallback */ private function runAngle(int $angle, Closure $moveCallback, Point $startPos): Point { $playerCommands = [ diff --git a/test/og/Movement/WorldCollisionTest.php b/test/og/Movement/WorldCollisionTest.php index f98dc79..f0e9a76 100644 --- a/test/og/Movement/WorldCollisionTest.php +++ b/test/og/Movement/WorldCollisionTest.php @@ -241,9 +241,30 @@ function (Player $p): void { $this->assertPositionSame(new Point(), $game->getPlayer(1)->getPositionClone()); } + public function testPlayerJumpIntoWallCancelGoingUpInstantly(): void + { + $game = $this->createTestGame(); + $this->assertGreaterThan(Setting::jumpDistancePerTick(), Setting::fallAmountPerTick()); + $maxY = (int)ceil(Setting::jumpDistancePerTick() / 2); + $baseY = 123; + $game->getWorld()->addFloor(new Floor(new Point(0, $baseY, 0), 1000, 1000)); + $game->getWorld()->addFloor(new Floor(new Point(0, $baseY + Setting::playerHeadHeightStand() + $maxY, 0), 1000, 1000)); + + $this->playPlayer($game, [ + fn(Player $p) => $p->setPosition(new Point(500, $baseY, 500)), + fn(Player $p) => $p->jump(), + function (Player $p) use ($baseY) { + $this->assertSame($baseY, $p->getPositionClone()->y); + $this->assertFalse($p->isFlying()); + $this->assertFalse($p->isJumping()); + }, + $this->endGame(), + ]); + } + public function testPlayerJumpCeiling(): void { - $ceiling = new Floor(new Point(0, Setting::playerJumpHeight() / 2, 0)); + $ceiling = new Floor(new Point(0, Setting::playerHeadHeightStand() + Setting::playerJumpHeight() - (Setting::jumpDistancePerTick() / 2), 0)); $game = $this->createOneRoundGame(Setting::tickCountJump()); $game->getWorld()->addFloor($ceiling); $game->getPlayer(1)->jump(); diff --git a/test/og/Shooting/BombTest.php b/test/og/Shooting/BombTest.php index 7a8d92e..d803b7b 100644 --- a/test/og/Shooting/BombTest.php +++ b/test/og/Shooting/BombTest.php @@ -216,6 +216,7 @@ function (Player $p) use ($game) { $score = $game->getScore()->toArray(); $history = $score['history'] ?? false; $this->assertIsArray($history); + $this->assertIsArray($history[1] ?? false); $this->assertSame(RoundEndReason::BOMB_EXPLODED->value, $history[1]['reason'] ?? false); $this->assertSame([0, 1], $score['score'] ?? false); $this->assertSame([0, 1], $score['firstHalfScore'] ?? false); diff --git a/test/og/Shooting/KnifeAttackTest.php b/test/og/Shooting/KnifeAttackTest.php index d13a69c..9e97848 100644 --- a/test/og/Shooting/KnifeAttackTest.php +++ b/test/og/Shooting/KnifeAttackTest.php @@ -20,12 +20,15 @@ public function testBlankStab(): void $game = $this->createNoPauseGame(); $this->playPlayer($game, [ $this->waitNTicks(Knife::equipReadyTimeMs) + 1, - function () use ($game) { - $knife = $game->getPlayer(1)->getEquippedItem(); + function (Player $p) use ($game) { + $knife = $p->getEquippedItem(); $this->assertInstanceOf(Knife::class, $knife); + $p->reload(); $this->assertTrue($knife->canAttack($game->getTickId())); $this->assertTrue($game->getWorld()->canAttack($game->getPlayer(1))); - $attack = $game->getPlayer(1)->attack(); + $attack = $p->attack(); + $this->assertNull($p->attack()); + $this->assertNull($p->attackSecondary()); $this->assertInstanceOf(AttackResult::class, $attack); $bullet = $attack->getBullet(); $this->assertSame(Knife::stabMaxDistance, $bullet->getDistanceTraveled()); @@ -34,10 +37,10 @@ function () use ($game) { $this->assertSame(0, $attack->getMoneyAward()); $this->assertCount(0, $attack->getHits()); }, - function () use ($game) { - $knife = $game->getPlayer(1)->getEquippedItem(); + function (Player $p) use ($game) { + $knife = $p->getEquippedItem(); $this->assertFalse($knife->canAttack($game->getTickId())); - $this->assertNull($game->getPlayer(1)->attack()); + $this->assertNull($p->attack()); }, $this->endGame(), ]); diff --git a/test/og/Shooting/MolotovGrenadeTest.php b/test/og/Shooting/MolotovGrenadeTest.php index 2a83831..71989d3 100644 --- a/test/og/Shooting/MolotovGrenadeTest.php +++ b/test/og/Shooting/MolotovGrenadeTest.php @@ -66,7 +66,6 @@ public function testShrinkPhaseDoDamage(): void $this->assertNull($event->getPlayerId()); $this->assertNull($event->getItem()); $eventSerialized = $event->serialize(); - $this->assertIsArray($eventSerialized); $this->assertIsArray($eventSerialized['extra']); $this->assertNotEmpty($eventSerialized['extra']['id'] ?? false); $p->setPosition(new Point(500, 0, 500)); @@ -355,22 +354,15 @@ public function testSmokeExtinguishFlames(): void fn(Player $p) => $this->assertTrue($p->equip(InventorySlot::SLOT_GRENADE_MOLOTOV)), $this->waitNTicks(Molotov::equipReadyTimeMs), $this->waitNTicks((int)ceil(Smoke::MAX_TIME_MS / 3)), - function () use ($game) { - $this->assertFalse( - $game->getWorld()->flameCanIgnite(new Column($game->getPlayer(1)->getPositionClone(), 2, 2)) - ); - $this->assertFalse( - $game->getWorld()->flameCanIgnite(new Column($game->getPlayer(1)->getPositionClone()->setY(Smoke::MAX_HEIGHT - 100), 2, 2)) - ); - $this->assertFalse( - $game->getWorld()->flameCanIgnite(new Column($game->getPlayer(1)->getPositionClone()->setY(Smoke::MAX_HEIGHT), 2, 2)) - ); - $this->assertTrue( - $game->getWorld()->flameCanIgnite(new Column($game->getPlayer(1)->getPositionClone()->setY(Smoke::MAX_HEIGHT + 2), 2, 2)) - ); - $this->assertFalse( - $game->getWorld()->flameCanIgnite(new Column($game->getPlayer(1)->getPositionClone()->setY(Smoke::MAX_CORNER_HEIGHT), 2, 2)) - ); + function (Player $p) use ($game) { + $pp = $p->getPositionClone(); + $this->assertFalse($game->getWorld()->flameCanIgnite(new Column($pp->clone(), 2, 2))); + $this->assertFalse($game->getWorld()->flameCanIgnite(new Column($pp->clone()->setY(Smoke::MAX_HEIGHT - 100), 2, 2))); + $this->assertFalse($game->getWorld()->flameCanIgnite(new Column($pp->clone()->setY(Smoke::MAX_HEIGHT), 2, 2))); + $this->assertTrue($game->getWorld()->flameCanIgnite(new Column($pp->clone()->setY(Smoke::MAX_HEIGHT + 2), 2, 2))); + $this->assertFalse($game->getWorld()->flameCanIgnite(new Column($pp->clone()->setY(Smoke::MAX_CORNER_HEIGHT), 2, 2))); + $this->assertFalse($game->getWorld()->flameCanIgnite(new Column($pp->clone()->setY(Smoke::MAX_CORNER_HEIGHT)->addZ(-200), 2, 2))); + $this->assertTrue($game->getWorld()->flameCanIgnite(new Column($pp->clone()->setY(Smoke::MAX_HEIGHT)->addZ(-200), 2, 2))); }, fn(Player $p) => $p->getSight()->look(0, -90), fn(Player $p) => $this->assertNotNull($p->attack()), diff --git a/test/og/Shooting/PlayerKillTest.php b/test/og/Shooting/PlayerKillTest.php index 8a995e6..6289b08 100644 --- a/test/og/Shooting/PlayerKillTest.php +++ b/test/og/Shooting/PlayerKillTest.php @@ -16,10 +16,8 @@ use cs\Enum\Color; use cs\Enum\HitBoxType; use cs\Enum\ItemId; -use cs\Enum\SoundType; use cs\Event\AttackResult; use cs\Event\KillEvent; -use cs\Event\SoundEvent; use cs\Weapon\PistolGlock; use cs\Weapon\PistolUsp; use cs\Weapon\RifleAk; @@ -311,7 +309,8 @@ public function testAwpOneBulletTripleHeadShotKills(): void $this->waitNTicks(RifleAWP::equipReadyTimeMs), function (Player $p) { $p->getSight()->look(45, 0); - $p->attackSecondary(); + $this->assertNull($p->attackSecondary()); + $this->assertSame(1, $p->getEquippedItem()->getScopeLevel()); $ar = $p->attack(); $this->assertInstanceOf(AttackResult::class, $ar); $this->assertTrue($ar->somePlayersWasHit()); @@ -407,6 +406,10 @@ public function testArmorShooting(): void $p2->setPosition(new Point(300, 0, 500)); $p2->buyItem(BuyMenuItem::KEVLAR_BODY); + $game->addPlayer(new Player(3, Color::ORANGE, false)); + $game->getPlayer(3)->setPosition(new Point(350, 0, 500)); + $game->getPlayer(3)->suicide(); + $this->playPlayer($game, [ fn(Player $p) => $p->equipSecondaryWeapon(), $this->waitNTicks(PistolGlock::equipReadyTimeMs), @@ -430,7 +433,7 @@ function (Player $p) use ($p2) { fn() => $this->assertSame(1, $game->getRoundNumber()), $this->waitNTicks(PistolGlock::recoilResetMs), fn(Player $p) => $p->getSight()->look(-90, 0), - fn(Player $p) => $this->assertPlayerHit($p->attack()), + fn(Player $p) => $this->assertCount(2, $this->assertPlayerHit($p->attack())->getHits()), $this->endGame(), ]); } diff --git a/test/og/Shooting/ShootTest.php b/test/og/Shooting/ShootTest.php index 26c529c..5f96ddc 100644 --- a/test/og/Shooting/ShootTest.php +++ b/test/og/Shooting/ShootTest.php @@ -29,6 +29,7 @@ public function testOneTapAmmoMagazine(): void $this->waitNTicks(RifleAk::equipReadyTimeMs), fn(Player $p) => $p->getSight()->lookVertical(-91), fn(Player $p) => $this->assertNull($p->attackSecondary()), + fn(Player $p) => $this->assertSame(0, $p->getEquippedItem()->getScopeLevel()), fn(Player $p) => $this->assertPlayerNotHit($p->attack()), ]; diff --git a/test/og/TestGame.php b/test/og/TestGame.php index e36d10f..2d27eef 100644 --- a/test/og/TestGame.php +++ b/test/og/TestGame.php @@ -5,7 +5,9 @@ use Closure; use cs\Core\Game; use cs\Core\GameException; +use cs\Core\GameState; use cs\Core\Setting; +use cs\Event\Event; use cs\Map\TestMap; use cs\Net\Protocol\TextProtocol; @@ -15,8 +17,11 @@ class TestGame extends Game { private int $tickMax = 1; + /** @var ?Closure(GameState):void */ private ?Closure $onTickCallback = null; + /** @var ?Closure(GameState):void */ private ?Closure $afterTickCallback = null; + /** @var ?Closure(non-empty-list):void */ private ?Closure $onEventsCallback = null; /** @var array */ private array $gameStates = []; @@ -58,7 +63,7 @@ public function start(bool $debug = false): void } /** - * @param Closure $callback function(GameState $state):void {} + * @param Closure(GameState):void $callback function(GameState $state):void {} */ public function onTick(Closure $callback): void { @@ -66,7 +71,7 @@ public function onTick(Closure $callback): void } /** - * @param Closure $callback function(GameState $state):void {} + * @param Closure(GameState):void $callback function(GameState $state):void {} */ public function onAfterTick(Closure $callback): void { @@ -74,7 +79,7 @@ public function onAfterTick(Closure $callback): void } /** - * @param Closure $callback function(array $events):void {foreach($events as $event){}} + * @param Closure(non-empty-list):void $callback function(array $events):void {foreach($events as $event){}} */ public function onEvents(Closure $callback): void { diff --git a/test/og/Unit/BallColliderTest.php b/test/og/Unit/BallColliderTest.php index 77cff52..e3a310d 100644 --- a/test/og/Unit/BallColliderTest.php +++ b/test/og/Unit/BallColliderTest.php @@ -29,6 +29,7 @@ public function testResolution1(): void $resolutionAngleVertical = 90.0; ///// + $world = $this->createWorld(); $ball = $this->createBall($start, $radius, $world, $angleHorizontal, $angleVertical); $world->addBox(new Box(new Point(9), 1, 1, 1)); $world->addBox(new Box(new Point(10), 1, 1, 1)); @@ -50,6 +51,7 @@ public function testResolution2(): void $extreme = $start->clone(); ///// + $world = $this->createWorld(); $ball = $this->createBall($start, $radius, $world, $angleHorizontal, $angleVertical); $this->assertPositionSame($extreme, $ball->getLastExtremePosition()); $this->assertFalse($ball->hasCollision($start->addPart(-2, 3, 0))); @@ -152,9 +154,8 @@ private function _testSingleWallBounce( $this->fail('No collision detected'); } - private function createBall(Point $start, int $radius, ?World &$world, float $angleHorizontal, float $angleVertical): BallCollider + private function createBall(Point $start, int $radius, World $world, float $angleHorizontal, float $angleVertical): BallCollider { - $world = $world ?? $this->createWorld(); return new BallCollider($world, $start, $radius, $angleHorizontal, $angleVertical); } diff --git a/test/og/Unit/BoxTest.php b/test/og/Unit/BoxTest.php index 243c6f0..c657e8f 100644 --- a/test/og/Unit/BoxTest.php +++ b/test/og/Unit/BoxTest.php @@ -26,13 +26,11 @@ public function testBox(): void $this->assertCount(2, $floors); $bottomFloor = $floors[0]; - $this->assertInstanceOf(Floor::class, $bottomFloor); $this->assertSame($point->y, $bottomFloor->getY()); $this->assertPositionSame($point, $bottomFloor->getStart()); $pointEnd = $point->clone()->addX($width)->addZ($depth); $this->assertPositionSame($pointEnd, $bottomFloor->getEnd()); $topFloor = $floors[1]; - $this->assertInstanceOf(Floor::class, $topFloor); $this->assertSame($point->y + $height, $topFloor->getY()); $this->assertPositionSame($point->clone()->addY($height), $topFloor->getStart()); $pointEnd = $point->clone()->addY($height)->addX($width)->addZ($depth); @@ -40,22 +38,18 @@ public function testBox(): void $frontWall = $walls[0]; - $this->assertInstanceOf(Wall::class, $frontWall); $this->assertPositionSame($point, $frontWall->getStart()); $pointEnd = $point->clone()->addY($height)->addX($width); $this->assertPositionSame($pointEnd, $frontWall->getEnd()); $backWall = $walls[1]; - $this->assertInstanceOf(Wall::class, $backWall); $this->assertPositionSame($point->clone()->addZ($depth), $backWall->getStart()); $pointEnd = $point->clone()->addZ($depth)->addY($height)->addX($width); $this->assertPositionSame($pointEnd, $backWall->getEnd()); $leftWall = $walls[2]; - $this->assertInstanceOf(Wall::class, $leftWall); $this->assertPositionSame($point, $leftWall->getStart()); $pointEnd = $point->clone()->addZ($depth)->addY($height); $this->assertPositionSame($pointEnd, $leftWall->getEnd()); $rightWall = $walls[3]; - $this->assertInstanceOf(Wall::class, $rightWall); $this->assertPositionSame($point->clone()->addX($width), $rightWall->getStart()); $pointEnd = $point->clone()->addX($width)->addZ($depth)->addY($height); $this->assertPositionSame($pointEnd, $rightWall->getEnd()); diff --git a/test/og/Unit/CollisionTest.php b/test/og/Unit/CollisionTest.php index fe6aa99..a140434 100644 --- a/test/og/Unit/CollisionTest.php +++ b/test/og/Unit/CollisionTest.php @@ -302,15 +302,19 @@ public function testBoxWithBox(): void )); $this->assertTrue(Collision::boxWithBox( new Point(-5, 0, -5), new Point(5, 4, 5), - new Point(1, 2, 5), new Point(3, 5, -5), + new Point(1, 2, -5), new Point(3, 5, 5), )); $this->assertTrue(Collision::boxWithBox( new Point(-5, 0, -5), new Point(5, 4, 5), - new Point(1, -2, 5), new Point(3, 0, -5), + new Point(1, -2, -5), new Point(3, 0, 5), )); $this->assertTrue(Collision::boxWithBox( new Point(-5, 0, -5), new Point(5, 4, 5), - new Point(-6, 0, 5), new Point(-5, 2, -5), + new Point(-6, 0, -5), new Point(-5, 2, 5), + )); + $this->assertTrue(Collision::boxWithBox( + new Point(2, 0, 1), new Point(5, 4, 5), + new Point(-1, -1, 5), new Point(2, 2, 5), )); diff --git a/test/og/Unit/InventoryTest.php b/test/og/Unit/InventoryTest.php index 568fa77..a70876e 100644 --- a/test/og/Unit/InventoryTest.php +++ b/test/og/Unit/InventoryTest.php @@ -18,7 +18,12 @@ public function testGrenadeLastEquippedSlots(): void $lastGrenadeEquippedSlots = $inventory->getLastEquippedGrenadeSlots(); $this->assertSame(InventorySlot::SLOT_GRENADE_SMOKE->value, array_shift($lastGrenadeEquippedSlots)); - $this->assertTrue($inventory->pickup(new Incendiary())); + $incendiary = new Incendiary(); + $this->assertSame(1, $incendiary->getMaxQuantity()); + $this->assertSame(1, $incendiary->getMaxBuyCount()); + $this->assertSame(1, $incendiary->getQuantity()); + + $this->assertTrue($inventory->pickup($incendiary)); $this->assertNotNull($inventory->equip(InventorySlot::SLOT_GRENADE_MOLOTOV)); $lastGrenadeEquippedSlots = $inventory->getLastEquippedGrenadeSlots(); $this->assertNotSame(InventorySlot::getGrenadeSlotIds(), $lastGrenadeEquippedSlots); diff --git a/test/og/Unit/PerformanceTest.php b/test/og/Unit/PerformanceTest.php index 82957f8..e063851 100644 --- a/test/og/Unit/PerformanceTest.php +++ b/test/og/Unit/PerformanceTest.php @@ -264,13 +264,13 @@ public function testMolotov(): void $attackResult = $player->attack(); $took = $timer->stop(); $this->assertNotNull($attackResult); - $this->assertLessThan(0.8 * self::$timeScale, $took->asMilliseconds()); + $this->assertLessThan(1 * self::$timeScale, $took->asMilliseconds()); foreach (range(1, Util::millisecondsToFrames(Molotov::MAX_TIME_MS)) as $i) { $timer->start(); $game->tick(++$tickId); $took = $timer->stop(); - $this->assertLessThan(0.8 * self::$timeScale, $took->asMilliseconds(), "Tick {$tickId}"); + $this->assertLessThan(1 * self::$timeScale, $took->asMilliseconds(), "Tick {$tickId}"); if ($game->getRoundNumber() === 2) { break; diff --git a/test/og/Unit/ProtocolTest.php b/test/og/Unit/ProtocolTest.php index 9f7f259..87570cb 100644 --- a/test/og/Unit/ProtocolTest.php +++ b/test/og/Unit/ProtocolTest.php @@ -37,6 +37,8 @@ public function testInvalidCommandsWhenExtendingMaxCallPerTick(): void public function testTextProtocol(): void { $protocol = new Protocol\TextProtocol(); + $this->assertGreaterThan(10, $protocol->getRequestMaxSizeBytes()); + $this->assertLessThan(2 ** 13, $protocol->getRequestMaxSizeBytes()); $this->assertSame( [ diff --git a/test/og/Unit/UtilTest.php b/test/og/Unit/UtilTest.php index 69efa2a..d33222c 100644 --- a/test/og/Unit/UtilTest.php +++ b/test/og/Unit/UtilTest.php @@ -13,6 +13,12 @@ class UtilTest extends BaseTest { + public function testAssertionsEnabled(): void + { + $this->expectException('\AssertionError'); + assert(false); // @phpstan-ignore function.impossibleType + } + public function testNegativeTime(): void { $this->expectExceptionMessage('Negative time given'); @@ -186,13 +192,7 @@ protected function _testWorldAngleUsingMovement(Point $start, float $h, float $v $end = $start->clone(); $end->addFromArray(Util::movementXYZ($h, $v, $distance)); [$actualH, $actualV] = Util::worldAngle($end, $start); - if (is_float($actualH)) { - $actualH = round($actualH); - } - if (is_float($actualV)) { - $actualV = round($actualV); - } - $this->assertSame([$h, $v], [$actualH, $actualV], "{$start}, angle ({$h},{$v})"); + $this->assertSame([$h, $v], [$actualH === null ? null : round($actualH), round($actualV)], "{$start}, angle ({$h},{$v})"); } public function testAngleNormalize(): void @@ -252,8 +252,8 @@ public function testPoint(): void 'x' => 2, 'y' => 4, ], $point->to2D('zy')->add(-1, 2)->toArray()); - $point->setFromArray([1,3,2]); - $this->assertTrue((new Point(1,3,2))->equals($point)); + $point->setFromArray([1, 3, 2]); + $this->assertTrue((new Point(1, 3, 2))->equals($point)); } public function testGamePropertyUnknownFieldGet(): void diff --git a/test/og/World/PlayerBoostTest.php b/test/og/World/PlayerBoostTest.php index 7967e16..aa1e74d 100644 --- a/test/og/World/PlayerBoostTest.php +++ b/test/og/World/PlayerBoostTest.php @@ -149,6 +149,8 @@ public function testPlayerCannotJumpWhenOtherPlayerStandingOnThem(): void $this->assertSame(0, $player1->getPositionClone()->y); $this->assertPositionSame($p2pos, $game->getPlayer(2)->getPositionClone()); $this->assertTrue($player2->canJump()); + $this->assertTrue($player1->canJump()); + $this->assertFalse($player1->isJumping()); } } diff --git a/test/og/World/WorldTest.php b/test/og/World/WorldTest.php index bef62fd..4b35593 100644 --- a/test/og/World/WorldTest.php +++ b/test/og/World/WorldTest.php @@ -75,6 +75,10 @@ public function testCanBeSeen(): void $this->assertFalse($game->getWorld()->canBeSeen( $player, $player->getPositionClone()->addY($player->getSightHeight())->addZ(100), 10, 100, true) ); + $game->getPlayer(2)->setPosition(new Point(100, 999, 100)); + $this->assertTrue($game->getWorld()->canBeSeen( + $player, $player->getPositionClone()->addY($player->getSightHeight())->addZ(100), 10, 100, true) + ); $player->getSight()->look(0, -80); $this->assertFalse($game->getWorld()->canBeSeen($player, $player->getPositionClone()->setY(-1), 10, 999));