From 67ecc14ba090d2eae0f78bb17e4668aec448a2a7 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 2 May 2024 00:06:31 +0200 Subject: [PATCH 01/11] Add dynamic game mission possible checks --- app/Factories/GameMissionFactory.php | 38 +++++++ app/GameMissions/Abstracts/GameMission.php | 16 +++ app/GameMissions/ColonisationMission.php | 49 +++++++++ app/GameMissions/DeploymentMission.php | 18 +++ .../Models/MissionPossibleStatus.php | 15 +++ app/GameMissions/TransportMission.php | 18 +++ app/GameObjects/Models/UnitCollection.php | 17 +++ app/Http/Controllers/FleetController.php | 103 +++++++++++------- 8 files changed, 236 insertions(+), 38 deletions(-) create mode 100644 app/Factories/GameMissionFactory.php create mode 100644 app/GameMissions/ColonisationMission.php create mode 100644 app/GameMissions/Models/MissionPossibleStatus.php diff --git a/app/Factories/GameMissionFactory.php b/app/Factories/GameMissionFactory.php new file mode 100644 index 00000000..7226a85b --- /dev/null +++ b/app/Factories/GameMissionFactory.php @@ -0,0 +1,38 @@ + + * @throws BindingResolutionException + */ + public static function getAllMissions(): array { + /* + { + "1": "Attack", + "2": "ACS Attack", + "3": "Transport", + "4": "Deployment", + "5": "ACS Defend", + "6": "Espionage", + "7": "Colonisation", + "8": "Recycle Debris Field", + "9": "Moon Destruction", + "15": "Expedition" + } + */ + return [ + app()->make(DeploymentMission::class), + app()->make(TransportMission::class), + app()->make(ColonisationMission::class), + ]; + } +} \ No newline at end of file diff --git a/app/GameMissions/Abstracts/GameMission.php b/app/GameMissions/Abstracts/GameMission.php index 60957944..39bbf708 100644 --- a/app/GameMissions/Abstracts/GameMission.php +++ b/app/GameMissions/Abstracts/GameMission.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Carbon; use OGame\Factories\PlanetServiceFactory; +use OGame\GameMissions\Models\MissionPossibleStatus; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\FleetMission; use OGame\Models\Resources; @@ -49,6 +50,21 @@ public static function hasReturnMission(): bool return static::$hasReturnMission; } + public static function getTypeId(): int + { + return static::$typeId; + } + + /** + * Checks if the mission is possible under the given circumstances. + * + * @param PlanetService $planet + * @param ?PlanetService $targetPlanet + * @param UnitCollection $units + * @return MissionPossibleStatus + */ + public abstract function isMissionPossible(PlanetService $planet, ?PlanetService $targetPlanet, UnitCollection $units): MissionPossibleStatus; + /** * Cancel an already started mission. * diff --git a/app/GameMissions/ColonisationMission.php b/app/GameMissions/ColonisationMission.php new file mode 100644 index 00000000..4273e267 --- /dev/null +++ b/app/GameMissions/ColonisationMission.php @@ -0,0 +1,49 @@ +getAmountByMachineName('colony_ship') > 0) { + return new MissionPossibleStatus(true); + } + else { + // Return error message + return new MissionPossibleStatus(false, __('You need a colony ship to colonize a planet.')); + } + } + + return new MissionPossibleStatus(false); + } + + /** + * @throws BindingResolutionException + */ + protected function processArrival(FleetMission $mission): void + { + // TOOD: Implement the colonisation logic + } + + protected function processReturn(FleetMission $mission): void + { + // TODO: Implement the return logic + } +} \ No newline at end of file diff --git a/app/GameMissions/DeploymentMission.php b/app/GameMissions/DeploymentMission.php index 13e88878..a0108084 100644 --- a/app/GameMissions/DeploymentMission.php +++ b/app/GameMissions/DeploymentMission.php @@ -5,7 +5,10 @@ use Illuminate\Contracts\Container\BindingResolutionException; use OGame\Factories\PlanetServiceFactory; use OGame\GameMissions\Abstracts\GameMission; +use OGame\GameMissions\Models\MissionPossibleStatus; +use OGame\GameObjects\Models\UnitCollection; use OGame\Models\FleetMission; +use OGame\Services\PlanetService; class DeploymentMission extends GameMission { @@ -13,6 +16,21 @@ class DeploymentMission extends GameMission protected static int $typeId = 4; protected static bool $hasReturnMission = false; + /** + * @inheritdoc + */ + public function isMissionPossible(PlanetService $planet, ?PlanetService $targetPlanet, UnitCollection $units): MissionPossibleStatus + { + if ($targetPlanet != null) { + if ($planet->getPlayer()->equals($targetPlanet->getPlayer())) { + // If target player is the same as the current player, this mission is possible. + return new MissionPossibleStatus(true); + } + } + + return new MissionPossibleStatus(false); + } + /** * @throws BindingResolutionException */ diff --git a/app/GameMissions/Models/MissionPossibleStatus.php b/app/GameMissions/Models/MissionPossibleStatus.php new file mode 100644 index 00000000..31babb71 --- /dev/null +++ b/app/GameMissions/Models/MissionPossibleStatus.php @@ -0,0 +1,15 @@ +possible = $possible; + $this->error = $error; + } +} diff --git a/app/GameMissions/TransportMission.php b/app/GameMissions/TransportMission.php index 5a06d061..52bebac2 100644 --- a/app/GameMissions/TransportMission.php +++ b/app/GameMissions/TransportMission.php @@ -5,7 +5,10 @@ use Illuminate\Contracts\Container\BindingResolutionException; use OGame\Factories\PlanetServiceFactory; use OGame\GameMissions\Abstracts\GameMission; +use OGame\GameMissions\Models\MissionPossibleStatus; +use OGame\GameObjects\Models\UnitCollection; use OGame\Models\FleetMission; +use OGame\Services\PlanetService; class TransportMission extends GameMission { @@ -13,6 +16,21 @@ class TransportMission extends GameMission protected static int $typeId = 3; protected static bool $hasReturnMission = true; + /** + * @inheritdoc + */ + public function isMissionPossible(PlanetService $planet, ?PlanetService $targetPlanet, UnitCollection $units): MissionPossibleStatus + { + if ($targetPlanet != null) { + if ($planet->getPlayer()->equals($targetPlanet->getPlayer())) { + // If target player is the same as the current player, this mission is possible. + return new MissionPossibleStatus(true); + } + } + + return new MissionPossibleStatus(false); + } + /** * @throws BindingResolutionException */ diff --git a/app/GameObjects/Models/UnitCollection.php b/app/GameObjects/Models/UnitCollection.php index a68e7e57..38696c8e 100644 --- a/app/GameObjects/Models/UnitCollection.php +++ b/app/GameObjects/Models/UnitCollection.php @@ -20,4 +20,21 @@ public function addUnit(UnitObject $unitObject, int $amount): void $this->units[] = $entry; } + + /** + * Get the amount of a unit in the collection. + * + * @param string $machine_name + * @return int + */ + public function getAmountByMachineName(string $machine_name): int + { + foreach ($this->units as $entry) { + if ($entry->unitObject->machine_name === $machine_name) { + return $entry->amount; + } + } + + return 0; + } } diff --git a/app/Http/Controllers/FleetController.php b/app/Http/Controllers/FleetController.php index 63ff7bcf..5f8d55f7 100644 --- a/app/Http/Controllers/FleetController.php +++ b/app/Http/Controllers/FleetController.php @@ -2,10 +2,12 @@ namespace OGame\Http\Controllers; +use Exception; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\View\View; +use OGame\Factories\GameMissionFactory; use OGame\Factories\PlanetServiceFactory; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Planet\Coordinate; @@ -24,7 +26,7 @@ class FleetController extends OGameController * @param PlayerService $player * @param ObjectService $objects * @return View - * @throws \Exception + * @throws Exception */ public function index(Request $request, PlayerService $player, ObjectService $objects): View { @@ -80,9 +82,21 @@ public function movement(): View /** * @throws BindingResolutionException */ - public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService $objects): JsonResponse - { - $enabledMissions = []; + public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService $objects) : JsonResponse { + $currentPlanet = $currentPlayer->planets->current(); + + // Return ships data for this planet taking into account the current planet's properties and research levels. + $shipsData = []; + foreach ($objects->getShipObjects() as $shipObject) { + $shipsData[$shipObject->id] = [ + 'id' => $shipObject->id, + 'name' => $shipObject->title, + 'baseFuelCapacity' => $shipObject->properties->capacity->calculate($currentPlanet)->totalValue, + 'baseCargoCapacity' => $shipObject->properties->capacity->calculate($currentPlanet)->totalValue, + 'fuelConsumption' => $shipObject->properties->fuel->calculate($currentPlanet)->totalValue, + 'speed' => $shipObject->properties->speed->calculate($currentPlanet)->totalValue + ]; + } // Get target coordinates $galaxy = request()->input('galaxy'); @@ -105,35 +119,29 @@ public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService $targetPlanetName = '?'; $targetPlayerName = 'Deep space'; $targetCoordinates = new Coordinate($galaxy, $system, $position); - $enabledMissions[] = 7; - } - - // If position is 16, only enable expedition mission - if ($position == 16) { - $enabledMissions = [15]; - } elseif ($currentPlayer->equals($targetPlayer)) { - // If target player is the same as the current player, enable transport/deployment missions. - $enabledMissions = [3, 4]; } - $currentPlanet = $currentPlayer->planets->current(); - - // Return ships data for this planet taking into account the current planet's properties and research levels. - $shipsData = []; - foreach ($objects->getShipObjects() as $shipObject) { - $shipsData[$shipObject->id] = [ - 'id' => $shipObject->id, - 'name' => $shipObject->title, - 'baseFuelCapacity' => $shipObject->properties->capacity->calculate($currentPlanet)->totalValue, - 'baseCargoCapacity' => $shipObject->properties->capacity->calculate($currentPlanet)->totalValue, - 'fuelConsumption' => $shipObject->properties->fuel->calculate($currentPlanet)->totalValue, - 'speed' => $shipObject->properties->speed->calculate($currentPlanet)->totalValue - ]; + // Determine enabled/available missions based on the current user, planet and target planet's properties. + $enabledMissions = []; + $errors = []; + $units = $this->getUnitsFromRequest($currentPlanet); + $allMissions = GameMissionFactory::getAllMissions(); + foreach ($allMissions as $mission) { + $possible = $mission->isMissionPossible($currentPlanet, $targetPlanet, $units); + if ($possible->possible) { + $enabledMissions[] = $mission::getTypeId(); + } + else if (!empty($possible->error)) { + // If the mission is not possible and has an error message, return error message in JSON. + $errors[] = [ + 'message' => $possible->error, + 'error' => 140035 // TODO: is this actually required by the frontend? + ]; + } } // Build orders array set key to true if the mission is enabled. Set to false if not. $orders = []; - // Possible mission types: 1, 2, 3, 4, 5, 6, 7, 8, 9, 15 $possible_mission_types = [1, 2, 3, 4, 5, 6, 7, 8, 9, 15]; foreach ($possible_mission_types as $mission) { if (in_array($mission, $enabledMissions)) { @@ -143,9 +151,15 @@ public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService } } + $status = 'success'; + if (count($errors) > 0) { + $status = 'failure'; + } + return response()->json([ 'shipsData' => $shipsData, - 'status' => 'success', + 'status' => $status, + 'errors' => $errors, 'additionalFlightSpeedinfo' => '', 'targetInhabited' => true, 'targetIsStrong' => false, @@ -177,7 +191,7 @@ public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService * Handles the dispatch of a fleet. * * @return JsonResponse - * @throws \Exception + * @throws Exception */ public function dispatchSendFleet(PlayerService $player): JsonResponse { @@ -222,15 +236,7 @@ public function dispatchSendFleet(PlayerService $player): JsonResponse // Extract units from the request and create a unit collection. // Loop through all input fields and get all units prefixed with "am". - $units = new UnitCollection(); - foreach (request()->all() as $key => $value) { - if (strpos($key, 'am') === 0) { - $unit_id = (int)str_replace('am', '', $key); - // Create GameObject - $unitObject = $player->planets->current()->objects->getUnitObjectById($unit_id); - $units->addUnit($unitObject, (int)$value); - } - } + $units = $this->getUnitsFromRequest($planet); // Extract resources from the request $metal = (int)request()->input('metal'); @@ -272,4 +278,25 @@ public function dispatchRecallFleet(): JsonResponse 'success' => true, ]); } + + /** + * Get units from the request and create a UnitCollection. + * + * @param PlanetService $planet + * @return UnitCollection + * @throws Exception + */ + private function getUnitsFromRequest(PlanetService $planet): UnitCollection { + $units = new UnitCollection(); + foreach (request()->all() as $key => $value) { + if (str_starts_with($key, 'am')) { + $unit_id = (int)str_replace('am', '', $key); + // Create GameObject + $unitObject = $planet->objects->getUnitObjectById($unit_id); + $units->addUnit($unitObject, (int)$value); + } + } + + return $units; + } } From ccb78d9fe5078a2bb732d94ae4791cd40c20dcb8 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 2 May 2024 00:07:09 +0200 Subject: [PATCH 02/11] Refactor tests --- tests/AccountTestCase.php | 70 ++++ tests/Feature/FleetDispatchColoniseTest.php | 88 +++++ tests/Feature/FleetDispatchDeployTest.php | 4 +- tests/Feature/FleetDispatchTransportTest.php | 12 +- tests/FleetDispatchSelfTestCase.php | 370 ++++++++++++++++++ tests/FleetDispatchTestCase.php | 374 +++---------------- 6 files changed, 586 insertions(+), 332 deletions(-) create mode 100644 tests/Feature/FleetDispatchColoniseTest.php create mode 100644 tests/FleetDispatchSelfTestCase.php diff --git a/tests/AccountTestCase.php b/tests/AccountTestCase.php index 77c96f50..7a0ffe66 100644 --- a/tests/AccountTestCase.php +++ b/tests/AccountTestCase.php @@ -5,6 +5,8 @@ use Exception; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Testing\TestResponse; +use OGame\Factories\PlanetServiceFactory; +use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; use OGame\Services\PlanetService; use OGame\Services\PlayerService; @@ -92,6 +94,74 @@ protected function getSecondPlayerId(): int return $playerIds[0]; } + /** + * Gets a nearby foreign planet for the current user. This is useful for testing interactions between two players. + * + * @return PlanetService + * @throws BindingResolutionException + */ + protected function getNearbyForeignPlanet(): PlanetService + { + // Find a planet of another player that is close to the current player by checking the same galaxy + // and up to 10 systems away. + $planet_id = \DB::table('planets') + ->where('user_id', '!=', $this->currentUserId) + ->where('galaxy', $this->planetService->getPlanetCoordinates()->galaxy) + ->whereBetween('system', [$this->planetService->getPlanetCoordinates()->system - 10, $this->planetService->getPlanetCoordinates()->system + 10]) + ->inRandomOrder() + ->limit(1) + ->pluck('id'); + + if ($planet_id == null) { + // No planets found, attempt to create a new user to see if this fixes it. + $this->createAndLoginUser(); + $planet_id = \DB::table('planets') + ->where('user_id', '!=', $this->currentUserId) + ->where('galaxy', $this->planetService->getPlanetCoordinates()->galaxy) + ->whereBetween('system', [$this->planetService->getPlanetCoordinates()->system - 10, $this->planetService->getPlanetCoordinates()->system + 10]) + ->inRandomOrder() + ->limit(1) + ->pluck('id'); + } + + if ($planet_id == null) { + $this->fail('Failed to find a nearby foreign planet for testing.'); + } + else { + // Create and return a new PlanetService instance for the found planet. + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + return $planetServiceFactory->make($planet_id[0]); + } + } + + /** + * Gets a nearby empty coordinate for the current user. This is useful for testing interactions towards empty planets. + * + * @param int $min_position + * @param int $max_position + * @return Coordinate + */ + protected function getNearbyEmptyCoordinate(int $min_position = 4, int $max_position = 12): Coordinate + { + // Find a position that has no planet in the same galaxy and up to 10 systems away between position 4-13. + $coordinate = new Coordinate($this->planetService->getPlanetCoordinates()->galaxy, 0, 0); + $tryCount = 0; + while ($tryCount < 100) { + $tryCount++; + $coordinate->system = $this->planetService->getPlanetCoordinates()->system + rand(-10, 10); + $coordinate->position = rand($min_position, $max_position); + $planetCount = \DB::table('planets') + ->where('galaxy', $coordinate->galaxy) + ->where('system', $coordinate->system) + ->where('planet', $coordinate->position) + ->count(); + if ($planetCount == 0) { + return $coordinate; + } + } + + $this->fail('Failed to find an empty coordinate for testing.'); + } /** * Add resources to current users current planet. diff --git a/tests/Feature/FleetDispatchColoniseTest.php b/tests/Feature/FleetDispatchColoniseTest.php new file mode 100644 index 00000000..45d3c37a --- /dev/null +++ b/tests/Feature/FleetDispatchColoniseTest.php @@ -0,0 +1,88 @@ +planetSetObjectLevel('robot_factory', 2); + // Set shipyard to level 1. + $this->planetSetObjectLevel('shipyard', 1); + // Set the research lab to level 1. + $this->planetSetObjectLevel('research_lab', 1); + // Set energy technology to level 1. + $this->playerSetResearchLevel('energy_technology', 1); + // Set combustion drive to level 1. + $this->playerSetResearchLevel('combustion_drive', 1); + // Add light cargo ship to the planet. + $this->planetAddUnit('small_cargo', 5); + // Add colony ship to the planet. + $this->planetAddUnit('colony_ship', 1); + } + + /** + * @throws BindingResolutionException + */ + public function testDispatchFleetCheckTargetResponse(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that check request to dispatch fleet to empty position succeeds with colony ship. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('colony_ship'), 1); + + // TODO: finish test so it makes a request to "/ajax/fleet/dispatch/check-target" and asserts the response. + // TODO: also add this check to the other fleet dispatch tests. + } + + /** + * @throws BindingResolutionException + */ + public function testDispatchFleetToNotEmptyPositionFails(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Send fleet to a planet position that is already colonized. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('colony_ship'), 1); + // Expecting 500 error. + $this->sendMissionToOtherPlayer($unitCollection, new Resources(0, 0, 0, 0), 500); + } +} diff --git a/tests/Feature/FleetDispatchDeployTest.php b/tests/Feature/FleetDispatchDeployTest.php index 8c3b15e4..966e744d 100644 --- a/tests/Feature/FleetDispatchDeployTest.php +++ b/tests/Feature/FleetDispatchDeployTest.php @@ -5,12 +5,12 @@ use Illuminate\Support\Carbon; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Resources; -use Tests\FleetDispatchTestCase; +use Tests\FleetDispatchSelfTestCase; /** * Test that fleet dispatch works as expected. */ -class FleetDispatchDeployTest extends FleetDispatchTestCase +class FleetDispatchDeployTest extends FleetDispatchSelfTestCase { /** * @var int The mission type for the test. diff --git a/tests/Feature/FleetDispatchTransportTest.php b/tests/Feature/FleetDispatchTransportTest.php index dac97b32..fef7771a 100644 --- a/tests/Feature/FleetDispatchTransportTest.php +++ b/tests/Feature/FleetDispatchTransportTest.php @@ -2,16 +2,17 @@ namespace Feature; +use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Carbon; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Message; use OGame\Models\Resources; -use Tests\FleetDispatchTestCase; +use Tests\FleetDispatchSelfTestCase; /** * Test that fleet dispatch works as expected. */ -class FleetDispatchTransportTest extends FleetDispatchTestCase +class FleetDispatchTransportTest extends FleetDispatchSelfTestCase { /** * @var int The mission type for the test. @@ -48,6 +49,9 @@ protected function messageCheckMissionReturn(): void ]); } + /** + * @throws BindingResolutionException + */ public function testDispatchFleetToOtherPlayer(): void { $this->basicSetup(); @@ -59,7 +63,7 @@ public function testDispatchFleetToOtherPlayer(): void // Send fleet to a planet of another player. $unitCollection = new UnitCollection(); $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $secondPlayer = $this->sendMissionToOtherPlayer($unitCollection, new Resources(100, 0, 0, 0)); + $foreignPlanet = $this->sendMissionToOtherPlayer($unitCollection, new Resources(100, 0, 0, 0)); // Increase time by 10 hours to ensure the mission is done. Carbon::setTestNow($startTime->copy()->addHours(10)); @@ -69,7 +73,7 @@ public function testDispatchFleetToOtherPlayer(): void $response->assertStatus(200); // Assert that last message sent to second player contains the transport confirm message. - $lastMessage = Message::where('user_id', $secondPlayer->getId()) + $lastMessage = Message::where('user_id', $foreignPlanet->getPlayer()->getId()) ->orderBy('id', 'desc') ->first(); diff --git a/tests/FleetDispatchSelfTestCase.php b/tests/FleetDispatchSelfTestCase.php new file mode 100644 index 00000000..0cbb20e4 --- /dev/null +++ b/tests/FleetDispatchSelfTestCase.php @@ -0,0 +1,370 @@ +planetSetObjectLevel('robot_factory', 2); + // Set shipyard to level 1. + $this->planetSetObjectLevel('shipyard', 1); + // Set the research lab to level 1. + $this->planetSetObjectLevel('research_lab', 1); + // Set energy technology to level 1. + $this->playerSetResearchLevel('energy_technology', 1); + // Set combustion drive to level 1. + $this->playerSetResearchLevel('combustion_drive', 1); + // Add light cargo ship to the planet. + $this->planetAddUnit('small_cargo', 5); + } + + protected abstract function messageCheckMissionArrival(): void; + protected abstract function messageCheckMissionReturn(): void; + + /** + * Verify that dispatching a fleet deducts correct amount of units from planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductUnits(): void + { + $this->basicSetup(); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 4, 'Small Cargo ship not deducted from planet after fleet dispatch.'); + } + + /** + * Verify that dispatching a fleet deducts correct amount of resources from planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductResources(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Get beginning resources of the planet. + $beginningMetal = $this->planetService->metal()->get(); + $beginningCrystal = $this->planetService->crystal()->get(); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + $response = $this->get('/shipyard'); + $response->assertStatus(200); + + // Assert that the resources were deducted from the planet. + $this->assertResourcesOnPage($response, new Resources($beginningMetal - 100, $beginningCrystal - 100, 0, 0)); + } + + /** + * Verify that dispatching a fleet with more resources than is on planet fails. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductTooMuchResources(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(4500, 100, 0, 0), 500); + } + + /** + * Verify that dispatching a fleet with more units than is on planet fails. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductTooMuchUnits(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 10); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0), 500); + } + + /** + * Verify that dispatching a fleet launches a return trip and brings back units to origin planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetReturnTrip(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Get time it takes for the fleet to travel to the second planet. + $fleetMissionDuration = $fleetMissionService->calculateFleetMissionDuration($fleetMission); + + // Set time to fleet mission duration + 30 seconds (we do 30 instead of 1 second to test later if the return trip start and endtime work as expected + // and are calculated based on the arrival time instead of the time the job got processed). + $fleetParentTime = $startTime->copy()->addSeconds($fleetMissionDuration + 30); + Carbon::setTestNow($fleetParentTime); + + // Set all messages as read to avoid unread messages count in the overview. + $this->playerSetAllMessagesRead(); + + // Do a request to trigger the update logic. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the fleet mission is processed. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->processed == 1, 'Fleet mission is not processed after fleet has arrived at destination.'); + + // Check that message has been received by calling extended method + $this->messageCheckMissionArrival(); + + $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); + if ($this->hasReturnMission) { + // Assert that a return trip has been launched by checking the active missions for the current planet. + $this->assertCount(1, $activeMissions, 'No return trip launched after fleet has arrived at destination.'); + + // Advance time to the return trip arrival. + $returnTripDuration = $activeMissions->first()->time_arrival - $activeMissions->first()->time_departure; + + $fleetReturnTime = $fleetParentTime->copy()->addSeconds($returnTripDuration + 1); + Carbon::setTestNow($fleetReturnTime); + + // Do a request to trigger the update logic. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the return trip has been processed. + $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); + $this->assertCount(0, $activeMissions, 'Return trip is not processed after fleet has arrived back at origin planet.'); + + // Assert that the units have been returned to the origin planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units after return trip.'); + + $this->messageCheckMissionReturn(); + } + else { + // Assert that NO return trip has been launched by checking the active missions for the current planet. + $this->assertCount(0, $activeMissions, 'Return trip launched after fleet with deployment mission has arrived at destination.'); + } + } + + /** + * Verify that an active mission also shows the (not yet existing) return trip in the fleet event list. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetReturnShown(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // The eventbox should only show 1 mission (the parent). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + + // The event list should show either 1 or 2 missions (the parent and the to-be-created return trip). + $response = $this->get('/ajax/fleet/eventlist/fetch'); + $response->assertStatus(200); + if ($this->hasReturnMission) { + // If the mission has a return mission, we should see both in the event list. + $response->assertSee($this->missionName); + $response->assertSee($this->missionName . ' (R)'); + // Assert that we see both rows in the event list. + $response->assertSee('data-return-flight="false"', false); + $response->assertSee('data-return-flight="true"', false); + } + else { + // If the mission does not have a return mission, we should only see the parent mission. + $response->assertSee($this->missionName); + $response->assertDontSee($this->missionName . ' (R)'); + // Assert that we see only parent row in the event list. + $response->assertSee('data-return-flight="false"', false); + $response->assertDontSee('data-return-flight="true"', false); + } + } + + /** + * Verify that canceling/recalling an active mission works. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetRecallMission(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Advance time by 1 minute + $fleetParentTime = $startTime->copy()->addMinutes(1); + Carbon::setTestNow($fleetParentTime); + + // Cancel the mission + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + $response->assertStatus(200); + + // Assert that the original mission is now canceled. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->canceled == 1, 'Fleet mission is not canceled after fleet recall is requested.'); + + // Assert that only the return trip is now visible. + // The eventbox should only show 1 mission (the parent). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + } + + /** + * Verify that canceling/recalling an active mission twice results in an error. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetRecallMissionTwiceError(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Advance time by 1 minute + $fleetParentTime = $startTime->copy()->addMinutes(1); + Carbon::setTestNow($fleetParentTime); + + // Cancel the mission + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + $response->assertStatus(200); + + // Cancel it again + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + // Expecting a 500 error because the mission is already canceled. + $response->assertStatus(500); + + // The eventbox should only show 1 mission (the first recalled mission). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + } +} diff --git a/tests/FleetDispatchTestCase.php b/tests/FleetDispatchTestCase.php index 33ca3247..748b5d0e 100644 --- a/tests/FleetDispatchTestCase.php +++ b/tests/FleetDispatchTestCase.php @@ -2,12 +2,10 @@ namespace Tests; -use Exception; use Illuminate\Contracts\Container\BindingResolutionException; -use Illuminate\Support\Carbon; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Resources; -use OGame\Services\FleetMissionService; +use OGame\Services\PlanetService; use OGame\Services\PlayerService; /** @@ -43,27 +41,17 @@ abstract class FleetDispatchTestCase extends AccountTestCase * @return void * @throws BindingResolutionException */ - protected function basicSetup(): void - { - // Set the robotics factory to level 2 - $this->planetSetObjectLevel('robot_factory', 2); - // Set shipyard to level 1. - $this->planetSetObjectLevel('shipyard', 1); - // Set the research lab to level 1. - $this->planetSetObjectLevel('research_lab', 1); - // Set energy technology to level 1. - $this->playerSetResearchLevel('energy_technology', 1); - // Set combustion drive to level 1. - $this->playerSetResearchLevel('combustion_drive', 1); - // Add light cargo ship to the planet. - $this->planetAddUnit('small_cargo', 5); - } - - abstract protected function messageCheckMissionArrival(): void; - abstract protected function messageCheckMissionReturn(): void; + protected abstract function basicSetup(): void; - protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $resources, int $assertStatus = 200): void - { + /** + * Send a fleet to the second planet of the test user. + * + * @param UnitCollection $units + * @param Resources $resources + * @param int $assertStatus + * @return void + */ + protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $resources, int $assertStatus = 200) : void { // Convert units to array. $unitsArray = []; foreach ($units->units as $unit) { @@ -91,20 +79,20 @@ protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $r $this->get('/ajax/fleet/eventlist/fetch')->assertStatus(200); } - protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $resources, int $assertStatus = 200): PlayerService - { + /** + * Send a fleet to a planet of another player. + * + * @throws BindingResolutionException + */ + protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $resources, int $assertStatus = 200): PlanetService { // Convert units to array. $unitsArray = []; foreach ($units->units as $unit) { $unitsArray['am' . $unit->unitObject->id] = $unit->amount; } - // Get the first planet of the other player. - // TODO: would be better to retrieve a planet near the current player for travel times and deut consumption etc. - $secondPlayerId = $this->getSecondPlayerId(); - $playerFactory = app()->make(\OGame\Factories\PlayerServiceFactory::class); - $secondPlayerService = $playerFactory->make($secondPlayerId); - $secondPlayerPlanet = $secondPlayerService->planets->first(); + // Get a planet of another nearby player. + $secondPlayerPlanet = $this->getNearbyForeignPlanet(); // Send fleet to the second planet of the test user. $post = $this->post('/ajax/fleet/dispatch/send-fleet', array_merge([ @@ -126,311 +114,45 @@ protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $re $this->get('/ajax/fleet/eventbox/fetch')->assertStatus(200); $this->get('/ajax/fleet/eventlist/fetch')->assertStatus(200); - return $secondPlayerService; - } - - /** - * Verify that dispatching a fleet deducts correct amount of units from planet. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetDeductUnits(): void - { - $this->basicSetup(); - - // Assert that we begin with 5 small cargo ships on planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 4, 'Small Cargo ship not deducted from planet after fleet dispatch.'); - } - - /** - * Verify that dispatching a fleet deducts correct amount of resources from planet. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetDeductResources(): void - { - $this->basicSetup(); - $this->get('/shipyard'); - - // Get beginning resources of the planet. - $beginningMetal = $this->planetService->metal()->get(); - $beginningCrystal = $this->planetService->crystal()->get(); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - $response = $this->get('/shipyard'); - $response->assertStatus(200); - - // Assert that the resources were deducted from the planet. - $this->assertResourcesOnPage($response, new Resources($beginningMetal - 100, $beginningCrystal - 100, 0, 0)); - } - - /** - * Verify that dispatching a fleet with more resources than is on planet fails. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetDeductTooMuchResources(): void - { - $this->basicSetup(); - $this->get('/shipyard'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(4500, 100, 0, 0), 500); - } - - /** - * Verify that dispatching a fleet with more units than is on planet fails. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetDeductTooMuchUnits(): void - { - $this->basicSetup(); - $this->get('/shipyard'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 10); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0), 500); - } - - /** - * Verify that dispatching a fleet launches a return trip and brings back units to origin planet. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetReturnTrip(): void - { - $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Assert that we begin with 5 small cargo ships on planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - // Get just dispatched fleet mission ID from database. - $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); - $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); - $fleetMissionId = $fleetMission->id; - - // Get time it takes for the fleet to travel to the second planet. - $fleetMissionDuration = $fleetMissionService->calculateFleetMissionDuration($fleetMission); - - // Set time to fleet mission duration + 30 seconds (we do 30 instead of 1 second to test later if the return trip start and endtime work as expected - // and are calculated based on the arrival time instead of the time the job got processed). - $fleetParentTime = $startTime->copy()->addSeconds($fleetMissionDuration + 30); - Carbon::setTestNow($fleetParentTime); - - // Set all messages as read to avoid unread messages count in the overview. - $this->playerSetAllMessagesRead(); - - // Do a request to trigger the update logic. - $response = $this->get('/overview'); - $response->assertStatus(200); - - // Assert that the fleet mission is processed. - $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); - $this->assertTrue($fleetMission->processed == 1, 'Fleet mission is not processed after fleet has arrived at destination.'); - - // Check that message has been received by calling extended method - $this->messageCheckMissionArrival(); - - $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); - if ($this->hasReturnMission) { - // Assert that a return trip has been launched by checking the active missions for the current planet. - $this->assertCount(1, $activeMissions, 'No return trip launched after fleet has arrived at destination.'); - - // Advance time to the return trip arrival. - $returnTripDuration = $activeMissions->first()->time_arrival - $activeMissions->first()->time_departure; - - $fleetReturnTime = $fleetParentTime->copy()->addSeconds($returnTripDuration + 1); - Carbon::setTestNow($fleetReturnTime); - - // Do a request to trigger the update logic. - $response = $this->get('/overview'); - $response->assertStatus(200); - - // Assert that the return trip has been processed. - $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); - $this->assertCount(0, $activeMissions, 'Return trip is not processed after fleet has arrived back at origin planet.'); - - // Assert that the units have been returned to the origin planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units after return trip.'); - - $this->messageCheckMissionReturn(); - } else { - // Assert that NO return trip has been launched by checking the active missions for the current planet. - $this->assertCount(0, $activeMissions, 'Return trip launched after fleet with deployment mission has arrived at destination.'); - } + return $secondPlayerPlanet; } /** - * Verify that an active mission also shows the (not yet existing) return trip in the fleet event list. - * @throws BindingResolutionException - * @throws Exception + * Send a fleet to an empty position. + * + * @param UnitCollection $units + * @param Resources $resources + * @param int $assertStatus + * @return void */ - public function testDispatchFleetReturnShown(): void - { - $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - // The eventbox should only show 1 mission (the parent). - $response = $this->get('/ajax/fleet/eventbox/fetch'); - $response->assertStatus(200); - $response->assertJsonFragment(['friendly' => 1]); - - // The event list should show either 1 or 2 missions (the parent and the to-be-created return trip). - $response = $this->get('/ajax/fleet/eventlist/fetch'); - $response->assertStatus(200); - if ($this->hasReturnMission) { - // If the mission has a return mission, we should see both in the event list. - $response->assertSee($this->missionName); - $response->assertSee($this->missionName . ' (R)'); - // Assert that we see both rows in the event list. - $response->assertSee('data-return-flight="false"', false); - $response->assertSee('data-return-flight="true"', false); - } else { - // If the mission does not have a return mission, we should only see the parent mission. - $response->assertSee($this->missionName); - $response->assertDontSee($this->missionName . ' (R)'); - // Assert that we see only parent row in the event list. - $response->assertSee('data-return-flight="false"', false); - $response->assertDontSee('data-return-flight="true"', false); + protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): void { + // Convert units to array. + $unitsArray = []; + foreach ($units->units as $unit) { + $unitsArray['am' . $unit->unitObject->id] = $unit->amount; } - } - - /** - * Verify that canceling/recalling an active mission works. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetRecallMission(): void - { - $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Assert that we begin with 5 small cargo ships on planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - // Get just dispatched fleet mission ID from database. - $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); - $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); - $fleetMissionId = $fleetMission->id; - - // Advance time by 1 minute - $fleetParentTime = $startTime->copy()->addMinutes(1); - Carbon::setTestNow($fleetParentTime); - - // Cancel the mission - $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ - 'fleet_mission_id' => $fleetMissionId, - '_token' => csrf_token(), - ]); - $response->assertStatus(200); - - // Assert that the original mission is now canceled. - $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); - $this->assertTrue($fleetMission->canceled == 1, 'Fleet mission is not canceled after fleet recall is requested.'); - // Assert that only the return trip is now visible. - // The eventbox should only show 1 mission (the parent). - $response = $this->get('/ajax/fleet/eventbox/fetch'); - $response->assertStatus(200); - $response->assertJsonFragment(['friendly' => 1]); - $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); - } - - /** - * Verify that canceling/recalling an active mission twice results in an error. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetRecallMissionTwiceError(): void - { - $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Assert that we begin with 5 small cargo ships on planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + // Get a planet of another nearby player. + $emptyPosition = $this->getNearbyEmptyCoordinate(); // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - // Get just dispatched fleet mission ID from database. - $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); - $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); - $fleetMissionId = $fleetMission->id; - - // Advance time by 1 minute - $fleetParentTime = $startTime->copy()->addMinutes(1); - Carbon::setTestNow($fleetParentTime); - - // Cancel the mission - $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ - 'fleet_mission_id' => $fleetMissionId, + $post = $this->post('/ajax/fleet/dispatch/send-fleet', array_merge([ + 'galaxy' => $emptyPosition->galaxy, + 'system' => $emptyPosition->system, + 'position' => $emptyPosition->position, + 'type' => 1, + 'mission' => $this->missionType, + 'metal' => $resources->metal->get(), + 'crystal' => $resources->crystal->get(), + 'deuterium' => $resources->deuterium->get(), '_token' => csrf_token(), - ]); - $response->assertStatus(200); + ], $unitsArray)); - // Cancel it again - $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ - 'fleet_mission_id' => $fleetMissionId, - '_token' => csrf_token(), - ]); - // Expecting a 500 error because the mission is already canceled. - $response->assertStatus(500); + // Assert that the fleet was dispatched successfully. + $post->assertStatus($assertStatus); - // The eventbox should only show 1 mission (the first recalled mission). - $response = $this->get('/ajax/fleet/eventbox/fetch'); - $response->assertStatus(200); - $response->assertJsonFragment(['friendly' => 1]); - $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + // Assert that eventbox fetch works when a fleet mission is active. + $this->get('/ajax/fleet/eventbox/fetch')->assertStatus(200); + $this->get('/ajax/fleet/eventlist/fetch')->assertStatus(200); } } From 1f2543f241a74407663e356a6b545955500cb2e8 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 2 May 2024 00:14:42 +0200 Subject: [PATCH 03/11] Update FleetDispatchSelfTestCase.php --- tests/FleetDispatchSelfTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FleetDispatchSelfTestCase.php b/tests/FleetDispatchSelfTestCase.php index 0cbb20e4..b25c4413 100644 --- a/tests/FleetDispatchSelfTestCase.php +++ b/tests/FleetDispatchSelfTestCase.php @@ -2,12 +2,12 @@ namespace Tests; +use Exception; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Carbon; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Resources; use OGame\Services\FleetMissionService; -use OGame\Services\PlayerService; /** * Base class to test that fleet missions work as expected. From 7030a7b1d73d21fb8b5f469f1f4de5456f08877c Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 3 May 2024 22:32:26 +0200 Subject: [PATCH 04/11] Code style refactor, fix bugs --- app/Factories/GameMissionFactory.php | 5 +++-- app/GameMissions/Abstracts/GameMission.php | 2 +- app/GameMissions/ColonisationMission.php | 5 ++--- app/Http/Controllers/FleetController.php | 13 ++++++++----- tests/AccountTestCase.php | 3 +-- tests/FleetDispatchSelfTestCase.php | 12 +++++------- tests/FleetDispatchTestCase.php | 12 +++++++----- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/app/Factories/GameMissionFactory.php b/app/Factories/GameMissionFactory.php index 7226a85b..2ed3b82d 100644 --- a/app/Factories/GameMissionFactory.php +++ b/app/Factories/GameMissionFactory.php @@ -14,7 +14,8 @@ class GameMissionFactory * @return array * @throws BindingResolutionException */ - public static function getAllMissions(): array { + public static function getAllMissions(): array + { /* { "1": "Attack", @@ -35,4 +36,4 @@ public static function getAllMissions(): array { app()->make(ColonisationMission::class), ]; } -} \ No newline at end of file +} diff --git a/app/GameMissions/Abstracts/GameMission.php b/app/GameMissions/Abstracts/GameMission.php index 39bbf708..1b3387a4 100644 --- a/app/GameMissions/Abstracts/GameMission.php +++ b/app/GameMissions/Abstracts/GameMission.php @@ -63,7 +63,7 @@ public static function getTypeId(): int * @param UnitCollection $units * @return MissionPossibleStatus */ - public abstract function isMissionPossible(PlanetService $planet, ?PlanetService $targetPlanet, UnitCollection $units): MissionPossibleStatus; + abstract public function isMissionPossible(PlanetService $planet, ?PlanetService $targetPlanet, UnitCollection $units): MissionPossibleStatus; /** * Cancel an already started mission. diff --git a/app/GameMissions/ColonisationMission.php b/app/GameMissions/ColonisationMission.php index 4273e267..ced492a0 100644 --- a/app/GameMissions/ColonisationMission.php +++ b/app/GameMissions/ColonisationMission.php @@ -24,8 +24,7 @@ public function isMissionPossible(PlanetService $planet, ?PlanetService $targetP // Check if a colony ship is present in the fleet if ($units->getAmountByMachineName('colony_ship') > 0) { return new MissionPossibleStatus(true); - } - else { + } else { // Return error message return new MissionPossibleStatus(false, __('You need a colony ship to colonize a planet.')); } @@ -46,4 +45,4 @@ protected function processReturn(FleetMission $mission): void { // TODO: Implement the return logic } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/FleetController.php b/app/Http/Controllers/FleetController.php index 5f8d55f7..f7ae42d6 100644 --- a/app/Http/Controllers/FleetController.php +++ b/app/Http/Controllers/FleetController.php @@ -14,6 +14,7 @@ use OGame\Models\Resources; use OGame\Services\FleetMissionService; use OGame\Services\ObjectService; +use OGame\Services\PlanetService; use OGame\Services\PlayerService; use OGame\ViewModels\UnitViewModel; @@ -82,7 +83,8 @@ public function movement(): View /** * @throws BindingResolutionException */ - public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService $objects) : JsonResponse { + public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService $objects): JsonResponse + { $currentPlanet = $currentPlayer->planets->current(); // Return ships data for this planet taking into account the current planet's properties and research levels. @@ -130,8 +132,7 @@ public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService $possible = $mission->isMissionPossible($currentPlanet, $targetPlanet, $units); if ($possible->possible) { $enabledMissions[] = $mission::getTypeId(); - } - else if (!empty($possible->error)) { + } elseif (!empty($possible->error)) { // If the mission is not possible and has an error message, return error message in JSON. $errors[] = [ 'message' => $possible->error, @@ -190,8 +191,9 @@ public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService /** * Handles the dispatch of a fleet. * + * @param PlayerService $player * @return JsonResponse - * @throws Exception + * @throws BindingResolutionException */ public function dispatchSendFleet(PlayerService $player): JsonResponse { @@ -286,7 +288,8 @@ public function dispatchRecallFleet(): JsonResponse * @return UnitCollection * @throws Exception */ - private function getUnitsFromRequest(PlanetService $planet): UnitCollection { + private function getUnitsFromRequest(PlanetService $planet): UnitCollection + { $units = new UnitCollection(); foreach (request()->all() as $key => $value) { if (str_starts_with($key, 'am')) { diff --git a/tests/AccountTestCase.php b/tests/AccountTestCase.php index 7a0ffe66..af20f677 100644 --- a/tests/AccountTestCase.php +++ b/tests/AccountTestCase.php @@ -126,8 +126,7 @@ protected function getNearbyForeignPlanet(): PlanetService if ($planet_id == null) { $this->fail('Failed to find a nearby foreign planet for testing.'); - } - else { + } else { // Create and return a new PlanetService instance for the found planet. $planetServiceFactory = app()->make(PlanetServiceFactory::class); return $planetServiceFactory->make($planet_id[0]); diff --git a/tests/FleetDispatchSelfTestCase.php b/tests/FleetDispatchSelfTestCase.php index b25c4413..0b8a06af 100644 --- a/tests/FleetDispatchSelfTestCase.php +++ b/tests/FleetDispatchSelfTestCase.php @@ -58,8 +58,8 @@ protected function basicSetup(): void $this->planetAddUnit('small_cargo', 5); } - protected abstract function messageCheckMissionArrival(): void; - protected abstract function messageCheckMissionReturn(): void; + abstract protected function messageCheckMissionArrival(): void; + abstract protected function messageCheckMissionReturn(): void; /** * Verify that dispatching a fleet deducts correct amount of units from planet. @@ -184,7 +184,7 @@ public function testDispatchFleetReturnTrip(): void $response->assertStatus(200); // Assert that the fleet mission is processed. - $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); $this->assertTrue($fleetMission->processed == 1, 'Fleet mission is not processed after fleet has arrived at destination.'); // Check that message has been received by calling extended method @@ -214,8 +214,7 @@ public function testDispatchFleetReturnTrip(): void $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units after return trip.'); $this->messageCheckMissionReturn(); - } - else { + } else { // Assert that NO return trip has been launched by checking the active missions for the current planet. $this->assertCount(0, $activeMissions, 'Return trip launched after fleet with deployment mission has arrived at destination.'); } @@ -254,8 +253,7 @@ public function testDispatchFleetReturnShown(): void // Assert that we see both rows in the event list. $response->assertSee('data-return-flight="false"', false); $response->assertSee('data-return-flight="true"', false); - } - else { + } else { // If the mission does not have a return mission, we should only see the parent mission. $response->assertSee($this->missionName); $response->assertDontSee($this->missionName . ' (R)'); diff --git a/tests/FleetDispatchTestCase.php b/tests/FleetDispatchTestCase.php index 748b5d0e..4077796c 100644 --- a/tests/FleetDispatchTestCase.php +++ b/tests/FleetDispatchTestCase.php @@ -6,7 +6,6 @@ use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Resources; use OGame\Services\PlanetService; -use OGame\Services\PlayerService; /** * Base class to test that fleet missions work as expected. @@ -41,7 +40,7 @@ abstract class FleetDispatchTestCase extends AccountTestCase * @return void * @throws BindingResolutionException */ - protected abstract function basicSetup(): void; + abstract protected function basicSetup(): void; /** * Send a fleet to the second planet of the test user. @@ -51,7 +50,8 @@ protected abstract function basicSetup(): void; * @param int $assertStatus * @return void */ - protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $resources, int $assertStatus = 200) : void { + protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $resources, int $assertStatus = 200): void + { // Convert units to array. $unitsArray = []; foreach ($units->units as $unit) { @@ -84,7 +84,8 @@ protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $r * * @throws BindingResolutionException */ - protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $resources, int $assertStatus = 200): PlanetService { + protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $resources, int $assertStatus = 200): PlanetService + { // Convert units to array. $unitsArray = []; foreach ($units->units as $unit) { @@ -125,7 +126,8 @@ protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $re * @param int $assertStatus * @return void */ - protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): void { + protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): void + { // Convert units to array. $unitsArray = []; foreach ($units->units as $unit) { From 4b8299587a4cb677768268dfb303eebd886fa8a3 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 4 May 2024 01:03:24 +0200 Subject: [PATCH 05/11] Add logic and extend tests --- CONTRIBUTING.md | 2 + app/GameMissions/Abstracts/GameMission.php | 42 +++-- app/GameMissions/ColonisationMission.php | 17 +- app/GameMissions/DeploymentMission.php | 19 ++- app/GameMissions/TransportMission.php | 21 ++- app/Http/Controllers/FleetController.php | 1 + app/Services/FleetMissionService.php | 10 +- app/Services/PlanetService.php | 11 +- .../FleetDispatchColoniseTest.php | 62 ++++--- .../FleetDispatchDeployTest.php | 4 +- .../FleetDispatchTransportTest.php | 4 +- tests/Feature/ResearchQueueCancelTest.php | 6 - tests/Feature/ResearchQueueTest.php | 6 - tests/Feature/UnitQueueTest.php | 8 - tests/FleetDispatchSelfTestCase.php | 76 ++++++++- tests/FleetDispatchTestCase.php | 154 ++++++++++-------- 16 files changed, 295 insertions(+), 148 deletions(-) rename tests/Feature/{ => FleetDispatch}/FleetDispatchColoniseTest.php (56%) rename tests/Feature/{ => FleetDispatch}/FleetDispatchDeployTest.php (96%) rename tests/Feature/{ => FleetDispatch}/FleetDispatchTransportTest.php (97%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f6b699a..2a622279 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,8 @@ If you have an idea or suggestion, feel free to start a discussion on the [Discu ## Pull Requests If you would like to contribute via pull requests, a good way to get started is to filter the issues list by the [good first issues](https://github.com/lanedirt/OGameX/labels/good%20first%20issue) label. This label is used for issues that are easy to fix and a good starting point for new contributors. +[![good first issues open](https://img.shields.io/github/issues/lanedirt/OGameX/good%20first%20issue.svg?logo=github)](https://github.com/lanedirt/OGameX/issues?q=is%3Aopen+is%3Aissue+label%3A"good+first+issue") + When submitting a pull request, please make sure to follow these guidelines: ### 1. PSR-12 Coding Standard diff --git a/app/GameMissions/Abstracts/GameMission.php b/app/GameMissions/Abstracts/GameMission.php index 1b3387a4..964ba17f 100644 --- a/app/GameMissions/Abstracts/GameMission.php +++ b/app/GameMissions/Abstracts/GameMission.php @@ -2,6 +2,7 @@ namespace OGame\GameMissions\Abstracts; +use Exception; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Carbon; use OGame\Factories\PlanetServiceFactory; @@ -84,28 +85,28 @@ public function cancel(FleetMission $mission): void } /** - * Generic sanity checks before starting a mission to make sure the planet has enough resources and units. + * Generic sanity checks before starting a mission to make sure all requirements are met. * * @param PlanetService $planet * @param UnitCollection $units * @param Resources $resources * @return void - * @throws \Exception + * @throws Exception */ public function startMissionSanityChecks(PlanetService $planet, UnitCollection $units, Resources $resources): void { if (!$planet->hasResources($resources)) { - throw new \Exception('Not enough resources on the planet to send the fleet.'); + throw new Exception('Not enough resources on the planet to send the fleet.'); } if (!$planet->hasUnits($units)) { - throw new \Exception('Not enough units on the planet to send the fleet.'); + throw new Exception('Not enough units on the planet to send the fleet.'); } } /** * Deduct mission resources from the planet (when starting mission). * - * @throws \Exception + * @throws Exception */ public function deductMissionResources(PlanetService $planet, Resources $resources, UnitCollection $units): void { @@ -122,7 +123,7 @@ public function deductMissionResources(PlanetService $planet, Resources $resourc * @param Resources $resources * @param int $parent_id * @return void - * @throws \Exception + * @throws Exception */ public function start(PlanetService $planet, PlanetService $targetPlanet, UnitCollection $units, Resources $resources, int $parent_id = 0): void { @@ -132,7 +133,7 @@ public function start(PlanetService $planet, PlanetService $targetPlanet, UnitCo $time_start = (int)Carbon::now()->timestamp; // Time fleet mission will arrive - //TODO: refactor calculate to gamemission base class? + // TODO: refactor calculate to gamemission base class? $time_end = $time_start + $this->fleetMissionService->calculateFleetMissionDuration(); $mission = new FleetMission(); @@ -151,17 +152,15 @@ public function start(PlanetService $planet, PlanetService $targetPlanet, UnitCo $mission->time_arrival = $time_end; $mission->planet_id_to = $targetPlanet->getPlanetId(); - // Coordinates $coords = $targetPlanet->getPlanetCoordinates(); $mission->galaxy_to = $coords->galaxy; $mission->system_to = $coords->system; $mission->position_to = $coords->position; - // Define units foreach ($units->units as $unit) { $mission->{$unit->unitObject->machine_name} = $unit->amount; } - // Define resources + $mission->metal = $resources->metal->getRounded(); $mission->crystal = $resources->crystal->getRounded(); $mission->deuterium = $resources->deuterium->getRounded(); @@ -178,7 +177,7 @@ public function start(PlanetService $planet, PlanetService $targetPlanet, UnitCo * * @param FleetMission $parentMission * @return void - * @throws \Illuminate\Contracts\Container\BindingResolutionException + * @throws BindingResolutionException */ protected function startReturn(FleetMission $parentMission): void { @@ -219,9 +218,21 @@ protected function startReturn(FleetMission $parentMission): void // Fill in the resources. Return missions do not carry resources as they have been // offloaded at the target planet. + // TODO: this assumption is not true for all mission types. Refactor this to be more flexible. + // TODO: attack and expedition missions should be able to carry "new" resources back. + // TODO: also if transport mission has been started but is then canceled, the resources should be returned. + // Change this current implementation to be more flexible! + // Add unittest for this in mission to self cancel test. $mission->metal = 0; $mission->crystal = 0; $mission->deuterium = 0; + if ($parentMission->canceled == 1) { + // If the parent mission was canceled, return the resources to the source planet via the return mission. + // TODO: do we want to clear the resources from the parent mission or leave as-is for bookkeeping purposes? + $mission->metal = $parentMission->metal; + $mission->crystal = $parentMission->crystal; + $mission->deuterium = $parentMission->deuterium; + } // Save the new fleet return mission. $mission->save(); @@ -235,14 +246,13 @@ protected function startReturn(FleetMission $parentMission): void */ public function process(FleetMission $mission): void { - if (!empty($mission->parent_id)) { - // This is a return mission as it has a parent mission. - $this->processReturn($mission); - return; - } else { + if (empty($mission->parent_id)) { // This is an arrival mission as it has no parent mission. // Process arrival. $this->processArrival($mission); + } else { + // This is a return mission as it has a parent mission. + $this->processReturn($mission); } } diff --git a/app/GameMissions/ColonisationMission.php b/app/GameMissions/ColonisationMission.php index ced492a0..2e6ea098 100644 --- a/app/GameMissions/ColonisationMission.php +++ b/app/GameMissions/ColonisationMission.php @@ -2,11 +2,13 @@ namespace OGame\GameMissions; +use Exception; use Illuminate\Contracts\Container\BindingResolutionException; use OGame\GameMissions\Abstracts\GameMission; use OGame\GameMissions\Models\MissionPossibleStatus; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\FleetMission; +use OGame\Models\Resources; use OGame\Services\PlanetService; class ColonisationMission extends GameMission @@ -15,6 +17,20 @@ class ColonisationMission extends GameMission protected static int $typeId = 7; protected static bool $hasReturnMission = true; + public function startMissionSanityChecks(PlanetService $planet, UnitCollection $units, Resources $resources): void + { + // Call the parent method + parent::startMissionSanityChecks($planet, $units, $resources); + + if ($units->getAmountByMachineName('colony_ship') == 0) { + throw new Exception(__('You need a colony ship to colonize a planet.')); + } + + if ($planet->getPlayer() != null) { + throw new Exception(__('You can only colonize empty planets.')); + } + } + /** * @inheritdoc */ @@ -34,7 +50,6 @@ public function isMissionPossible(PlanetService $planet, ?PlanetService $targetP } /** - * @throws BindingResolutionException */ protected function processArrival(FleetMission $mission): void { diff --git a/app/GameMissions/DeploymentMission.php b/app/GameMissions/DeploymentMission.php index a0108084..d950622b 100644 --- a/app/GameMissions/DeploymentMission.php +++ b/app/GameMissions/DeploymentMission.php @@ -70,10 +70,25 @@ protected function processReturn(FleetMission $mission): void // Transport return trip: add back the units to the source planet. Then we're done. $target_planet->addUnits($this->fleetMissionService->getFleetUnits($mission)); - // Send message to player that the return mission has arrived - $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet]. + // Add resources to the origin planet (if any). + // TODO: make messages translatable by using tokens instead of directly inserting dynamic content. + $return_resources = $this->fleetMissionService->getResources($mission); + if ($return_resources->sum() > 0) { + $target_planet->addResources($return_resources); + + // Send message to player that the return mission has arrived + $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet] and delivered its goods: + +Metal: ' . $mission->metal . ' +Crystal: ' . $mission->crystal . ' +Deuterium: ' . $mission->deuterium, 'return_of_fleet'); + } + else { + // Send message to player that the return mission has arrived + $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet]. The fleet doesn\'t deliver goods.', 'return_of_fleet'); + } // Mark the return mission as processed $mission->processed = 1; diff --git a/app/GameMissions/TransportMission.php b/app/GameMissions/TransportMission.php index 52bebac2..7b7e7c6e 100644 --- a/app/GameMissions/TransportMission.php +++ b/app/GameMissions/TransportMission.php @@ -73,10 +73,25 @@ protected function processReturn(FleetMission $mission): void // Transport return trip: add back the units to the source planet. $target_planet->addUnits($this->fleetMissionService->getFleetUnits($mission)); - // Send message to player that the return mission has arrived - $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet]. + // Add resources to the origin planet (if any). + // TODO: make messages translatable by using tokens instead of directly inserting dynamic content. + $return_resources = $this->fleetMissionService->getResources($mission); + if ($return_resources->sum() > 0) { + $target_planet->addResources($return_resources); + + // Send message to player that the return mission has arrived + $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet] and delivered its goods: + +Metal: ' . $mission->metal . ' +Crystal: ' . $mission->crystal . ' +Deuterium: ' . $mission->deuterium, 'return_of_fleet'); + } + else { + // Send message to player that the return mission has arrived + $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet]. - The fleet doesn\'t deliver goods.', 'return_of_fleet'); + The fleet doesn\'t deliver goods.', 'return_of_fleet'); + } // Mark the return mission as processed $mission->processed = 1; diff --git a/app/Http/Controllers/FleetController.php b/app/Http/Controllers/FleetController.php index f7ae42d6..4d1ba634 100644 --- a/app/Http/Controllers/FleetController.php +++ b/app/Http/Controllers/FleetController.php @@ -194,6 +194,7 @@ public function dispatchCheckTarget(PlayerService $currentPlayer, ObjectService * @param PlayerService $player * @return JsonResponse * @throws BindingResolutionException + * @throws Exception */ public function dispatchSendFleet(PlayerService $player): JsonResponse { diff --git a/app/Services/FleetMissionService.php b/app/Services/FleetMissionService.php index 720ef9d0..a508c031 100644 --- a/app/Services/FleetMissionService.php +++ b/app/Services/FleetMissionService.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Carbon; +use OGame\GameMissions\ColonisationMission; use OGame\GameMissions\DeploymentMission; use OGame\GameMissions\TransportMission; use OGame\GameObjects\Models\UnitCollection; @@ -103,6 +104,8 @@ public function calculateMaxSpeed(): int */ public function calculateFleetMissionDuration(): int { + // TODO: make the calculation dynamic based on the current planet, target coordinates and fleet + // (including research levels for speed). return 300; } @@ -266,8 +269,13 @@ public function createNewFromPlanet(PlanetService $planet, PlanetService $target 'messageService' => $this->messageService, ]); $deployMission->start($planet, $targetPlanet, $units, $resources, $parent_id); + } elseif ($missionType == 7) { + $deployMission = app()->make(ColonisationMission::class, [ + 'fleetMissionService' => $this, + 'messageService' => $this->messageService, + ]); + $deployMission->start($planet, $targetPlanet, $units, $resources, $parent_id); } - return; } /** diff --git a/app/Services/PlanetService.php b/app/Services/PlanetService.php index 8db71f7e..0213309f 100644 --- a/app/Services/PlanetService.php +++ b/app/Services/PlanetService.php @@ -8,6 +8,7 @@ use OGame\Factories\PlayerServiceFactory; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Planet; +use OGame\Models\Planet\Coordinate; use OGame\Models\Resource; use OGame\Models\Resources; @@ -94,9 +95,9 @@ public function loadByPlanetId(int $id): void /** * Get the player object who owns this planet. * - * @return PlayerService + * @return ?PlayerService */ - public function getPlayer(): PlayerService + public function getPlayer(): ?PlayerService { return $this->player; } @@ -134,12 +135,12 @@ public function getPlanetName(): string /** * Get planet coordinates in array. * - * @return Planet\Coordinate + * @return Coordinate * Array with coordinates (galaxy, system, planet) */ - public function getPlanetCoordinates(): Planet\Coordinate + public function getPlanetCoordinates(): Coordinate { - return new Planet\Coordinate($this->planet->galaxy, $this->planet->system, $this->planet->planet); + return new Coordinate($this->planet->galaxy, $this->planet->system, $this->planet->planet); } /** diff --git a/tests/Feature/FleetDispatchColoniseTest.php b/tests/Feature/FleetDispatch/FleetDispatchColoniseTest.php similarity index 56% rename from tests/Feature/FleetDispatchColoniseTest.php rename to tests/Feature/FleetDispatch/FleetDispatchColoniseTest.php index 45d3c37a..cb72e485 100644 --- a/tests/Feature/FleetDispatchColoniseTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchColoniseTest.php @@ -1,9 +1,9 @@ planetSetObjectLevel('robot_factory', 2); - // Set shipyard to level 1. $this->planetSetObjectLevel('shipyard', 1); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); - // Set energy technology to level 1. $this->playerSetResearchLevel('energy_technology', 1); - // Set combustion drive to level 1. $this->playerSetResearchLevel('combustion_drive', 1); - // Add light cargo ship to the planet. $this->planetAddUnit('small_cargo', 5); - // Add colony ship to the planet. $this->planetAddUnit('colony_ship', 1); } /** + * Assert that check request to dispatch fleet to empty position succeeds with colony ship. + * * @throws BindingResolutionException + * @throws Exception */ - public function testDispatchFleetCheckTargetResponse(): void + public function testFleetCheckWithColonyShipSuccess(): void { $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Assert that check request to dispatch fleet to empty position succeeds with colony ship. $unitCollection = new UnitCollection(); $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('colony_ship'), 1); + $this->fleetCheckToEmptyPosition($unitCollection, true); + } - // TODO: finish test so it makes a request to "/ajax/fleet/dispatch/check-target" and asserts the response. - // TODO: also add this check to the other fleet dispatch tests. + /** + * Assert that check request to dispatch fleet to empty position fails without colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckWithoutColonyShipError(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToEmptyPosition($unitCollection, false); } /** + * Send fleet to a planet position that is already colonized. + * * @throws BindingResolutionException + * @throws Exception */ public function testDispatchFleetToNotEmptyPositionFails(): void { $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Send fleet to a planet position that is already colonized. $unitCollection = new UnitCollection(); $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('colony_ship'), 1); // Expecting 500 error. $this->sendMissionToOtherPlayer($unitCollection, new Resources(0, 0, 0, 0), 500); } + + /** + * Send fleet to empty planet without colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetWithoutColonyShipFails(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + // Expecting 500 error. + $this->sendMissionToEmptyPosition($unitCollection, new Resources(0, 0, 0, 0), 500); + } } diff --git a/tests/Feature/FleetDispatchDeployTest.php b/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php similarity index 96% rename from tests/Feature/FleetDispatchDeployTest.php rename to tests/Feature/FleetDispatch/FleetDispatchDeployTest.php index 966e744d..ff4549cc 100644 --- a/tests/Feature/FleetDispatchDeployTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php @@ -1,7 +1,9 @@ planetAddResources(new Resources(0, 10000, 5000, 0)); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); // Set the current time to a specific moment for testing @@ -88,9 +86,7 @@ public function testResearchQueueCancelMultiple(): void */ public function testResearchQueueCancelRefundResources(): void { - // Add resources to planet that test requires. $this->planetAddResources(new Resources(0, 800, 400, 0)); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); // Set the current time to a specific moment for testing @@ -144,9 +140,7 @@ public function testResearchQueueCancelRefundResources(): void */ public function testBuildQueueCancelSecondEntry(): void { - // Add resources to planet that test requires. $this->planetAddResources(new Resources(0, 1600, 1600, 0)); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); // Set the current time to a specific moment for testing diff --git a/tests/Feature/ResearchQueueTest.php b/tests/Feature/ResearchQueueTest.php index 7e06f944..6ba3761e 100644 --- a/tests/Feature/ResearchQueueTest.php +++ b/tests/Feature/ResearchQueueTest.php @@ -20,9 +20,7 @@ class ResearchQueueTest extends AccountTestCase */ public function testResearchQueueEnergyTechnology(): void { - // Add resources to planet that test requires. $this->planetAddResources(new Resources(0, 800, 400, 0)); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); // Set the current time to a specific moment for testing @@ -72,9 +70,7 @@ public function testResearchQueueEnergyTechnology(): void */ public function testResearchQueueMultiQueue(): void { - // Add resources to planet that test requires. $this->planetAddResources(new Resources(0, 2400, 1200, 0)); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); // Set the current time to a specific moment for testing @@ -125,9 +121,7 @@ public function testResearchQueueMultiQueue(): void */ public function testResearchQueueCancelRefundResources(): void { - // Add resources to planet that test requires. $this->planetAddResources(new Resources(0, 800, 400, 0)); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); // Set the current time to a specific moment for testing diff --git a/tests/Feature/UnitQueueTest.php b/tests/Feature/UnitQueueTest.php index 1cf2f10f..7550a0b5 100644 --- a/tests/Feature/UnitQueueTest.php +++ b/tests/Feature/UnitQueueTest.php @@ -21,15 +21,10 @@ class UnitQueueTest extends AccountTestCase */ private function basicSetup(): void { - // Set the robotics factory to level 2 $this->planetSetObjectLevel('robot_factory', 2); - // Set shipyard to level 1. $this->planetSetObjectLevel('shipyard', 1); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); - // Set energy technology to level 1. $this->playerSetResearchLevel('energy_technology', 1); - // Set combustion drive to level 1. $this->playerSetResearchLevel('combustion_drive', 1); } @@ -157,11 +152,8 @@ public function testUnitQueueDefense(): void { $this->basicSetup(); - // Add resources to planet that test requires. $this->planetAddResources(new Resources(20000, 0, 0, 0)); - // Set the robotics factory to level 2 $this->planetSetObjectLevel('robot_factory', 2); - // Set shipyard to level 1. $this->planetSetObjectLevel('shipyard', 1); // Set the current time to a specific moment for testing diff --git a/tests/FleetDispatchSelfTestCase.php b/tests/FleetDispatchSelfTestCase.php index 0b8a06af..6915e535 100644 --- a/tests/FleetDispatchSelfTestCase.php +++ b/tests/FleetDispatchSelfTestCase.php @@ -44,23 +44,59 @@ abstract class FleetDispatchSelfTestCase extends FleetDispatchTestCase */ protected function basicSetup(): void { - // Set the robotics factory to level 2 $this->planetSetObjectLevel('robot_factory', 2); - // Set shipyard to level 1. $this->planetSetObjectLevel('shipyard', 1); - // Set the research lab to level 1. $this->planetSetObjectLevel('research_lab', 1); - // Set energy technology to level 1. $this->playerSetResearchLevel('energy_technology', 1); - // Set combustion drive to level 1. $this->playerSetResearchLevel('combustion_drive', 1); - // Add light cargo ship to the planet. $this->planetAddUnit('small_cargo', 5); } abstract protected function messageCheckMissionArrival(): void; abstract protected function messageCheckMissionReturn(): void; + /** + * Assert that check request to dispatch fleet to empty position succeeds with colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToOwnPlanetSuccess(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToSecondPlanet($unitCollection, true); + } + + /** + * Assert that check request to dispatch fleet to foreign planet position fails without colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToForeignPlanetError(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToOtherPlayer($unitCollection, false); + } + + /** + * Assert that check request to dispatch fleet to empty position fails without colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToEmptyPlanetError(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToEmptyPosition($unitCollection, false); + } + /** * Verify that dispatching a fleet deducts correct amount of units from planet. * @throws BindingResolutionException @@ -272,6 +308,9 @@ public function testDispatchFleetRecallMission(): void { $this->basicSetup(); + // Add resources for test. + $this->planetAddResources(new Resources(5000, 5000, 0, 0)); + // Set time to static time 2024-01-01 $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); Carbon::setTestNow($startTime); @@ -282,8 +321,8 @@ public function testDispatchFleetRecallMission(): void // Send fleet to the second planet of the test user. $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 5); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(5000, 5000, 0, 0)); // Get just dispatched fleet mission ID from database. $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); @@ -311,6 +350,27 @@ public function testDispatchFleetRecallMission(): void $response->assertStatus(200); $response->assertJsonFragment(['friendly' => 1]); $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + + // Advance time by amount of minutes it takes for the return trip to arrive. + Carbon::setTestNow(Carbon::createFromTimestamp($fleetMission->time_arrival)); + + // Do a request to trigger the update logic. + $this->get('/overview'); + + // Assert that the return trip is processed. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->processed == 1, 'Return trip is not processed after fleet has arrived back at origin planet.'); + + // Assert that the units have been returned to the origin planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at original 5 units after recalled trip has been processed.'); + // Assert that the resources have been returned to the origin planet. + $this->assertTrue($this->planetService->hasResources(new Resources(5000, 5000, 0, 0)), 'Resources are not returned to origin planet after recalling mission.'); } /** diff --git a/tests/FleetDispatchTestCase.php b/tests/FleetDispatchTestCase.php index 4077796c..ed22846c 100644 --- a/tests/FleetDispatchTestCase.php +++ b/tests/FleetDispatchTestCase.php @@ -3,7 +3,9 @@ namespace Tests; use Illuminate\Contracts\Container\BindingResolutionException; +use Illuminate\Support\Carbon; use OGame\GameObjects\Models\UnitCollection; +use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; use OGame\Services\PlanetService; @@ -42,6 +44,27 @@ abstract class FleetDispatchTestCase extends AccountTestCase */ abstract protected function basicSetup(): void; + public function fleetCheckToSecondPlanet(UnitCollection $units, bool $assertSuccess): void + { + $coordinates = $this->secondPlanetService->getPlanetCoordinates(); + $this->checkTargetFleet($coordinates, $units, $assertSuccess); + } + + /** + * @throws BindingResolutionException + */ + public function fleetCheckToOtherPlayer(UnitCollection $units, bool $assertSuccess): void + { + $nearbyForeignPlanet = $this->getNearbyForeignPlanet(); + $this->checkTargetFleet($nearbyForeignPlanet->getPlanetCoordinates(), $units, $assertSuccess); + } + + public function fleetCheckToEmptyPosition(UnitCollection $units, bool $assertSuccess): void + { + $coordinates = $this->getNearbyEmptyCoordinate(); + $this->checkTargetFleet($coordinates, $units, $assertSuccess); + } + /** * Send a fleet to the second planet of the test user. * @@ -50,33 +73,9 @@ abstract protected function basicSetup(): void; * @param int $assertStatus * @return void */ - protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $resources, int $assertStatus = 200): void - { - // Convert units to array. - $unitsArray = []; - foreach ($units->units as $unit) { - $unitsArray['am' . $unit->unitObject->id] = $unit->amount; - } - - // Send fleet to the second planet of the test user. - $post = $this->post('/ajax/fleet/dispatch/send-fleet', array_merge([ - 'galaxy' => $this->secondPlanetService->getPlanetCoordinates()->galaxy, - 'system' => $this->secondPlanetService->getPlanetCoordinates()->system, - 'position' => $this->secondPlanetService->getPlanetCoordinates()->position, - 'type' => 1, - 'mission' => $this->missionType, - 'metal' => $resources->metal->get(), - 'crystal' => $resources->crystal->get(), - 'deuterium' => $resources->deuterium->get(), - '_token' => csrf_token(), - ], $unitsArray)); - - // Assert that the fleet was dispatched successfully. - $post->assertStatus($assertStatus); - - // Assert that eventbox fetch works when a fleet mission is active. - $this->get('/ajax/fleet/eventbox/fetch')->assertStatus(200); - $this->get('/ajax/fleet/eventlist/fetch')->assertStatus(200); + protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $resources, int $assertStatus = 200): void { + $coordinates = $this->secondPlanetService->getPlanetCoordinates(); + $this->dispatchFleet($coordinates, $units, $resources, $assertStatus); } /** @@ -84,64 +83,90 @@ protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $r * * @throws BindingResolutionException */ - protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $resources, int $assertStatus = 200): PlanetService - { - // Convert units to array. + protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $resources, int $assertStatus = 200): PlanetService { + $nearbyForeignPlanet = $this->getNearbyForeignPlanet(); + $this->dispatchFleet($nearbyForeignPlanet->getPlanetCoordinates(), $units, $resources, $assertStatus); + return $nearbyForeignPlanet; + } + + /** + * Send a fleet to an empty position. + * + * @param UnitCollection $units + * @param Resources $resources + * @param int $assertStatus + * @return void + */ + protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): void { + $coordinates = $this->getNearbyEmptyCoordinate(); + $this->dispatchFleet($coordinates, $units, $resources, $assertStatus); + } + + /** + * Convert units to dispatchable array format. + * + * @param UnitCollection $units + * @return array + */ + private function convertUnitsToArray(UnitCollection $units): array { $unitsArray = []; foreach ($units->units as $unit) { $unitsArray['am' . $unit->unitObject->id] = $unit->amount; } + return $unitsArray; + } - // Get a planet of another nearby player. - $secondPlayerPlanet = $this->getNearbyForeignPlanet(); + /** + * Call check-target fleet method with the given units and coordinates. + * + * @param Coordinate $coordinates + * @param UnitCollection $units + * @param bool $assertSuccess + * @return void + */ + protected function checkTargetFleet(Coordinate $coordinates, UnitCollection $units, bool $assertSuccess): void { + $unitsArray = $this->convertUnitsToArray($units); - // Send fleet to the second planet of the test user. - $post = $this->post('/ajax/fleet/dispatch/send-fleet', array_merge([ - 'galaxy' => $secondPlayerPlanet->getPlanetCoordinates()->galaxy, - 'system' => $secondPlayerPlanet->getPlanetCoordinates()->system, - 'position' => $secondPlayerPlanet->getPlanetCoordinates()->position, + $post = $this->post('/ajax/fleet/dispatch/check-target', array_merge([ + 'galaxy' => $coordinates->galaxy, + 'system' => $coordinates->system, + 'position' => $coordinates->position, 'type' => 1, 'mission' => $this->missionType, - 'metal' => $resources->metal->get(), - 'crystal' => $resources->crystal->get(), - 'deuterium' => $resources->deuterium->get(), '_token' => csrf_token(), ], $unitsArray)); - // Assert that the fleet was dispatched successfully. - $post->assertStatus($assertStatus); - - // Assert that eventbox fetch works when a fleet mission is active. - $this->get('/ajax/fleet/eventbox/fetch')->assertStatus(200); - $this->get('/ajax/fleet/eventlist/fetch')->assertStatus(200); - - return $secondPlayerPlanet; + // Check request should always result in success HTTP call, even if the mission is not possible. + // All errors should be included in the JSON response. + $post->assertStatus(200); + + // Assert that JSON response has the correct status and mission type. + if ($assertSuccess) { + $post->assertJson([ + 'status' => 'success', + 'orders' => [ + $this->missionType => true, + ] + ]); + } } /** - * Send a fleet to an empty position. + * Dispatch a fleet to a specified location. * + * @param Coordinate $coordinates * @param UnitCollection $units * @param Resources $resources * @param int $assertStatus * @return void */ - protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): void - { - // Convert units to array. - $unitsArray = []; - foreach ($units->units as $unit) { - $unitsArray['am' . $unit->unitObject->id] = $unit->amount; - } + protected function dispatchFleet(Coordinate $coordinates, UnitCollection $units, Resources $resources, int $assertStatus = 200): void { + $unitsArray = $this->convertUnitsToArray($units); - // Get a planet of another nearby player. - $emptyPosition = $this->getNearbyEmptyCoordinate(); - - // Send fleet to the second planet of the test user. $post = $this->post('/ajax/fleet/dispatch/send-fleet', array_merge([ - 'galaxy' => $emptyPosition->galaxy, - 'system' => $emptyPosition->system, - 'position' => $emptyPosition->position, + 'galaxy' => $coordinates->galaxy, + 'system' => $coordinates->system, + 'position' => $coordinates->position, 'type' => 1, 'mission' => $this->missionType, 'metal' => $resources->metal->get(), @@ -150,10 +175,7 @@ protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $ '_token' => csrf_token(), ], $unitsArray)); - // Assert that the fleet was dispatched successfully. $post->assertStatus($assertStatus); - - // Assert that eventbox fetch works when a fleet mission is active. $this->get('/ajax/fleet/eventbox/fetch')->assertStatus(200); $this->get('/ajax/fleet/eventlist/fetch')->assertStatus(200); } From 24c2f2b9af2fff409eeaf6fd828ba8829025c873 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 4 May 2024 01:05:41 +0200 Subject: [PATCH 06/11] Code style refactor --- app/GameMissions/ColonisationMission.php | 1 - app/GameMissions/DeploymentMission.php | 3 +-- app/GameMissions/TransportMission.php | 3 +-- .../FleetDispatch/FleetDispatchDeployTest.php | 2 -- tests/FleetDispatchTestCase.php | 19 ++++++++++++------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/GameMissions/ColonisationMission.php b/app/GameMissions/ColonisationMission.php index 2e6ea098..9a213e12 100644 --- a/app/GameMissions/ColonisationMission.php +++ b/app/GameMissions/ColonisationMission.php @@ -3,7 +3,6 @@ namespace OGame\GameMissions; use Exception; -use Illuminate\Contracts\Container\BindingResolutionException; use OGame\GameMissions\Abstracts\GameMission; use OGame\GameMissions\Models\MissionPossibleStatus; use OGame\GameObjects\Models\UnitCollection; diff --git a/app/GameMissions/DeploymentMission.php b/app/GameMissions/DeploymentMission.php index d950622b..1c770309 100644 --- a/app/GameMissions/DeploymentMission.php +++ b/app/GameMissions/DeploymentMission.php @@ -82,8 +82,7 @@ protected function processReturn(FleetMission $mission): void Metal: ' . $mission->metal . ' Crystal: ' . $mission->crystal . ' Deuterium: ' . $mission->deuterium, 'return_of_fleet'); - } - else { + } else { // Send message to player that the return mission has arrived $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet]. diff --git a/app/GameMissions/TransportMission.php b/app/GameMissions/TransportMission.php index 7b7e7c6e..9294c3bc 100644 --- a/app/GameMissions/TransportMission.php +++ b/app/GameMissions/TransportMission.php @@ -85,8 +85,7 @@ protected function processReturn(FleetMission $mission): void Metal: ' . $mission->metal . ' Crystal: ' . $mission->crystal . ' Deuterium: ' . $mission->deuterium, 'return_of_fleet'); - } - else { + } else { // Send message to player that the return mission has arrived $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet]. diff --git a/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php b/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php index ff4549cc..1ac4d277 100644 --- a/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php @@ -2,8 +2,6 @@ namespace Feature\FleetDispatch; -use Exception; -use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Carbon; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Resources; diff --git a/tests/FleetDispatchTestCase.php b/tests/FleetDispatchTestCase.php index ed22846c..f30e0f33 100644 --- a/tests/FleetDispatchTestCase.php +++ b/tests/FleetDispatchTestCase.php @@ -3,7 +3,6 @@ namespace Tests; use Illuminate\Contracts\Container\BindingResolutionException; -use Illuminate\Support\Carbon; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; @@ -73,7 +72,8 @@ public function fleetCheckToEmptyPosition(UnitCollection $units, bool $assertSuc * @param int $assertStatus * @return void */ - protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $resources, int $assertStatus = 200): void { + protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $resources, int $assertStatus = 200): void + { $coordinates = $this->secondPlanetService->getPlanetCoordinates(); $this->dispatchFleet($coordinates, $units, $resources, $assertStatus); } @@ -83,7 +83,8 @@ protected function sendMissionToSecondPlanet(UnitCollection $units, Resources $r * * @throws BindingResolutionException */ - protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $resources, int $assertStatus = 200): PlanetService { + protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $resources, int $assertStatus = 200): PlanetService + { $nearbyForeignPlanet = $this->getNearbyForeignPlanet(); $this->dispatchFleet($nearbyForeignPlanet->getPlanetCoordinates(), $units, $resources, $assertStatus); return $nearbyForeignPlanet; @@ -97,7 +98,8 @@ protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $re * @param int $assertStatus * @return void */ - protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): void { + protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): void + { $coordinates = $this->getNearbyEmptyCoordinate(); $this->dispatchFleet($coordinates, $units, $resources, $assertStatus); } @@ -108,7 +110,8 @@ protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $ * @param UnitCollection $units * @return array */ - private function convertUnitsToArray(UnitCollection $units): array { + private function convertUnitsToArray(UnitCollection $units): array + { $unitsArray = []; foreach ($units->units as $unit) { $unitsArray['am' . $unit->unitObject->id] = $unit->amount; @@ -124,7 +127,8 @@ private function convertUnitsToArray(UnitCollection $units): array { * @param bool $assertSuccess * @return void */ - protected function checkTargetFleet(Coordinate $coordinates, UnitCollection $units, bool $assertSuccess): void { + protected function checkTargetFleet(Coordinate $coordinates, UnitCollection $units, bool $assertSuccess): void + { $unitsArray = $this->convertUnitsToArray($units); $post = $this->post('/ajax/fleet/dispatch/check-target', array_merge([ @@ -160,7 +164,8 @@ protected function checkTargetFleet(Coordinate $coordinates, UnitCollection $uni * @param int $assertStatus * @return void */ - protected function dispatchFleet(Coordinate $coordinates, UnitCollection $units, Resources $resources, int $assertStatus = 200): void { + protected function dispatchFleet(Coordinate $coordinates, UnitCollection $units, Resources $resources, int $assertStatus = 200): void + { $unitsArray = $this->convertUnitsToArray($units); $post = $this->post('/ajax/fleet/dispatch/send-fleet', array_merge([ From 10e8787569e6b8f3fc0b793091fd852a8a3e598c Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 4 May 2024 15:35:25 +0200 Subject: [PATCH 07/11] Refactor test abstractions --- tests/AccountTestCase.php | 75 +++ tests/CreatesApplication.php | 23 - tests/DatabaseTestCase.php | 17 - .../FleetDispatch/FleetDispatchDeployTest.php | 394 +++++++++++++++- .../FleetDispatchTransportTest.php | 392 +++++++++++++++- tests/FleetDispatchSelfTestCase.php | 428 ------------------ tests/FleetDispatchTestCase.php | 36 +- tests/TestCase.php | 79 +--- 8 files changed, 883 insertions(+), 561 deletions(-) delete mode 100644 tests/CreatesApplication.php delete mode 100644 tests/DatabaseTestCase.php delete mode 100644 tests/FleetDispatchSelfTestCase.php diff --git a/tests/AccountTestCase.php b/tests/AccountTestCase.php index af20f677..a57ac582 100644 --- a/tests/AccountTestCase.php +++ b/tests/AccountTestCase.php @@ -4,8 +4,10 @@ use Exception; use Illuminate\Contracts\Container\BindingResolutionException; +use Illuminate\Support\Str; use Illuminate\Testing\TestResponse; use OGame\Factories\PlanetServiceFactory; +use OGame\Models\Planet; use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; use OGame\Services\PlanetService; @@ -35,6 +37,79 @@ protected function setUp(): void $this->retrieveMetaFields(); } + protected PlanetService $planetService; + + /** + * Create a new user and login via the register form on login page. + * + * @return void + */ + protected function createAndLoginUser(): void + { + // First go to logout page to ensure we are not logged in. + $this->post('/logout'); + + $response = $this->get('/login'); + + // Check for existence of register form + $response->assertSee('subscribeForm'); + + // Simulate form data + // Generate random email + $randomEmail = Str::random(10) . '@example.com'; + + $formData = [ + '_token' => csrf_token(), + 'email' => $randomEmail, + 'password' => 'asdasdasd', + 'v' => '3', + 'step' => 'validate', + 'kid' => '', + 'errorCodeOn' => '1', + 'is_utf8' => '1', + 'agb' => 'on', + ]; + + // Submit the registration form + $response = $this->post('/register', $formData); + if ($response->status() !== 302) { + var_dump($response->getContent()); + $this->fail('Failed to register account. Response status: ' . $response->status(). '. Check the logs.'); + } + + // Check if we are authenticated after registration. + $this->assertAuthenticated(); + } + + /** + * Helper method to create a planet model and configure it. + * + * @param array $attributes + */ + protected function createAndSetPlanetModel(array $attributes): void + { + // Create fake planet eloquent model with additional attributes + $planetModelFake = Planet::factory()->make($attributes); + // Set the fake model to the planet service + $this->planetService->setPlanet($planetModelFake); + } + + /** + * Set up the planet service for testing. + * + * @return void + * @throws BindingResolutionException + */ + protected function setUpPlanetService(): void + { + // Initialize empty playerService object directly without factory as we do not + // actually want to load a player from the database. + $playerService = app()->make(PlayerService::class, ['player_id' => 0]); + // Initialize the planet service with factory. + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + $this->planetService = $planetServiceFactory->makeForPlayer($playerService, 0); + } + /** * Retrieve meta fields from page response to extract player id and planet id. * diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php deleted file mode 100644 index ed8efc0c..00000000 --- a/tests/CreatesApplication.php +++ /dev/null @@ -1,23 +0,0 @@ -make(Kernel::class)->bootstrap(); - - return $app; - } -} diff --git a/tests/DatabaseTestCase.php b/tests/DatabaseTestCase.php deleted file mode 100644 index 46ad0603..00000000 --- a/tests/DatabaseTestCase.php +++ /dev/null @@ -1,17 +0,0 @@ -planetSetObjectLevel('robot_factory', 2); + $this->planetSetObjectLevel('shipyard', 1); + $this->planetSetObjectLevel('research_lab', 1); + $this->playerSetResearchLevel('energy_technology', 1); + $this->playerSetResearchLevel('combustion_drive', 1); + $this->planetAddUnit('small_cargo', 5); + } + protected function messageCheckMissionArrival(): void { // Assert that message has been sent to player and contains the correct information. @@ -47,6 +66,48 @@ protected function messageCheckMissionReturn(): void ]); } + /** + * Assert that check request to dispatch fleet to empty position succeeds with colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToOwnPlanetSuccess(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToSecondPlanet($unitCollection, true); + } + + /** + * Assert that check request to dispatch fleet to foreign planet position fails without colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToForeignPlanetError(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToOtherPlayer($unitCollection, false); + } + + /** + * Assert that check request to dispatch fleet to empty position fails without colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToEmptyPlanetError(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToEmptyPosition($unitCollection, false); + } + public function testDispatchFleetReturnTripWithoutResources(): void { $this->basicSetup(); @@ -78,4 +139,333 @@ public function testDispatchFleetReturnTripWithoutResources(): void $this->secondPlanetService->getPlanetName() ]); } + + /** + * Verify that dispatching a fleet deducts correct amount of units from planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductUnits(): void + { + $this->basicSetup(); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 4, 'Small Cargo ship not deducted from planet after fleet dispatch.'); + } + + /** + * Verify that dispatching a fleet deducts correct amount of resources from planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductResources(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Get beginning resources of the planet. + $beginningMetal = $this->planetService->metal()->get(); + $beginningCrystal = $this->planetService->crystal()->get(); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + $response = $this->get('/shipyard'); + $response->assertStatus(200); + + // Assert that the resources were deducted from the planet. + $this->assertResourcesOnPage($response, new Resources($beginningMetal - 100, $beginningCrystal - 100, 0, 0)); + } + + /** + * Verify that dispatching a fleet with more resources than is on planet fails. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductTooMuchResources(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(4500, 100, 0, 0), 500); + } + + /** + * Verify that dispatching a fleet with more units than is on planet fails. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductTooMuchUnits(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 10); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0), 500); + } + + /** + * Verify that dispatching a fleet launches a return trip and brings back units to origin planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetReturnTrip(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Get time it takes for the fleet to travel to the second planet. + $fleetMissionDuration = $fleetMissionService->calculateFleetMissionDuration($fleetMission); + + // Set time to fleet mission duration + 30 seconds (we do 30 instead of 1 second to test later if the return trip start and endtime work as expected + // and are calculated based on the arrival time instead of the time the job got processed). + $fleetParentTime = $startTime->copy()->addSeconds($fleetMissionDuration + 30); + Carbon::setTestNow($fleetParentTime); + + // Set all messages as read to avoid unread messages count in the overview. + $this->playerSetAllMessagesRead(); + + // Do a request to trigger the update logic. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the fleet mission is processed. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->processed == 1, 'Fleet mission is not processed after fleet has arrived at destination.'); + + // Check that message has been received by calling extended method + $this->messageCheckMissionArrival(); + + $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); + if ($this->hasReturnMission) { + // Assert that a return trip has been launched by checking the active missions for the current planet. + $this->assertCount(1, $activeMissions, 'No return trip launched after fleet has arrived at destination.'); + + // Advance time to the return trip arrival. + $returnTripDuration = $activeMissions->first()->time_arrival - $activeMissions->first()->time_departure; + + $fleetReturnTime = $fleetParentTime->copy()->addSeconds($returnTripDuration + 1); + Carbon::setTestNow($fleetReturnTime); + + // Do a request to trigger the update logic. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the return trip has been processed. + $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); + $this->assertCount(0, $activeMissions, 'Return trip is not processed after fleet has arrived back at origin planet.'); + + // Assert that the units have been returned to the origin planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units after return trip.'); + + $this->messageCheckMissionReturn(); + } else { + // Assert that NO return trip has been launched by checking the active missions for the current planet. + $this->assertCount(0, $activeMissions, 'Return trip launched after fleet with deployment mission has arrived at destination.'); + } + } + + /** + * Verify that an active mission also shows the (not yet existing) return trip in the fleet event list. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetReturnShown(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // The eventbox should only show 1 mission (the parent). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + + // The event list should show either 1 or 2 missions (the parent and the to-be-created return trip). + $response = $this->get('/ajax/fleet/eventlist/fetch'); + $response->assertStatus(200); + if ($this->hasReturnMission) { + // If the mission has a return mission, we should see both in the event list. + $response->assertSee($this->missionName); + $response->assertSee($this->missionName . ' (R)'); + // Assert that we see both rows in the event list. + $response->assertSee('data-return-flight="false"', false); + $response->assertSee('data-return-flight="true"', false); + } else { + // If the mission does not have a return mission, we should only see the parent mission. + $response->assertSee($this->missionName); + $response->assertDontSee($this->missionName . ' (R)'); + // Assert that we see only parent row in the event list. + $response->assertSee('data-return-flight="false"', false); + $response->assertDontSee('data-return-flight="true"', false); + } + } + + /** + * Verify that canceling/recalling an active mission works. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetRecallMission(): void + { + $this->basicSetup(); + + // Add resources for test. + $this->planetAddResources(new Resources(5000, 5000, 0, 0)); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 5); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(5000, 5000, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Advance time by 1 minute + $fleetParentTime = $startTime->copy()->addMinutes(1); + Carbon::setTestNow($fleetParentTime); + + // Cancel the mission + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + $response->assertStatus(200); + + // Assert that the original mission is now canceled. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->canceled == 1, 'Fleet mission is not canceled after fleet recall is requested.'); + + // Assert that only the return trip is now visible. + // The eventbox should only show 1 mission (the parent). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + + // Advance time by amount of minutes it takes for the return trip to arrive. + Carbon::setTestNow(Carbon::createFromTimestamp($fleetMission->time_arrival)); + + // Do a request to trigger the update logic. + $this->get('/overview'); + + // Assert that the return trip is processed. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->processed == 1, 'Return trip is not processed after fleet has arrived back at origin planet.'); + + // Assert that the units have been returned to the origin planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at original 5 units after recalled trip has been processed.'); + // Assert that the resources have been returned to the origin planet. + $this->assertTrue($this->planetService->hasResources(new Resources(5000, 5000, 0, 0)), 'Resources are not returned to origin planet after recalling mission.'); + } + + /** + * Verify that canceling/recalling an active mission twice results in an error. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetRecallMissionTwiceError(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Advance time by 1 minute + $fleetParentTime = $startTime->copy()->addMinutes(1); + Carbon::setTestNow($fleetParentTime); + + // Cancel the mission + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + $response->assertStatus(200); + + // Cancel it again + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + // Expecting a 500 error because the mission is already canceled. + $response->assertStatus(500); + + // The eventbox should only show 1 mission (the first recalled mission). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + } } diff --git a/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php b/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php index 317418b6..8fdb208e 100644 --- a/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php @@ -8,12 +8,13 @@ use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Message; use OGame\Models\Resources; -use Tests\FleetDispatchSelfTestCase; +use OGame\Services\FleetMissionService; +use Tests\FleetDispatchTestCase; /** * Test that fleet dispatch works as expected. */ -class FleetDispatchTransportTest extends FleetDispatchSelfTestCase +class FleetDispatchTransportTest extends FleetDispatchTestCase { /** * @var int The mission type for the test. @@ -27,6 +28,22 @@ class FleetDispatchTransportTest extends FleetDispatchSelfTestCase protected bool $hasReturnMission = true; + /** + * Prepare the planet for the test so it has the required buildings and research. + * + * @return void + * @throws BindingResolutionException + */ + protected function basicSetup(): void + { + $this->planetSetObjectLevel('robot_factory', 2); + $this->planetSetObjectLevel('shipyard', 1); + $this->planetSetObjectLevel('research_lab', 1); + $this->playerSetResearchLevel('energy_technology', 1); + $this->playerSetResearchLevel('combustion_drive', 1); + $this->planetAddUnit('small_cargo', 5); + } + protected function messageCheckMissionArrival(): void { // Assert that message has been sent to player and contains the correct information. @@ -50,6 +67,48 @@ protected function messageCheckMissionReturn(): void ]); } + /** + * Assert that check request to dispatch fleet to empty position succeeds with colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToOwnPlanetSuccess(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToSecondPlanet($unitCollection, true); + } + + /** + * Assert that check request to dispatch fleet to foreign planet position fails without colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToForeignPlanetError(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToOtherPlayer($unitCollection, false); + } + + /** + * Assert that check request to dispatch fleet to empty position fails without colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testFleetCheckToEmptyPlanetError(): void + { + $this->basicSetup(); + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->fleetCheckToEmptyPosition($unitCollection, false); + } + /** * @throws BindingResolutionException * @throws Exception @@ -82,4 +141,333 @@ public function testDispatchFleetToOtherPlayer(): void $this->assertStringContainsString('An incoming fleet from planet', $lastMessage->body); $this->assertStringContainsString('has reached your planet', $lastMessage->body); } + + /** + * Verify that dispatching a fleet deducts correct amount of units from planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductUnits(): void + { + $this->basicSetup(); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 4, 'Small Cargo ship not deducted from planet after fleet dispatch.'); + } + + /** + * Verify that dispatching a fleet deducts correct amount of resources from planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductResources(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Get beginning resources of the planet. + $beginningMetal = $this->planetService->metal()->get(); + $beginningCrystal = $this->planetService->crystal()->get(); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + $response = $this->get('/shipyard'); + $response->assertStatus(200); + + // Assert that the resources were deducted from the planet. + $this->assertResourcesOnPage($response, new Resources($beginningMetal - 100, $beginningCrystal - 100, 0, 0)); + } + + /** + * Verify that dispatching a fleet with more resources than is on planet fails. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductTooMuchResources(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(4500, 100, 0, 0), 500); + } + + /** + * Verify that dispatching a fleet with more units than is on planet fails. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetDeductTooMuchUnits(): void + { + $this->basicSetup(); + $this->get('/shipyard'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 10); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0), 500); + } + + /** + * Verify that dispatching a fleet launches a return trip and brings back units to origin planet. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetReturnTrip(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Get time it takes for the fleet to travel to the second planet. + $fleetMissionDuration = $fleetMissionService->calculateFleetMissionDuration($fleetMission); + + // Set time to fleet mission duration + 30 seconds (we do 30 instead of 1 second to test later if the return trip start and endtime work as expected + // and are calculated based on the arrival time instead of the time the job got processed). + $fleetParentTime = $startTime->copy()->addSeconds($fleetMissionDuration + 30); + Carbon::setTestNow($fleetParentTime); + + // Set all messages as read to avoid unread messages count in the overview. + $this->playerSetAllMessagesRead(); + + // Do a request to trigger the update logic. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the fleet mission is processed. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->processed == 1, 'Fleet mission is not processed after fleet has arrived at destination.'); + + // Check that message has been received by calling extended method + $this->messageCheckMissionArrival(); + + $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); + if ($this->hasReturnMission) { + // Assert that a return trip has been launched by checking the active missions for the current planet. + $this->assertCount(1, $activeMissions, 'No return trip launched after fleet has arrived at destination.'); + + // Advance time to the return trip arrival. + $returnTripDuration = $activeMissions->first()->time_arrival - $activeMissions->first()->time_departure; + + $fleetReturnTime = $fleetParentTime->copy()->addSeconds($returnTripDuration + 1); + Carbon::setTestNow($fleetReturnTime); + + // Do a request to trigger the update logic. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the return trip has been processed. + $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); + $this->assertCount(0, $activeMissions, 'Return trip is not processed after fleet has arrived back at origin planet.'); + + // Assert that the units have been returned to the origin planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units after return trip.'); + + $this->messageCheckMissionReturn(); + } else { + // Assert that NO return trip has been launched by checking the active missions for the current planet. + $this->assertCount(0, $activeMissions, 'Return trip launched after fleet with deployment mission has arrived at destination.'); + } + } + + /** + * Verify that an active mission also shows the (not yet existing) return trip in the fleet event list. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetReturnShown(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // The eventbox should only show 1 mission (the parent). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + + // The event list should show either 1 or 2 missions (the parent and the to-be-created return trip). + $response = $this->get('/ajax/fleet/eventlist/fetch'); + $response->assertStatus(200); + if ($this->hasReturnMission) { + // If the mission has a return mission, we should see both in the event list. + $response->assertSee($this->missionName); + $response->assertSee($this->missionName . ' (R)'); + // Assert that we see both rows in the event list. + $response->assertSee('data-return-flight="false"', false); + $response->assertSee('data-return-flight="true"', false); + } else { + // If the mission does not have a return mission, we should only see the parent mission. + $response->assertSee($this->missionName); + $response->assertDontSee($this->missionName . ' (R)'); + // Assert that we see only parent row in the event list. + $response->assertSee('data-return-flight="false"', false); + $response->assertDontSee('data-return-flight="true"', false); + } + } + + /** + * Verify that canceling/recalling an active mission works. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetRecallMission(): void + { + $this->basicSetup(); + + // Add resources for test. + $this->planetAddResources(new Resources(5000, 5000, 0, 0)); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 5); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(5000, 5000, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Advance time by 1 minute + $fleetParentTime = $startTime->copy()->addMinutes(1); + Carbon::setTestNow($fleetParentTime); + + // Cancel the mission + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + $response->assertStatus(200); + + // Assert that the original mission is now canceled. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->canceled == 1, 'Fleet mission is not canceled after fleet recall is requested.'); + + // Assert that only the return trip is now visible. + // The eventbox should only show 1 mission (the parent). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + + // Advance time by amount of minutes it takes for the return trip to arrive. + Carbon::setTestNow(Carbon::createFromTimestamp($fleetMission->time_arrival)); + + // Do a request to trigger the update logic. + $this->get('/overview'); + + // Assert that the return trip is processed. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->processed == 1, 'Return trip is not processed after fleet has arrived back at origin planet.'); + + // Assert that the units have been returned to the origin planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at original 5 units after recalled trip has been processed.'); + // Assert that the resources have been returned to the origin planet. + $this->assertTrue($this->planetService->hasResources(new Resources(5000, 5000, 0, 0)), 'Resources are not returned to origin planet after recalling mission.'); + } + + /** + * Verify that canceling/recalling an active mission twice results in an error. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetRecallMissionTwiceError(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); + + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); + $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Advance time by 1 minute + $fleetParentTime = $startTime->copy()->addMinutes(1); + Carbon::setTestNow($fleetParentTime); + + // Cancel the mission + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + $response->assertStatus(200); + + // Cancel it again + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + // Expecting a 500 error because the mission is already canceled. + $response->assertStatus(500); + + // The eventbox should only show 1 mission (the first recalled mission). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + } } diff --git a/tests/FleetDispatchSelfTestCase.php b/tests/FleetDispatchSelfTestCase.php deleted file mode 100644 index 6915e535..00000000 --- a/tests/FleetDispatchSelfTestCase.php +++ /dev/null @@ -1,428 +0,0 @@ -planetSetObjectLevel('robot_factory', 2); - $this->planetSetObjectLevel('shipyard', 1); - $this->planetSetObjectLevel('research_lab', 1); - $this->playerSetResearchLevel('energy_technology', 1); - $this->playerSetResearchLevel('combustion_drive', 1); - $this->planetAddUnit('small_cargo', 5); - } - - abstract protected function messageCheckMissionArrival(): void; - abstract protected function messageCheckMissionReturn(): void; - - /** - * Assert that check request to dispatch fleet to empty position succeeds with colony ship. - * - * @throws BindingResolutionException - * @throws Exception - */ - public function testFleetCheckToOwnPlanetSuccess(): void - { - $this->basicSetup(); - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->fleetCheckToSecondPlanet($unitCollection, true); - } - - /** - * Assert that check request to dispatch fleet to foreign planet position fails without colony ship. - * - * @throws BindingResolutionException - * @throws Exception - */ - public function testFleetCheckToForeignPlanetError(): void - { - $this->basicSetup(); - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->fleetCheckToOtherPlayer($unitCollection, false); - } - - /** - * Assert that check request to dispatch fleet to empty position fails without colony ship. - * - * @throws BindingResolutionException - * @throws Exception - */ - public function testFleetCheckToEmptyPlanetError(): void - { - $this->basicSetup(); - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->fleetCheckToEmptyPosition($unitCollection, false); - } - - /** - * Verify that dispatching a fleet deducts correct amount of units from planet. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetDeductUnits(): void - { - $this->basicSetup(); - - // Assert that we begin with 5 small cargo ships on planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 4, 'Small Cargo ship not deducted from planet after fleet dispatch.'); - } - - /** - * Verify that dispatching a fleet deducts correct amount of resources from planet. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetDeductResources(): void - { - $this->basicSetup(); - $this->get('/shipyard'); - - // Get beginning resources of the planet. - $beginningMetal = $this->planetService->metal()->get(); - $beginningCrystal = $this->planetService->crystal()->get(); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - $response = $this->get('/shipyard'); - $response->assertStatus(200); - - // Assert that the resources were deducted from the planet. - $this->assertResourcesOnPage($response, new Resources($beginningMetal - 100, $beginningCrystal - 100, 0, 0)); - } - - /** - * Verify that dispatching a fleet with more resources than is on planet fails. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetDeductTooMuchResources(): void - { - $this->basicSetup(); - $this->get('/shipyard'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(4500, 100, 0, 0), 500); - } - - /** - * Verify that dispatching a fleet with more units than is on planet fails. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetDeductTooMuchUnits(): void - { - $this->basicSetup(); - $this->get('/shipyard'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 10); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0), 500); - } - - /** - * Verify that dispatching a fleet launches a return trip and brings back units to origin planet. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetReturnTrip(): void - { - $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Assert that we begin with 5 small cargo ships on planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - // Get just dispatched fleet mission ID from database. - $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); - $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); - $fleetMissionId = $fleetMission->id; - - // Get time it takes for the fleet to travel to the second planet. - $fleetMissionDuration = $fleetMissionService->calculateFleetMissionDuration($fleetMission); - - // Set time to fleet mission duration + 30 seconds (we do 30 instead of 1 second to test later if the return trip start and endtime work as expected - // and are calculated based on the arrival time instead of the time the job got processed). - $fleetParentTime = $startTime->copy()->addSeconds($fleetMissionDuration + 30); - Carbon::setTestNow($fleetParentTime); - - // Set all messages as read to avoid unread messages count in the overview. - $this->playerSetAllMessagesRead(); - - // Do a request to trigger the update logic. - $response = $this->get('/overview'); - $response->assertStatus(200); - - // Assert that the fleet mission is processed. - $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); - $this->assertTrue($fleetMission->processed == 1, 'Fleet mission is not processed after fleet has arrived at destination.'); - - // Check that message has been received by calling extended method - $this->messageCheckMissionArrival(); - - $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); - if ($this->hasReturnMission) { - // Assert that a return trip has been launched by checking the active missions for the current planet. - $this->assertCount(1, $activeMissions, 'No return trip launched after fleet has arrived at destination.'); - - // Advance time to the return trip arrival. - $returnTripDuration = $activeMissions->first()->time_arrival - $activeMissions->first()->time_departure; - - $fleetReturnTime = $fleetParentTime->copy()->addSeconds($returnTripDuration + 1); - Carbon::setTestNow($fleetReturnTime); - - // Do a request to trigger the update logic. - $response = $this->get('/overview'); - $response->assertStatus(200); - - // Assert that the return trip has been processed. - $activeMissions = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer(); - $this->assertCount(0, $activeMissions, 'Return trip is not processed after fleet has arrived back at origin planet.'); - - // Assert that the units have been returned to the origin planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units after return trip.'); - - $this->messageCheckMissionReturn(); - } else { - // Assert that NO return trip has been launched by checking the active missions for the current planet. - $this->assertCount(0, $activeMissions, 'Return trip launched after fleet with deployment mission has arrived at destination.'); - } - } - - /** - * Verify that an active mission also shows the (not yet existing) return trip in the fleet event list. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetReturnShown(): void - { - $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - // The eventbox should only show 1 mission (the parent). - $response = $this->get('/ajax/fleet/eventbox/fetch'); - $response->assertStatus(200); - $response->assertJsonFragment(['friendly' => 1]); - - // The event list should show either 1 or 2 missions (the parent and the to-be-created return trip). - $response = $this->get('/ajax/fleet/eventlist/fetch'); - $response->assertStatus(200); - if ($this->hasReturnMission) { - // If the mission has a return mission, we should see both in the event list. - $response->assertSee($this->missionName); - $response->assertSee($this->missionName . ' (R)'); - // Assert that we see both rows in the event list. - $response->assertSee('data-return-flight="false"', false); - $response->assertSee('data-return-flight="true"', false); - } else { - // If the mission does not have a return mission, we should only see the parent mission. - $response->assertSee($this->missionName); - $response->assertDontSee($this->missionName . ' (R)'); - // Assert that we see only parent row in the event list. - $response->assertSee('data-return-flight="false"', false); - $response->assertDontSee('data-return-flight="true"', false); - } - } - - /** - * Verify that canceling/recalling an active mission works. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetRecallMission(): void - { - $this->basicSetup(); - - // Add resources for test. - $this->planetAddResources(new Resources(5000, 5000, 0, 0)); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Assert that we begin with 5 small cargo ships on planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 5); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(5000, 5000, 0, 0)); - - // Get just dispatched fleet mission ID from database. - $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); - $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); - $fleetMissionId = $fleetMission->id; - - // Advance time by 1 minute - $fleetParentTime = $startTime->copy()->addMinutes(1); - Carbon::setTestNow($fleetParentTime); - - // Cancel the mission - $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ - 'fleet_mission_id' => $fleetMissionId, - '_token' => csrf_token(), - ]); - $response->assertStatus(200); - - // Assert that the original mission is now canceled. - $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); - $this->assertTrue($fleetMission->canceled == 1, 'Fleet mission is not canceled after fleet recall is requested.'); - - // Assert that only the return trip is now visible. - // The eventbox should only show 1 mission (the parent). - $response = $this->get('/ajax/fleet/eventbox/fetch'); - $response->assertStatus(200); - $response->assertJsonFragment(['friendly' => 1]); - $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); - - $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); - $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); - $fleetMissionId = $fleetMission->id; - $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); - - // Advance time by amount of minutes it takes for the return trip to arrive. - Carbon::setTestNow(Carbon::createFromTimestamp($fleetMission->time_arrival)); - - // Do a request to trigger the update logic. - $this->get('/overview'); - - // Assert that the return trip is processed. - $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); - $this->assertTrue($fleetMission->processed == 1, 'Return trip is not processed after fleet has arrived back at origin planet.'); - - // Assert that the units have been returned to the origin planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at original 5 units after recalled trip has been processed.'); - // Assert that the resources have been returned to the origin planet. - $this->assertTrue($this->planetService->hasResources(new Resources(5000, 5000, 0, 0)), 'Resources are not returned to origin planet after recalling mission.'); - } - - /** - * Verify that canceling/recalling an active mission twice results in an error. - * @throws BindingResolutionException - * @throws Exception - */ - public function testDispatchFleetRecallMissionTwiceError(): void - { - $this->basicSetup(); - - // Set time to static time 2024-01-01 - $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); - Carbon::setTestNow($startTime); - - // Assert that we begin with 5 small cargo ships on planet. - $response = $this->get('/shipyard'); - $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at 5 units at beginning of test.'); - - // Send fleet to the second planet of the test user. - $unitCollection = new UnitCollection(); - $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); - $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - - // Get just dispatched fleet mission ID from database. - $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); - $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); - $fleetMissionId = $fleetMission->id; - - // Advance time by 1 minute - $fleetParentTime = $startTime->copy()->addMinutes(1); - Carbon::setTestNow($fleetParentTime); - - // Cancel the mission - $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ - 'fleet_mission_id' => $fleetMissionId, - '_token' => csrf_token(), - ]); - $response->assertStatus(200); - - // Cancel it again - $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ - 'fleet_mission_id' => $fleetMissionId, - '_token' => csrf_token(), - ]); - // Expecting a 500 error because the mission is already canceled. - $response->assertStatus(500); - - // The eventbox should only show 1 mission (the first recalled mission). - $response = $this->get('/ajax/fleet/eventbox/fetch'); - $response->assertStatus(200); - $response->assertJsonFragment(['friendly' => 1]); - $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); - } -} diff --git a/tests/FleetDispatchTestCase.php b/tests/FleetDispatchTestCase.php index f30e0f33..dc02a4b6 100644 --- a/tests/FleetDispatchTestCase.php +++ b/tests/FleetDispatchTestCase.php @@ -43,7 +43,7 @@ abstract class FleetDispatchTestCase extends AccountTestCase */ abstract protected function basicSetup(): void; - public function fleetCheckToSecondPlanet(UnitCollection $units, bool $assertSuccess): void + protected function fleetCheckToSecondPlanet(UnitCollection $units, bool $assertSuccess): void { $coordinates = $this->secondPlanetService->getPlanetCoordinates(); $this->checkTargetFleet($coordinates, $units, $assertSuccess); @@ -52,13 +52,13 @@ public function fleetCheckToSecondPlanet(UnitCollection $units, bool $assertSucc /** * @throws BindingResolutionException */ - public function fleetCheckToOtherPlayer(UnitCollection $units, bool $assertSuccess): void + protected function fleetCheckToOtherPlayer(UnitCollection $units, bool $assertSuccess): void { $nearbyForeignPlanet = $this->getNearbyForeignPlanet(); $this->checkTargetFleet($nearbyForeignPlanet->getPlanetCoordinates(), $units, $assertSuccess); } - public function fleetCheckToEmptyPosition(UnitCollection $units, bool $assertSuccess): void + protected function fleetCheckToEmptyPosition(UnitCollection $units, bool $assertSuccess): void { $coordinates = $this->getNearbyEmptyCoordinate(); $this->checkTargetFleet($coordinates, $units, $assertSuccess); @@ -104,21 +104,6 @@ protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $ $this->dispatchFleet($coordinates, $units, $resources, $assertStatus); } - /** - * Convert units to dispatchable array format. - * - * @param UnitCollection $units - * @return array - */ - private function convertUnitsToArray(UnitCollection $units): array - { - $unitsArray = []; - foreach ($units->units as $unit) { - $unitsArray['am' . $unit->unitObject->id] = $unit->amount; - } - return $unitsArray; - } - /** * Call check-target fleet method with the given units and coordinates. * @@ -184,4 +169,19 @@ protected function dispatchFleet(Coordinate $coordinates, UnitCollection $units, $this->get('/ajax/fleet/eventbox/fetch')->assertStatus(200); $this->get('/ajax/fleet/eventlist/fetch')->assertStatus(200); } + + /** + * Convert units to dispatchable array format. + * + * @param UnitCollection $units + * @return array + */ + private function convertUnitsToArray(UnitCollection $units): array + { + $unitsArray = []; + foreach ($units->units as $unit) { + $unitsArray['am' . $unit->unitObject->id] = $unit->amount; + } + return $unitsArray; + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 822b4f3a..991fd3b1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,86 +2,23 @@ namespace Tests; +use Illuminate\Contracts\Console\Kernel; +use Illuminate\Foundation\Application; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; -use Illuminate\Support\Str; -use OGame\Factories\PlanetServiceFactory; -use OGame\Models\Planet; -use OGame\Services\PlanetService; -use OGame\Services\PlayerService; abstract class TestCase extends BaseTestCase { - use CreatesApplication; - protected PlanetService $planetService; - /** - * Create a new user and login via the register form on login page. + * Creates the application. * - * @return void + * @return Application */ - protected function createAndLoginUser(): void + public function createApplication(): Application { - // First go to logout page to ensure we are not logged in. - $this->post('/logout'); - - $response = $this->get('/login'); - - // Check for existence of register form - $response->assertSee('subscribeForm'); - - // Simulate form data - // Generate random email - $randomEmail = Str::random(10) . '@example.com'; - - $formData = [ - '_token' => csrf_token(), - 'email' => $randomEmail, - 'password' => 'asdasdasd', - 'v' => '3', - 'step' => 'validate', - 'kid' => '', - 'errorCodeOn' => '1', - 'is_utf8' => '1', - 'agb' => 'on', - ]; + $app = require __DIR__ . '/../bootstrap/app.php'; - // Submit the registration form - $response = $this->post('/register', $formData); - if ($response->status() !== 302) { - var_dump($response->getContent()); - $this->fail('Failed to register account. Response status: ' . $response->status(). '. Check the logs.'); - } + $app->make(Kernel::class)->bootstrap(); - // Check if we are authenticated after registration. - $this->assertAuthenticated(); - } - - /** - * Helper method to create a planet model and configure it. - * - * @param array $attributes - */ - protected function createAndSetPlanetModel(array $attributes): void - { - // Create fake planet eloquent model with additional attributes - $planetModelFake = Planet::factory()->make($attributes); - // Set the fake model to the planet service - $this->planetService->setPlanet($planetModelFake); - } - - /** - * Set up the planet service for testing. - * - * @return void - * @throws \Illuminate\Contracts\Container\BindingResolutionException - */ - protected function setUpPlanetService(): void - { - // Initialize empty playerService object directly without factory as we do not - // actually want to load a player from the database. - $playerService = app()->make(PlayerService::class, ['player_id' => 0]); - // Initialize the planet service with factory. - $planetServiceFactory = app()->make(PlanetServiceFactory::class); - $this->planetService = $planetServiceFactory->makeForPlayer($playerService, 0); + return $app; } } From e4fc8196e64b6514e1ce1c4c47c3711d7c3c37fb Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 4 May 2024 22:48:55 +0200 Subject: [PATCH 08/11] Add colonisation mission --- app/Factories/GameMissionFactory.php | 27 ++- app/Factories/PlanetServiceFactory.php | 50 ++-- app/GameMissions/Abstracts/GameMission.php | 75 +++++- app/GameMissions/ColonisationMission.php | 95 +++++++- app/GameMissions/TransportMission.php | 6 +- app/GameObjects/Models/UnitCollection.php | 24 ++ app/Http/Controllers/FleetController.php | 7 +- .../Controllers/FleetEventsController.php | 36 ++- app/Http/Middleware/GlobalGame.php | 5 +- app/Models/FleetMission.php | 12 +- app/Services/FleetMissionService.php | 82 ++----- app/Services/MessageService.php | 5 +- app/Services/PlanetListService.php | 3 +- app/Services/PlanetService.php | 38 ++- app/ViewModels/MessageViewModel.php | 25 +- ..._04_171614_update_fleet_missions_table.php | 36 +++ public/img/fleet/7.gif | Bin 0 -> 1136 bytes tests/AccountTestCase.php | 14 ++ tests/Feature/FactoryTest.php | 8 +- .../FleetDispatchColoniseTest.php | 218 +++++++++++++++++- .../FleetDispatch/FleetDispatchDeployTest.php | 5 + .../FleetDispatchTransportTest.php | 12 + tests/FleetDispatchTestCase.php | 11 +- 23 files changed, 663 insertions(+), 131 deletions(-) create mode 100644 database/migrations/2024_05_04_171614_update_fleet_missions_table.php create mode 100644 public/img/fleet/7.gif diff --git a/app/Factories/GameMissionFactory.php b/app/Factories/GameMissionFactory.php index 2ed3b82d..4738ee86 100644 --- a/app/Factories/GameMissionFactory.php +++ b/app/Factories/GameMissionFactory.php @@ -31,9 +31,30 @@ public static function getAllMissions(): array } */ return [ - app()->make(DeploymentMission::class), - app()->make(TransportMission::class), - app()->make(ColonisationMission::class), + 3 => app()->make(TransportMission::class), + 4 => app()->make(DeploymentMission::class), + 7 => app()->make(ColonisationMission::class), ]; } + + /** + * @param int $missionId + * @param array $dependencies + * + * @return GameMission + * @throws BindingResolutionException + */ + public static function getMissionById(int $missionId, array $dependencies): GameMission + { + switch ($missionId) { + case 3: + return app()->make(TransportMission::class, $dependencies); + case 4: + return app()->make(DeploymentMission::class, $dependencies); + case 7: + return app()->make(ColonisationMission::class, $dependencies); + } + $missions = self::getAllMissions(); + return $missions[$missionId]; + } } diff --git a/app/Factories/PlanetServiceFactory.php b/app/Factories/PlanetServiceFactory.php index 6c8891ec..a40f43a2 100644 --- a/app/Factories/PlanetServiceFactory.php +++ b/app/Factories/PlanetServiceFactory.php @@ -2,6 +2,7 @@ namespace OGame\Factories; +use Exception; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Carbon; use OGame\Models\Planet; @@ -92,7 +93,7 @@ public function makeForPlayer(PlayerService $player, int $planetId): PlanetServi * @return ?PlanetService * @throws BindingResolutionException */ - public function makeForCoordinate(Planet\Coordinate $coordinate): ?PlanetService + public function makeForCoordinate(Coordinate $coordinate): ?PlanetService { if (!isset($this->instancesByCoordinate[$coordinate->asString()])) { // Get the planet ID from the database. @@ -116,10 +117,10 @@ public function makeForCoordinate(Planet\Coordinate $coordinate): ?PlanetService /** * Determine next available new planet position. * - * @return Planet\Coordinate - * @throws \Exception + * @return Coordinate + * @throws Exception */ - public function determineNewPlanetPosition(): Planet\Coordinate + public function determineNewPlanetPosition(): Coordinate { $lastAssignedGalaxy = (int)$this->settings->get('last_assigned_galaxy', 1); $lastAssignedSystem = (int)$this->settings->get('last_assigned_system', 1); @@ -162,29 +163,56 @@ public function determineNewPlanetPosition(): Planet\Coordinate } // If more than 100 tries have been done with no success, give up. - throw new \Exception('Unable to determine new planet position.'); + throw new Exception('Unable to determine new planet position.'); } /** - * Creates a new random planet and then return the planetService instance for it. + * Creates a new random initial planet for a player and then return the planetService instance for it. * * @param PlayerService $player * @param string $planetName * @return PlanetService * @throws BindingResolutionException */ - public function createForPlayer(PlayerService $player, string $planetName = 'Colony'): PlanetService + public function createInitialForPlayer(PlayerService $player, string $planetName): PlanetService { $new_position = $this->determineNewPlanetPosition(); if (empty($new_position->galaxy) || empty($new_position->system) || empty($new_position->position)) { // Failed to get a new position for the to be created planet. Throw exception. - throw new \Exception('Unable to determine new planet position.'); + throw new Exception('Unable to determine new planet position.'); } + $createdPlanet = $this->createPlanet($player, $new_position, $planetName); + + // Update settings with the last assigned galaxy and system if they changed. + $this->settings->set('last_assigned_galaxy', $createdPlanet->getPlanetCoordinates()->galaxy); + $this->settings->set('last_assigned_system', $createdPlanet->getPlanetCoordinates()->system); + + return $createdPlanet; + } + + /** + * Creates a new planet for a player at the given coordinate and then return the planetService instance for it. + * + * @param PlayerService $player + * @param Coordinate $coordinate + * @return PlanetService + * @throws BindingResolutionException + */ + public function createAdditionalForPlayer(PlayerService $player, Coordinate $coordinate): PlanetService + { + return $this->createPlanet($player, $coordinate, 'Colony'); + } + + /** + * @throws BindingResolutionException + */ + private function createPlanet(PlayerService $player, Coordinate $new_position, string $planet_name): PlanetService + { // Position is available $planet = new Planet(); $planet->user_id = $player->getId(); - $planet->name = $planetName; + $planet->name = $planet_name; $planet->galaxy = $new_position->galaxy; $planet->system = $new_position->system; $planet->planet = $new_position->position; @@ -211,10 +239,6 @@ public function createForPlayer(PlayerService $player, string $planetName = 'Col $planet->time_last_update = (int)Carbon::now()->timestamp; $planet->save(); - // Update settings with the last assigned galaxy and system if they changed. - $this->settings->set('last_assigned_galaxy', $new_position->galaxy); - $this->settings->set('last_assigned_system', $new_position->system); - // Now make and return the planetService. return $this->makeForPlayer($player, $planet->id); } diff --git a/app/GameMissions/Abstracts/GameMission.php b/app/GameMissions/Abstracts/GameMission.php index 964ba17f..11f1a53d 100644 --- a/app/GameMissions/Abstracts/GameMission.php +++ b/app/GameMissions/Abstracts/GameMission.php @@ -9,6 +9,7 @@ use OGame\GameMissions\Models\MissionPossibleStatus; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\FleetMission; +use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; use OGame\Services\FleetMissionService; use OGame\Services\MessageService; @@ -75,6 +76,10 @@ abstract public function isMissionPossible(PlanetService $planet, ?PlanetService */ public function cancel(FleetMission $mission): void { + // Update the mission arrived time to now instead of original planned arrival time if the mission would finish by itself. + // This arrival time is used by the return mission to calculate the return time. + $mission->time_arrival = (int)Carbon::now()->timestamp; + // Mark parent mission as canceled. $mission->canceled = 1; $mission->processed = 1; @@ -88,12 +93,13 @@ public function cancel(FleetMission $mission): void * Generic sanity checks before starting a mission to make sure all requirements are met. * * @param PlanetService $planet + * @param Coordinate $targetCoordinate * @param UnitCollection $units * @param Resources $resources * @return void * @throws Exception */ - public function startMissionSanityChecks(PlanetService $planet, UnitCollection $units, Resources $resources): void + public function startMissionSanityChecks(PlanetService $planet, Coordinate $targetCoordinate, UnitCollection $units, Resources $resources): void { if (!$planet->hasResources($resources)) { throw new Exception('Not enough resources on the planet to send the fleet.'); @@ -112,22 +118,25 @@ public function deductMissionResources(PlanetService $planet, Resources $resourc { $planet->deductResources($resources, false); $planet->removeUnits($units, false); + + // Save the planet to commit removed resources/units. + $planet->save(); } /** * Start a new mission. * * @param PlanetService $planet - * @param PlanetService $targetPlanet + * @param Coordinate $targetCoordinate * @param UnitCollection $units * @param Resources $resources * @param int $parent_id * @return void * @throws Exception */ - public function start(PlanetService $planet, PlanetService $targetPlanet, UnitCollection $units, Resources $resources, int $parent_id = 0): void + public function start(PlanetService $planet, Coordinate $targetCoordinate, UnitCollection $units, Resources $resources, int $parent_id = 0): void { - $this->startMissionSanityChecks($planet, $units, $resources); + $this->startMissionSanityChecks($planet, $targetCoordinate, $units, $resources); // Time this fleet mission will depart (now) $time_start = (int)Carbon::now()->timestamp; @@ -146,16 +155,24 @@ public function start(PlanetService $planet, PlanetService $targetPlanet, UnitCo } $mission->user_id = $planet->getPlayer()->getId(); + $mission->planet_id_from = $planet->getPlanetId(); + $mission->galaxy_from = $planet->getPlanetCoordinates()->galaxy; + $mission->system_from = $planet->getPlanetCoordinates()->system; + $mission->position_from = $planet->getPlanetCoordinates()->position; + $mission->mission_type = static::$typeId; $mission->time_departure = $time_start; $mission->time_arrival = $time_end; - $mission->planet_id_to = $targetPlanet->getPlanetId(); - $coords = $targetPlanet->getPlanetCoordinates(); - $mission->galaxy_to = $coords->galaxy; - $mission->system_to = $coords->system; - $mission->position_to = $coords->position; + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + $target_planet = $planetServiceFactory->makeForCoordinate($targetCoordinate); + if ($target_planet !== null) { + $mission->planet_id_to = $target_planet->getPlanetId(); + } + $mission->galaxy_to = $targetCoordinate->galaxy; + $mission->system_to = $targetCoordinate->system; + $mission->position_to = $targetCoordinate->position; foreach ($units->units as $unit) { $mission->{$unit->unitObject->machine_name} = $unit->amount; @@ -170,6 +187,13 @@ public function start(PlanetService $planet, PlanetService $targetPlanet, UnitCo // Save the new fleet mission. $mission->save(); + + // Check if the created mission arrival time is in the past. This can happen if the planet hasn't been updated + // for some time and missions have already played out in the meantime. + // If the mission is in the past, process it immediately. + if ($mission->time_arrival < Carbon::now()->timestamp) { + $this->process($mission); + } } /** @@ -194,7 +218,30 @@ protected function startReturn(FleetMission $parentMission): void $mission = new FleetMission(); $mission->parent_id = $parentMission->id; $mission->user_id = $parentMission->user_id; - $mission->planet_id_from = $parentMission->planet_id_to; + + // If planet_id_to is not set, it can mean that the target planet was colonized or the mission was canceled. + // In this case, we keep planet_id_from as null. + if ($parentMission->planet_id_to === null) { + // Attempt to load it from the target coordinates. + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + $targetPlanet = $planetServiceFactory->makeForCoordinate(new Coordinate($parentMission->galaxy_to, $parentMission->system_to, $parentMission->position_to)); + if ($targetPlanet !== null) { + $mission->planet_id_from = $targetPlanet->getPlanetId(); + $mission->galaxy_from = $targetPlanet->getPlanetCoordinates()->galaxy; + $mission->system_from = $targetPlanet->getPlanetCoordinates()->system; + $mission->position_from = $targetPlanet->getPlanetCoordinates()->position; + } else { + $mission->planet_id_from = null; + $mission->galaxy_from = $parentMission->galaxy_to; + $mission->system_from = $parentMission->system_to; + $mission->position_from = $parentMission->position_to; + } + } else { + $mission->planet_id_from = $parentMission->planet_id_to; + $mission->galaxy_from = $parentMission->galaxy_to; + $mission->system_from = $parentMission->system_to; + $mission->position_from = $parentMission->position_to; + } $mission->mission_type = $parentMission->mission_type; $mission->time_departure = $time_start; $mission->time_arrival = $time_end; @@ -202,7 +249,6 @@ protected function startReturn(FleetMission $parentMission): void // Planet from service $planetServiceFactory = app()->make(PlanetServiceFactory::class); - $planetFromService = $planetServiceFactory->make($mission->planet_id_from); $planetToService = $planetServiceFactory->make($mission->planet_id_to); // Coordinates @@ -236,6 +282,13 @@ protected function startReturn(FleetMission $parentMission): void // Save the new fleet return mission. $mission->save(); + + // Check if the created mission arrival time is in the past. This can happen if the planet hasn't been updated + // for some time and missions have already played out in the meantime. + // If the mission is in the past, process it immediately. + if ($mission->time_arrival < Carbon::now()->timestamp) { + $this->process($mission); + } } /** diff --git a/app/GameMissions/ColonisationMission.php b/app/GameMissions/ColonisationMission.php index 9a213e12..affadc20 100644 --- a/app/GameMissions/ColonisationMission.php +++ b/app/GameMissions/ColonisationMission.php @@ -3,10 +3,14 @@ namespace OGame\GameMissions; use Exception; +use Illuminate\Contracts\Container\BindingResolutionException; +use OGame\Factories\PlanetServiceFactory; +use OGame\Factories\PlayerServiceFactory; use OGame\GameMissions\Abstracts\GameMission; use OGame\GameMissions\Models\MissionPossibleStatus; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\FleetMission; +use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; use OGame\Services\PlanetService; @@ -14,18 +18,20 @@ class ColonisationMission extends GameMission { protected static string $name = 'Colonisation'; protected static int $typeId = 7; - protected static bool $hasReturnMission = true; + protected static bool $hasReturnMission = false; - public function startMissionSanityChecks(PlanetService $planet, UnitCollection $units, Resources $resources): void + public function startMissionSanityChecks(PlanetService $planet, Coordinate $targetCoordinate, UnitCollection $units, Resources $resources): void { // Call the parent method - parent::startMissionSanityChecks($planet, $units, $resources); + parent::startMissionSanityChecks($planet, $targetCoordinate, $units, $resources); if ($units->getAmountByMachineName('colony_ship') == 0) { throw new Exception(__('You need a colony ship to colonize a planet.')); } - if ($planet->getPlayer() != null) { + // Try to load planet. If it succeeds it means the planet is not empty. + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + if ($planetServiceFactory->makeForCoordinate($targetCoordinate) != null) { throw new Exception(__('You can only colonize empty planets.')); } } @@ -49,14 +55,91 @@ public function isMissionPossible(PlanetService $planet, ?PlanetService $targetP } /** + * @throws BindingResolutionException */ protected function processArrival(FleetMission $mission): void { - // TOOD: Implement the colonisation logic + // Sanity check: make sure the target coordinates are valid and the planet is (still) empty. + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + $target_planet = $planetServiceFactory->makeForCoordinate(new Coordinate($mission->galaxy_to, $mission->system_to, $mission->position_to)); + + // Load the mission owner user + $playerServiceFactory = app()->make(PlayerServiceFactory::class); + $player = $playerServiceFactory->make($mission->user_id); + + if ($target_planet != null) { + // TODO: add unittest for this behavior. + // Cancel the current mission. + $this->cancel($mission); + // Send fleet back. + $this->startReturn($mission); + return; + } + + // Sanity check: colonisation mission without a colony ship is not possible. + if ($mission->colony_ship < 1) { + // Cancel the current mission. + $this->cancel($mission); + // Send fleet back. + $this->startReturn($mission); + } + + // Create a new planet at the target coordinates. + $target_planet = $planetServiceFactory->createAdditionalForPlayer($player, new Coordinate($mission->galaxy_to, $mission->system_to, $mission->position_to)); + + // Success message + $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Settlement Report', 'The fleet has arrived at the assigned coordinates [coordinates]' . $target_planet->getPlanetCoordinates()->asString() . '[/coordinates], found a new planet there and are beginning to develop upon it immediately.', 'colony_established'); + + // Add resources to the target planet if the mission has any. + $resources = $this->fleetMissionService->getResources($mission); + $target_planet->addResources($resources); + + // Remove the colony ship from the fleet as it is consumed in the colonization process. + $mission->colony_ship -= 1; + + // Mark the arrival mission as processed + $mission->processed = 1; + $mission->save(); + + // Check if the mission has any ships left. If yes, start a return mission to send them back. + if ($this->fleetMissionService->getFleetUnitCount($mission) > 0) { + // Create and start the return mission. + $this->startReturn($mission); + } } protected function processReturn(FleetMission $mission): void { - // TODO: Implement the return logic + // Load the target planet + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + $target_planet = $planetServiceFactory->make($mission->planet_id_to); + + // Transport return trip: add back the units to the source planet. Then we're done. + $target_planet->addUnits($this->fleetMissionService->getFleetUnits($mission)); + + // Add resources to the origin planet (if any). + // TODO: make messages translatable by using tokens instead of directly inserting dynamic content. + $return_resources = $this->fleetMissionService->getResources($mission); + if ($return_resources->sum() > 0) { + $target_planet->addResources($return_resources); + + // Send message to player that the return mission has arrived + // TODO: replace [planet] with coordinates if planet is not available. + // TODO: move this message to a generic place? It is used in multiple mission types for the generic return of fleet message. + $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet] and delivered its goods: + +Metal: ' . $mission->metal . ' +Crystal: ' . $mission->crystal . ' +Deuterium: ' . $mission->deuterium, 'return_of_fleet'); + } else { + // Send message to player that the return mission has arrived + $this->messageService->sendMessageToPlayer($target_planet->getPlayer(), 'Return of a fleet', 'Your fleet is returning from planet [planet]' . $mission->planet_id_from . '[/planet] to planet [planet]' . $mission->planet_id_to . '[/planet]. + + The fleet doesn\'t deliver goods.', 'return_of_fleet'); + } + + // Mark the return mission as processed + $mission->processed = 1; + $mission->save(); } } diff --git a/app/GameMissions/TransportMission.php b/app/GameMissions/TransportMission.php index 9294c3bc..5ffffd7e 100644 --- a/app/GameMissions/TransportMission.php +++ b/app/GameMissions/TransportMission.php @@ -56,12 +56,12 @@ protected function processArrival(FleetMission $mission): void Metal: ' . $mission->metal . ' Crystal: ' . $mission->crystal . ' Deuterium: ' . $mission->deuterium, 'transport_received'); } - // Create and start the return mission. - $this->startReturn($mission); - // Mark the arrival mission as processed $mission->processed = 1; $mission->save(); + + // Create and start the return mission. + $this->startReturn($mission); } protected function processReturn(FleetMission $mission): void diff --git a/app/GameObjects/Models/UnitCollection.php b/app/GameObjects/Models/UnitCollection.php index 38696c8e..fd2df093 100644 --- a/app/GameObjects/Models/UnitCollection.php +++ b/app/GameObjects/Models/UnitCollection.php @@ -21,6 +21,30 @@ public function addUnit(UnitObject $unitObject, int $amount): void $this->units[] = $entry; } + /** + * Remove a unit from the collection. + */ + public function removeUnit(UnitObject $unitObject, int $amount): void + { + $found = false; + foreach ($this->units as $key => $entry) { + if ($entry->unitObject->machine_name === $unitObject->machine_name) { + $this->units[$key]->amount -= $amount; + + if ($this->units[$key]->amount <= 0) { + unset($this->units[$key]); + } + + $found = true; + } + } + + // Throw an exception if the to be removed unit was not found in the collection. + if (!$found) { + throw new \Exception('Unit not found in collection'); + } + } + /** * Get the amount of a unit in the collection. * diff --git a/app/Http/Controllers/FleetController.php b/app/Http/Controllers/FleetController.php index 4d1ba634..3231410f 100644 --- a/app/Http/Controllers/FleetController.php +++ b/app/Http/Controllers/FleetController.php @@ -233,9 +233,8 @@ public function dispatchSendFleet(PlayerService $player): JsonResponse // Get the current player's planet $planet = $player->planets->current(); - // Load the target planet - $planetServiceFactory = app()->make(PlanetServiceFactory::class); - $target_planet = $planetServiceFactory->makeForCoordinate(new Coordinate($galaxy, $system, $position)); + // Create the target coordinate + $target_coordinate = new Coordinate($galaxy, $system, $position); // Extract units from the request and create a unit collection. // Loop through all input fields and get all units prefixed with "am". @@ -252,7 +251,7 @@ public function dispatchSendFleet(PlayerService $player): JsonResponse // Create a new fleet mission $fleetMissionService = app()->make(FleetMissionService::class); - $fleetMissionService->createNewFromPlanet($planet, $target_planet, $mission_type, $units, $resources); + $fleetMissionService->createNewFromPlanet($planet, $target_coordinate, $mission_type, $units, $resources); return response()->json([ 'components' => [], diff --git a/app/Http/Controllers/FleetEventsController.php b/app/Http/Controllers/FleetEventsController.php index baa993ea..a1495e6a 100644 --- a/app/Http/Controllers/FleetEventsController.php +++ b/app/Http/Controllers/FleetEventsController.php @@ -7,6 +7,7 @@ use Illuminate\Support\Carbon; use Illuminate\View\View; use OGame\Factories\PlanetServiceFactory; +use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; use OGame\Services\FleetMissionService; use OGame\ViewModels\FleetEventRowViewModel; @@ -72,8 +73,6 @@ public function fetchEventList(): View foreach ($friendlyMissionRows as $row) { // Planet from service $planetServiceFactory = app()->make(PlanetServiceFactory::class); - $planetFromService = $planetServiceFactory->make($row->planet_id_from); - $planetToService = $planetServiceFactory->make($row->planet_id_to); $eventRowViewModel = new FleetEventRowViewModel(); $eventRowViewModel->id = $row->id; @@ -81,10 +80,27 @@ public function fetchEventList(): View $eventRowViewModel->mission_label = $fleetMissionService->missionTypeToLabel($row->mission_type); $eventRowViewModel->mission_time_arrival = $row->time_arrival; $eventRowViewModel->is_return_trip = !empty($row->parent_id); // If mission has a parent, it is a return trip - $eventRowViewModel->origin_planet_name = $planetFromService->getPlanetName(); // TODO: implement null planet from/to checks - $eventRowViewModel->origin_planet_coords = $planetFromService->getPlanetCoordinates(); - $eventRowViewModel->destination_planet_name = $planetToService->getPlanetName(); // TODO: implement null planet from/to checks - $eventRowViewModel->destination_planet_coords = $planetToService->getPlanetCoordinates(); + + $eventRowViewModel->origin_planet_name = ''; + $eventRowViewModel->origin_planet_coords = new Coordinate($row->galaxy_from, $row->system_from, $row->position_from); + if ($row->planet_id_from != null) { + $planetFromService = $planetServiceFactory->make($row->planet_id_from); + if ($planetFromService != null) { + $eventRowViewModel->origin_planet_name = $planetFromService->getPlanetName(); + $eventRowViewModel->origin_planet_coords = $planetFromService->getPlanetCoordinates(); + } + } + + $eventRowViewModel->destination_planet_name = ''; + $eventRowViewModel->destination_planet_coords = new Coordinate($row->galaxy_to, $row->system_to, $row->position_to); + if ($row->planet_id_to != null) { + $planetToService = $planetServiceFactory->make($row->planet_id_to); + if ($planetToService != null) { + $eventRowViewModel->destination_planet_name = $planetToService->getPlanetName(); + $eventRowViewModel->destination_planet_coords = $planetToService->getPlanetCoordinates(); + } + } + $eventRowViewModel->fleet_unit_count = $fleetMissionService->getFleetUnitCount($row); $eventRowViewModel->fleet_units = $fleetMissionService->getFleetUnits($row); $eventRowViewModel->resources = $fleetMissionService->getResources($row); @@ -99,10 +115,10 @@ public function fetchEventList(): View $returnTripRow->mission_type = $eventRowViewModel->mission_type; $returnTripRow->mission_label = $fleetMissionService->missionTypeToLabel($eventRowViewModel->mission_type); $returnTripRow->mission_time_arrival = $row->time_arrival + ($row->time_arrival - $row->time_departure); // Round trip arrival time is double the time of the first trip - $returnTripRow->origin_planet_name = $planetToService->getPlanetName(); - $returnTripRow->origin_planet_coords = $planetToService->getPlanetCoordinates(); - $returnTripRow->destination_planet_name = $planetFromService->getPlanetName(); - $returnTripRow->destination_planet_coords = $planetFromService->getPlanetCoordinates(); + $returnTripRow->origin_planet_name = $eventRowViewModel->destination_planet_name; + $returnTripRow->origin_planet_coords = $eventRowViewModel->destination_planet_coords; + $returnTripRow->destination_planet_name = $eventRowViewModel->origin_planet_name; + $returnTripRow->destination_planet_coords = $eventRowViewModel->origin_planet_coords; $returnTripRow->fleet_unit_count = $eventRowViewModel->fleet_unit_count; $returnTripRow->fleet_units = $eventRowViewModel->fleet_units; $returnTripRow->resources = new Resources(0, 0, 0, 0); diff --git a/app/Http/Middleware/GlobalGame.php b/app/Http/Middleware/GlobalGame.php index 9564bc13..13535f81 100644 --- a/app/Http/Middleware/GlobalGame.php +++ b/app/Http/Middleware/GlobalGame.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Auth; use OGame\Services\ObjectService; use OGame\Services\PlayerService; +use OGame\Services\SettingsService; class GlobalGame { @@ -27,8 +28,8 @@ public function handle(Request $request, Closure $next): mixed app()->instance(ObjectService::class, $object); // Instantiate settings service. - $settings = app()->make(\OGame\Services\SettingsService::class); - app()->instance(\OGame\Services\SettingsService::class, $settings); + $settings = app()->make(SettingsService::class); + app()->instance(SettingsService::class, $settings); // Load player. $player = app()->make(PlayerService::class, ['player_id' => $request->user()->id]); diff --git a/app/Models/FleetMission.php b/app/Models/FleetMission.php index 94ce9d62..f861b87d 100644 --- a/app/Models/FleetMission.php +++ b/app/Models/FleetMission.php @@ -9,7 +9,7 @@ * * * @property int $id - * @property int $planet_id_from + * @property int|null $planet_id_from * @property int|null $planet_id_to * @property int|null $galaxy_to * @property int|null $system_to @@ -71,6 +71,16 @@ * @method static \Illuminate\Database\Eloquent\Builder|FleetMission whereTimeArrival($value) * @method static \Illuminate\Database\Eloquent\Builder|FleetMission whereTimeDeparture($value) * @method static \Illuminate\Database\Eloquent\Builder|FleetMission whereUpdatedAt($value) + * @property int|null $parent_id + * @property int $user_id + * @property int|null $galaxy_from + * @property int|null $system_from + * @property int|null $position_from + * @method static \Illuminate\Database\Eloquent\Builder|FleetMission whereGalaxyFrom($value) + * @method static \Illuminate\Database\Eloquent\Builder|FleetMission whereParentId($value) + * @method static \Illuminate\Database\Eloquent\Builder|FleetMission wherePositionFrom($value) + * @method static \Illuminate\Database\Eloquent\Builder|FleetMission whereSystemFrom($value) + * @method static \Illuminate\Database\Eloquent\Builder|FleetMission whereUserId($value) * @mixin \Eloquent */ class FleetMission extends Model diff --git a/app/Services/FleetMissionService.php b/app/Services/FleetMissionService.php index a508c031..9756b7b4 100644 --- a/app/Services/FleetMissionService.php +++ b/app/Services/FleetMissionService.php @@ -6,11 +6,13 @@ use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Carbon; +use OGame\Factories\GameMissionFactory; use OGame\GameMissions\ColonisationMission; use OGame\GameMissions\DeploymentMission; use OGame\GameMissions\TransportMission; use OGame\GameObjects\Models\UnitCollection; use OGame\Models\FleetMission; +use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; /** @@ -47,6 +49,7 @@ class FleetMissionService protected array $missionTypeToClass = [ 3 => TransportMission::class, 4 => DeploymentMission::class, + 7 => ColonisationMission::class, ]; /** @@ -247,7 +250,7 @@ public function getFleetMissionById(int $id, bool $only_active = true): FleetMis * Creates a new fleet mission for the current planet. * * @param PlanetService $planet - * @param PlanetService $targetPlanet + * @param Coordinate $targetCoordinate * @param int $missionType * @param UnitCollection $units * @param Resources $resources @@ -255,27 +258,14 @@ public function getFleetMissionById(int $id, bool $only_active = true): FleetMis * @return void * @throws Exception */ - public function createNewFromPlanet(PlanetService $planet, PlanetService $targetPlanet, int $missionType, UnitCollection $units, Resources $resources, int $parent_id = 0): void + public function createNewFromPlanet(PlanetService $planet, Coordinate $targetCoordinate, int $missionType, UnitCollection $units, Resources $resources, int $parent_id = 0): void { - if ($missionType == 3) { - $transportMission = app()->make(TransportMission::class, [ - 'fleetMissionService' => $this, - 'messageService' => $this->messageService, - ]); - $transportMission->start($planet, $targetPlanet, $units, $resources, $parent_id); - } elseif ($missionType == 4) { - $deployMission = app()->make(DeploymentMission::class, [ - 'fleetMissionService' => $this, - 'messageService' => $this->messageService, - ]); - $deployMission->start($planet, $targetPlanet, $units, $resources, $parent_id); - } elseif ($missionType == 7) { - $deployMission = app()->make(ColonisationMission::class, [ - 'fleetMissionService' => $this, - 'messageService' => $this->messageService, - ]); - $deployMission->start($planet, $targetPlanet, $units, $resources, $parent_id); - } + $missionFactory = app()->make(GameMissionFactory::class); + $missionObject = $missionFactory->getMissionById($missionType, [ + 'fleetMissionService' => $this, + 'messageService' => $this->messageService, + ]); + $missionObject->start($planet, $targetCoordinate, $units, $resources, $parent_id); } /** @@ -293,25 +283,12 @@ public function updateMission(FleetMission $mission): void return; } - // TODO: make an abstraction layer where each mission is its own class and process/cancel logic is stored there. - switch ($mission->mission_type) { - case 3: - // Transport - $transportMission = app()->make(TransportMission::class, [ - 'fleetMissionService' => $this, - 'messageService' => $this->messageService, - ]); - $transportMission->process($mission); - break; - case 4: - // Deploy - $deployMission = app()->make(DeploymentMission::class, [ - 'fleetMissionService' => $this, - 'messageService' => $this->messageService, - ]); - $deployMission->process($mission); - break; - } + $missionFactory = app()->make(GameMissionFactory::class); + $missionObject = $missionFactory->getMissionById($mission->mission_type, [ + 'fleetMissionService' => $this, + 'messageService' => $this->messageService, + ]); + $missionObject->process($mission); } /** @@ -319,6 +296,7 @@ public function updateMission(FleetMission $mission): void * * @param FleetMission $mission * @return void + * @throws BindingResolutionException */ public function cancelMission(FleetMission $mission): void { @@ -327,23 +305,11 @@ public function cancelMission(FleetMission $mission): void return; } - switch ($mission->mission_type) { - case 3: - // Transport - $transportMission = app()->make(TransportMission::class, [ - 'fleetMissionService' => $this, - 'messageService' => $this->messageService, - ]); - $transportMission->cancel($mission); - break; - case 4: - // Deploy - $deployMission = app()->make(DeploymentMission::class, [ - 'fleetMissionService' => $this, - 'messageService' => $this->messageService, - ]); - $deployMission->cancel($mission); - break; - } + $missionFactory = app()->make(GameMissionFactory::class); + $missionObject = $missionFactory->getMissionById($mission->mission_type, [ + 'fleetMissionService' => $this, + 'messageService' => $this->messageService, + ]); + $missionObject->cancel($mission); } } diff --git a/app/Services/MessageService.php b/app/Services/MessageService.php index b2d1f77c..528af9c7 100644 --- a/app/Services/MessageService.php +++ b/app/Services/MessageService.php @@ -54,8 +54,9 @@ class MessageService ], 'economy' => [ 'economy' => [ - 61, // Production canceled - 62, // Repair completed + 'production_canceled' => 61, // Production canceled + 'repair_completed' => 62, // Repair completed + 'colony_established' => 63, // Colony established ], ], 'universe' => [ diff --git a/app/Services/PlanetListService.php b/app/Services/PlanetListService.php index bf11755a..2ce9f96b 100644 --- a/app/Services/PlanetListService.php +++ b/app/Services/PlanetListService.php @@ -45,6 +45,7 @@ public function __construct(PlayerService $player) * @param int $id * @return void * @throws BindingResolutionException + * @throws Exception */ public function load(int $id): void { @@ -65,7 +66,7 @@ public function load(int $id): void $planetNames = ['Homeworld', 'Colony']; for ($i = 0; $i < 2; $i++) { $planetServiceFactory = app()->make(PlanetServiceFactory::class); - $planetService = $planetServiceFactory->createForPlayer($this->player, $planetNames[$i]); + $planetService = $planetServiceFactory->createInitialForPlayer($this->player, $planetNames[$i]); $this->planets[] = $planetService; } diff --git a/app/Services/PlanetService.php b/app/Services/PlanetService.php index 0213309f..74ab5d06 100644 --- a/app/Services/PlanetService.php +++ b/app/Services/PlanetService.php @@ -92,6 +92,16 @@ public function loadByPlanetId(int $id): void $this->planet = $planet; } + /** + * Reloads the planet object from the database. + * + * @return void + */ + public function reloadPlanet(): void + { + $this->loadByPlanetId($this->planet->id); + } + /** * Get the player object who owns this planet. * @@ -113,6 +123,14 @@ public function setPlanet(Planet $planet): void $this->planet = $planet; } + /** + * Save the planet model to persist changes to the database. + */ + public function save(): void + { + $this->planet->save(); + } + /** * Returns true if the underlying planet model has been initialized. For unittests this can be used to check * if the planet model itself has been set up already. @@ -336,7 +354,7 @@ public function deductResources(Resources $resources, bool $save_planet = true): } if ($save_planet) { - $this->planet->save(); + $this->save(); } } @@ -566,7 +584,7 @@ public function setBuildingPercent(int $building_id, int $percentage): bool } $this->planet->{$building->machine_name . '_percent'} = $percentage; - $this->planet->save(); + $this->save(); return true; } @@ -611,7 +629,7 @@ public function update(): void $this->updateFleetMissions(false); // Save the planet manually here to prevent it from happening 5+ times in the methods above. - $this->planet->save(); + $this->save(); } /** @@ -692,7 +710,7 @@ public function updateResources(bool $save_planet = true): void $this->planet->time_last_update = $current_time; if ($save_planet) { - $this->planet->save(); + $this->save(); } } } @@ -756,7 +774,7 @@ public function addResources(Resources $resources, bool $save_planet = true): vo } if ($save_planet) { - $this->planet->save(); + $this->save(); } } @@ -819,7 +837,7 @@ public function setObjectLevel(int $object_id, int $level, bool $save_planet = t $object = $this->objects->getObjectById($object_id); $this->planet->{$object->machine_name} = $level; if ($save_planet) { - $this->planet->save(); + $this->save(); } } @@ -900,7 +918,7 @@ public function addUnit(string $machine_name, int $amount, bool $save_planet = t $this->planet->{$object->machine_name} += $amount; if ($save_planet) { - $this->planet->save(); + $this->save(); } } @@ -937,7 +955,7 @@ public function removeUnit(string $machine_name, int $amount, bool $save_planet) $this->planet->{$object->machine_name} -= $amount; if ($save_planet) { - $this->planet->save(); + $this->save(); } } @@ -990,7 +1008,7 @@ public function updateResourceProductionStats(bool $save_planet = true): void $this->updateResourceProductionStatsInner($production_total, $energy_production_total, $energy_consumption_total); if ($save_planet) { - $this->planet->save(); + $this->save(); } } @@ -1245,7 +1263,7 @@ public function updateResourceStorageStats(bool $save_planet = true): void $this->planet->crystal_max = (int)$storage_sum->crystal->get(); $this->planet->deuterium_max = (int)$storage_sum->deuterium->get(); if ($save_planet) { - $this->planet->save(); + $this->save(); } } diff --git a/app/ViewModels/MessageViewModel.php b/app/ViewModels/MessageViewModel.php index c40f04c8..4066907c 100644 --- a/app/ViewModels/MessageViewModel.php +++ b/app/ViewModels/MessageViewModel.php @@ -6,6 +6,7 @@ use OGame\Factories\PlanetServiceFactory; use OGame\Factories\PlayerServiceFactory; use OGame\Models\Message; +use OGame\Models\Planet\Coordinate; /** * MessageViewModel @@ -40,6 +41,8 @@ public function getFrom(): string { // From is based on the type of the message and/or the user_id/alliance_id. switch ($this->type) { + case 63: // colony_established + return 'Settlers'; default: return 'Fleet Command'; } @@ -93,7 +96,7 @@ public function getBody(): string $planetServiceFactory = app()->make(PlanetServiceFactory::class); $planetService = $planetServiceFactory->make((int)$matches[1]); - if ($planetService->getPlanetId() > 0) { + if ($planetService != null) { $planetName = '
' . $planetService->getPlanetName() . ' [' . $planetService->getPlanetCoordinates()->asString() . ']
'; @@ -104,6 +107,26 @@ public function getBody(): string return $planetName; }, $body); + $body = preg_replace_callback('/\[coordinates\](\d+):(\d+):(\d+)\[\/coordinates\]/', function ($matches) { + // Assuming getPlayerNameById is a method to get a player's name by ID + if (!is_numeric($matches[1]) || !is_numeric($matches[2]) || !is_numeric($matches[3])) { + return "Unknown Planet"; + } + + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + $planetService = $planetServiceFactory->makeForCoordinate(new Coordinate((int)$matches[1], (int)$matches[2], (int)$matches[3])); + + if ($planetService != null) { + $planetName = ' +
+ [' . $planetService->getPlanetCoordinates()->asString() . ']
'; + } else { + $planetName = "Unknown Planet"; + } + + return $planetName; + }, $body); + return $body; } diff --git a/database/migrations/2024_05_04_171614_update_fleet_missions_table.php b/database/migrations/2024_05_04_171614_update_fleet_missions_table.php new file mode 100644 index 00000000..835914bb --- /dev/null +++ b/database/migrations/2024_05_04_171614_update_fleet_missions_table.php @@ -0,0 +1,36 @@ +integer('galaxy_from')->after('planet_id_from')->nullable(); + $table->integer('system_from')->after('galaxy_from')->nullable(); + $table->integer('position_from')->after('system_from')->nullable(); + + // Make planet_id_from nullable as it is not available for all mission types. + $table->integer('planet_id_from', false, true)->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('fleet_missions', function (Blueprint $table) { + $table->dropColumn('galaxy_from'); + $table->dropColumn('system_from'); + $table->dropColumn('position_from'); + }); + } +}; diff --git a/public/img/fleet/7.gif b/public/img/fleet/7.gif new file mode 100644 index 0000000000000000000000000000000000000000..85a4865b74bc07b04cf3822143774ae752a9e2d4 GIT binary patch literal 1136 zcmV-$1dsbiNk%w1VG#fk0Qdg@CaS9?r>Q2Rr6HW1Bb%Hal9LyCc@=nhC#|k1t*#}g zss8`}7kzvll9U>ThbXVGBA=k}{{JMVrWt&E9+8q5eS9gatu)EWD66doRaGmwyD-Yj z7p0~tu&^DCjxN>MA)1>drKJ~MUn#V;CakL^qN5_8pBQ|79k8;K=jb7tnXvZt`u_bJ zhl$_p>-hftW#Zv2z`y}jRzbVEn8n2(m6jr&pflRuG0V>uWMm+kn)?0xD6FnF+1e(n ztTNBfD6X%e*Vyv*^lGxRD66Y1y1ON;ts%F$^Zx(z|Nk#>Zwpja6q=d*{{JSasY|1! zC$qAe*Vg_0{~L*mAeNWC;NRZ+`z6ZDAeo#Xo1P9^T`{${d+F*&;o~pJ$(!8W=llEt zP*Ewevoqb^Ex*5`<>e-n)vpz;0w@%{fV#l|1M!Y8n>5@lr^j*tpv zW+S7a`2GDHi;Nb0eHwy>D!IBTxw$5-ttFgmX{iSevrJo5{io^tEnZkvmbtaCaEV%PWqKF3HL;y(-Y|lLCZwiQii#}4!)czMBAuWUad9x;->3BRExf-j zy}l)`vGV-;rQ_x%tgrh1|NH;`?*08H;^icvq`~j-Bcr7wpP}&m|1Pn#G2r4Xv$Gj| zeKN$w_5S`OprIzQvE}me7ItWW5J%81bz@8NWcRF5fYvLNt0zG zohp0@I@Ejf0D%H(xJ>M6rY{755xMyBx}wXAFg)P`$onHng#t0>M&0Pbh0vH&8;T&P zMot3=BcXm6i#EyMu{6DUwE5F*8!{}QVtk0y$_h6~D-vPDL9fJxWau8afdOqn5;RDf zyh!CLkAQVP*m+{aijK7i5!SE~1SZ8e0%U;E+V#z!vorH7EqkVJ0i7r2vPlRtObVhX zg$@C7#OzCuL7QYO%9o+kw?G^!mGL(%778X+T;za4N{J8}oC1hb#KPB+SDA+F2-c{U z2uh#Y5pZ&C#ujA)8o>K?$=jSIavr@+t3xHry#YGnIXG}@77$`u>^SgHV}Ke65Mc44 z(C-c$38vUGK?_sFLBT!+G_X%O?(`u_F{u2riUB7~piKpsFu*_sh8S=FIGX$u5CA(U C`%Lu! literal 0 HcmV?d00001 diff --git a/tests/AccountTestCase.php b/tests/AccountTestCase.php index a57ac582..59b10e82 100644 --- a/tests/AccountTestCase.php +++ b/tests/AccountTestCase.php @@ -10,6 +10,7 @@ use OGame\Models\Planet; use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; +use OGame\Models\User; use OGame\Services\PlanetService; use OGame\Services\PlayerService; @@ -39,6 +40,19 @@ protected function setUp(): void protected PlanetService $planetService; + /** + * By default, Laravel does not refresh the application state between requests in a single test. + * By invoking this method we ensure that the application state is refreshed to avoid any side effects from + * previous requests such as planets not being updated on the initial request. + * + * @return void + */ + public function reloadApplication(): void + { + $this->refreshApplication(); + $this->be(User::find($this->currentUserId)); + } + /** * Create a new user and login via the register form on login page. * diff --git a/tests/Feature/FactoryTest.php b/tests/Feature/FactoryTest.php index 7ac95564..3bd414dc 100644 --- a/tests/Feature/FactoryTest.php +++ b/tests/Feature/FactoryTest.php @@ -2,6 +2,8 @@ namespace Tests\Feature; +use OGame\Factories\PlanetServiceFactory; +use OGame\Models\Planet; use Tests\AccountTestCase; /** @@ -55,11 +57,11 @@ public function testPlanetFactoryLoad(): void } // Get the first planet of each user. - $planet1 = \OGame\Models\Planet::where('user_id', $playerIds[0])->first(); - $planet2 = \OGame\Models\Planet::where('user_id', $playerIds[1])->first(); + $planet1 = Planet::where('user_id', $playerIds[0])->first(); + $planet2 = Planet::where('user_id', $playerIds[1])->first(); // Get the planet service factory. - $planetServiceFactory = app()->make(\OGame\Factories\PlanetServiceFactory::class); + $planetServiceFactory = app()->make(PlanetServiceFactory::class); // Load the first planet. $planetService1 = $planetServiceFactory->make($planet1->id); diff --git a/tests/Feature/FleetDispatch/FleetDispatchColoniseTest.php b/tests/Feature/FleetDispatch/FleetDispatchColoniseTest.php index cb72e485..7996faf9 100644 --- a/tests/Feature/FleetDispatch/FleetDispatchColoniseTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchColoniseTest.php @@ -4,8 +4,12 @@ use Exception; use Illuminate\Contracts\Container\BindingResolutionException; +use Illuminate\Support\Carbon; +use OGame\Factories\PlanetServiceFactory; use OGame\GameObjects\Models\UnitCollection; +use OGame\Models\Message; use OGame\Models\Resources; +use OGame\Services\FleetMissionService; use Tests\FleetDispatchTestCase; /** @@ -21,7 +25,7 @@ class FleetDispatchColoniseTest extends FleetDispatchTestCase /** * @var string The mission name for the test, displayed in UI. */ - protected string $missionName = 'Colonise'; + protected string $missionName = 'Colonisation'; protected bool $hasReturnMission = false; @@ -99,4 +103,216 @@ public function testDispatchFleetWithoutColonyShipFails(): void // Expecting 500 error. $this->sendMissionToEmptyPosition($unitCollection, new Resources(0, 0, 0, 0), 500); } + + /** + * Main test for colonizing an empty planet (happy path). + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetColonizeEmptyPlanet(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Send fleet to an empty planet. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('colony_ship'), 1); + $newPlanetCoordinates = $this->sendMissionToEmptyPosition($unitCollection, new Resources(100, 0, 0, 0)); + + // Increase time by 10 hours to ensure the mission is done. + Carbon::setTestNow($startTime->copy()->addHours(10)); + + // Do a request to trigger the update logic. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the new planet has been created. + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + $newPlanet = $planetServiceFactory->makeForCoordinate($newPlanetCoordinates); + $this->assertNotNull($newPlanet, 'New planet cannot be loaded while it should have been created.'); + + // Assert that last message sent to current player contains the new planet colonize confirm message. + $lastMessage = Message::where('user_id', $this->currentUserId) + ->orderBy('id', 'desc') + ->first(); + + $this->assertStringContainsString('The fleet has arrived', $lastMessage->body); + $this->assertStringContainsString('found a new planet there and are beginning to develop upon it immediately.', $lastMessage->body); + } + + /** + * Test that when sending cargos along with the colony ship, the resources are added to the new planet + * and the cargo ships return without the colony ship. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetColonizeReturnTripCargo(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 1 colony ship and 5 small cargos. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'colony_ship', 1); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5); + + // Send fleet to an empty planet. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('colony_ship'), 1); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 3); + $newPlanetCoordinates = $this->sendMissionToEmptyPosition($unitCollection, new Resources(400, 400, 0, 0)); + + // Assert that the cargo ships have been sent. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'colony_ship', 0); + $this->assertObjectLevelOnPage($response, 'small_cargo', 2); + + // Increase time by 10 hours to ensure the arrival and return missions are done. + Carbon::setTestNow($startTime->copy()->addHours(10)); + + // Do a request to trigger the update logic. + // Note: we only make one request here, as the arrival and return missions should be processed in the same request + // since enough time has passed. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the new planet has been created. + $planetServiceFactory = app()->make(PlanetServiceFactory::class); + $newPlanet = $planetServiceFactory->makeForCoordinate($newPlanetCoordinates); + $this->assertNotNull($newPlanet, 'New planet cannot be loaded while it should have been created.'); + + // Assert that the cargo ships have returned without the colony ship. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'colony_ship', 0); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5); + } + + /** + * Test that when a mission has been sent and update happens long time later, both the arrival + * and return missions are processed in the same request. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetColonizeReturnTripProcessSingleRequest(): void + { + $this->basicSetup(); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 1 colony ship and 5 small cargos. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'colony_ship', 1); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5); + + // Send fleet to an empty planet. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('colony_ship'), 1); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 3); + $this->sendMissionToEmptyPosition($unitCollection, new Resources(400, 400, 0, 0)); + + // Increase time by 10 hours to ensure the arrival and return missions are done. + Carbon::setTestNow($startTime->copy()->addHours(10)); + + // Do a request to trigger the update logic. + // Note: we only make one request here, as the arrival and return missions should be processed in the same request + // since enough time has passed. + $response = $this->get('/shipyard'); + $response->assertStatus(200); + + // Assert that the cargo ships have returned without the colony ship. + $this->assertObjectLevelOnPage($response, 'small_cargo', 5); + $this->assertObjectLevelOnPage($response, 'colony_ship', 0); + } + + /** + * Verify that canceling/recalling an active mission works. + * @throws BindingResolutionException + * @throws Exception + */ + public function testDispatchFleetRecallMission(): void + { + $this->basicSetup(); + + // Add resources for test. + $this->planetAddResources(new Resources(5000, 5000, 0, 0)); + + // Set time to static time 2024-01-01 + $startTime = Carbon::create(2024, 1, 1, 0, 0, 0); + Carbon::setTestNow($startTime); + + // Assert that we begin with 5 small cargo ships and 1 colony ship on planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'colony_ship', 1); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5); + // Send fleet to the second planet of the test user. + $unitCollection = new UnitCollection(); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 5); + $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('colony_ship'), 1); + $this->sendMissionToEmptyPosition($unitCollection, new Resources(5000, 5000, 0, 0)); + + // Get just dispatched fleet mission ID from database. + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + + // Advance time by 1 minute + $fleetParentTime = $startTime->copy()->addMinutes(1); + Carbon::setTestNow($fleetParentTime); + + // Cancel the mission + $response = $this->post('/ajax/fleet/dispatch/recall-fleet', [ + 'fleet_mission_id' => $fleetMissionId, + '_token' => csrf_token(), + ]); + $response->assertStatus(200); + + // Assert that the original mission is now canceled. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->canceled == 1, 'Fleet mission is not canceled after fleet recall is requested.'); + + // Assert that only the return trip is now visible. + // The eventbox should only show 1 mission (the parent). + $response = $this->get('/ajax/fleet/eventbox/fetch'); + $response->assertStatus(200); + $response->assertJsonFragment(['friendly' => 1]); + $response->assertJsonFragment(['eventText' => $this->missionName . ' (R)']); + + $fleetMissionService = app()->make(FleetMissionService::class, ['player' => $this->planetService->getPlayer()]); + $fleetMission = $fleetMissionService->getActiveFleetMissionsForCurrentPlayer()->first(); + $fleetMissionId = $fleetMission->id; + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + + // Assert that the return trip arrival time is exactly 1 minute after the cancelation time. + // Because the return trip should take exactly as long as the original trip has traveled until it was canceled. + $this->assertTrue($fleetMission->time_arrival == $fleetParentTime->addSeconds(60)->timestamp, 'Return trip duration is not the same as the original mission has been active.'); + + // Advance time by amount it takes for the return trip to arrive. + Carbon::setTestNow(Carbon::createFromTimestamp($fleetMission->time_arrival)); + + // Do a request to trigger the update logic. + $this->get('/overview'); + + // Assert that the return trip is processed. + $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + $this->assertTrue($fleetMission->processed == 1, 'Return trip is not processed after fleet has arrived back at origin planet.'); + + // Assert that the units have been returned to the origin planet. + $response = $this->get('/shipyard'); + $this->assertObjectLevelOnPage($response, 'colony_ship', 1, 'Colony ship is not at original 1 units after recalled trip has been processed.'); + $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at original 5 units after recalled trip has been processed.'); + // Assert that the resources have been returned to the origin planet. + $this->planetService->reloadPlanet(); + $this->assertTrue($this->planetService->hasResources(new Resources(5000, 5000, 0, 0)), 'Resources are not returned to origin planet after recalling mission.'); + } } diff --git a/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php b/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php index 4ebf7ce0..206821a2 100644 --- a/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchDeployTest.php @@ -399,6 +399,10 @@ public function testDispatchFleetRecallMission(): void $fleetMissionId = $fleetMission->id; $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + // Assert that the return trip arrival time is exactly 1 minute after the cancelation time. + // Because the return trip should take exactly as long as the original trip has traveled until it was canceled. + $this->assertTrue($fleetMission->time_arrival == $fleetParentTime->addSeconds(60)->timestamp, 'Return trip duration is not the same as the original mission has been active.'); + // Advance time by amount of minutes it takes for the return trip to arrive. Carbon::setTestNow(Carbon::createFromTimestamp($fleetMission->time_arrival)); @@ -413,6 +417,7 @@ public function testDispatchFleetRecallMission(): void $response = $this->get('/shipyard'); $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at original 5 units after recalled trip has been processed.'); // Assert that the resources have been returned to the origin planet. + $this->planetService->reloadPlanet(); $this->assertTrue($this->planetService->hasResources(new Resources(5000, 5000, 0, 0)), 'Resources are not returned to origin planet after recalling mission.'); } diff --git a/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php b/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php index 8fdb208e..f93d9534 100644 --- a/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php @@ -8,6 +8,7 @@ use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Message; use OGame\Models\Resources; +use OGame\Models\User; use OGame\Services\FleetMissionService; use Tests\FleetDispatchTestCase; @@ -174,6 +175,9 @@ public function testDispatchFleetDeductResources(): void $this->basicSetup(); $this->get('/shipyard'); + $this->refreshApplication(); // Reboot the application + $this->be(User::find($this->currentUserId)); + // Get beginning resources of the planet. $beginningMetal = $this->planetService->metal()->get(); $beginningCrystal = $this->planetService->crystal()->get(); @@ -183,6 +187,9 @@ public function testDispatchFleetDeductResources(): void $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); + $this->refreshApplication(); // Reboot the application + $this->be(User::find($this->currentUserId)); + $response = $this->get('/shipyard'); $response->assertStatus(200); @@ -401,6 +408,10 @@ public function testDispatchFleetRecallMission(): void $fleetMissionId = $fleetMission->id; $fleetMission = $fleetMissionService->getFleetMissionById($fleetMissionId, false); + // Assert that the return trip arrival time is exactly 1 minute after the cancelation time. + // Because the return trip should take exactly as long as the original trip has traveled until it was canceled. + $this->assertTrue($fleetMission->time_arrival == $fleetParentTime->addSeconds(60)->timestamp, 'Return trip duration is not the same as the original mission has been active.'); + // Advance time by amount of minutes it takes for the return trip to arrive. Carbon::setTestNow(Carbon::createFromTimestamp($fleetMission->time_arrival)); @@ -415,6 +426,7 @@ public function testDispatchFleetRecallMission(): void $response = $this->get('/shipyard'); $this->assertObjectLevelOnPage($response, 'small_cargo', 5, 'Small Cargo ships are not at original 5 units after recalled trip has been processed.'); // Assert that the resources have been returned to the origin planet. + $this->planetService->reloadPlanet(); $this->assertTrue($this->planetService->hasResources(new Resources(5000, 5000, 0, 0)), 'Resources are not returned to origin planet after recalling mission.'); } diff --git a/tests/FleetDispatchTestCase.php b/tests/FleetDispatchTestCase.php index dc02a4b6..d9eaeb9e 100644 --- a/tests/FleetDispatchTestCase.php +++ b/tests/FleetDispatchTestCase.php @@ -6,6 +6,7 @@ use OGame\GameObjects\Models\UnitCollection; use OGame\Models\Planet\Coordinate; use OGame\Models\Resources; +use OGame\Models\User; use OGame\Services\PlanetService; /** @@ -96,12 +97,13 @@ protected function sendMissionToOtherPlayer(UnitCollection $units, Resources $re * @param UnitCollection $units * @param Resources $resources * @param int $assertStatus - * @return void + * @return Coordinate */ - protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): void + protected function sendMissionToEmptyPosition(UnitCollection $units, Resources $resources, int $assertStatus = 200): Coordinate { $coordinates = $this->getNearbyEmptyCoordinate(); $this->dispatchFleet($coordinates, $units, $resources, $assertStatus); + return $coordinates; } /** @@ -138,6 +140,8 @@ protected function checkTargetFleet(Coordinate $coordinates, UnitCollection $uni ] ]); } + + $this->reloadApplication(); } /** @@ -166,6 +170,9 @@ protected function dispatchFleet(Coordinate $coordinates, UnitCollection $units, ], $unitsArray)); $post->assertStatus($assertStatus); + + $this->reloadApplication(); + $this->get('/ajax/fleet/eventbox/fetch')->assertStatus(200); $this->get('/ajax/fleet/eventlist/fetch')->assertStatus(200); } From db9a93d3b98725b01d6188e9c8599a34430b3b37 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 4 May 2024 22:56:13 +0200 Subject: [PATCH 09/11] Fix tests --- tests/Feature/FleetDispatch/FleetDispatchTransportTest.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php b/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php index f93d9534..6f830d40 100644 --- a/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchTransportTest.php @@ -175,9 +175,6 @@ public function testDispatchFleetDeductResources(): void $this->basicSetup(); $this->get('/shipyard'); - $this->refreshApplication(); // Reboot the application - $this->be(User::find($this->currentUserId)); - // Get beginning resources of the planet. $beginningMetal = $this->planetService->metal()->get(); $beginningCrystal = $this->planetService->crystal()->get(); @@ -187,9 +184,6 @@ public function testDispatchFleetDeductResources(): void $unitCollection->addUnit($this->planetService->objects->getUnitObjectByMachineName('small_cargo'), 1); $this->sendMissionToSecondPlanet($unitCollection, new Resources(100, 100, 0, 0)); - $this->refreshApplication(); // Reboot the application - $this->be(User::find($this->currentUserId)); - $response = $this->get('/shipyard'); $response->assertStatus(200); From 330a2b9e463bf34f3b09eb0e04e22e7fc6e3a3db Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 4 May 2024 23:13:00 +0200 Subject: [PATCH 10/11] Add extra false positive test --- .../FleetDispatchGenericTest.php | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/Feature/FleetDispatch/FleetDispatchGenericTest.php diff --git a/tests/Feature/FleetDispatch/FleetDispatchGenericTest.php b/tests/Feature/FleetDispatch/FleetDispatchGenericTest.php new file mode 100644 index 00000000..84bbec0f --- /dev/null +++ b/tests/Feature/FleetDispatch/FleetDispatchGenericTest.php @@ -0,0 +1,89 @@ +planetSetObjectLevel('robot_factory', 2); + $this->planetSetObjectLevel('shipyard', 1); + $this->planetSetObjectLevel('research_lab', 1); + $this->playerSetResearchLevel('energy_technology', 1); + $this->playerSetResearchLevel('combustion_drive', 1); + $this->planetAddUnit('small_cargo', 5); + $this->planetAddUnit('colony_ship', 1); + } + + /** + * Test that deducting resources without saving the planet does not still save it + * in another request. This is to make sure the test logic works correctly and + * objects are not cached between requests in the same test. + * + * @throws BindingResolutionException + * @throws Exception + */ + public function testDeductResourcesWithoutSavingPlanetIgnored(): void + { + $this->basicSetup(); + + // Add resources for test. + $this->planetAddResources(new Resources(5000, 5000, 0, 0)); + + // Do initial HTTP request. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Reload application to make sure the planet is not cached. + $this->reloadApplication(); + + // Deduct resources from the planet but don't save the planet itself. + // This should NOT deduct the resources. + $this->planetService->deductResources(new Resources(5000, 5000, 0, 0), false); + + // Reload application to make sure the planet is not cached. + // NOTE: without this method, the planet would be cached and the resources would be deducted + // during the next HTTP request in this test. + $this->reloadApplication(); + + // Do another HTTP request. + $response = $this->get('/overview'); + $response->assertStatus(200); + + // Assert that the resources are NOT deducted. + $this->planetService->reloadPlanet(); + $this->assertTrue($this->planetService->hasResources(new Resources(5000, 5000, 0, 0)), 'Resources are deducted from planet without saving it. State seems to be cached between requests. Check the AccountTestCase::reloadApplication() test logic.'); + } + +} From d22deb488d79adc7c8f93d86fef13c824e553b8d Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 4 May 2024 23:15:31 +0200 Subject: [PATCH 11/11] Update FleetDispatchGenericTest.php --- tests/Feature/FleetDispatch/FleetDispatchGenericTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Feature/FleetDispatch/FleetDispatchGenericTest.php b/tests/Feature/FleetDispatch/FleetDispatchGenericTest.php index 84bbec0f..6c4b7dbd 100644 --- a/tests/Feature/FleetDispatch/FleetDispatchGenericTest.php +++ b/tests/Feature/FleetDispatch/FleetDispatchGenericTest.php @@ -4,12 +4,7 @@ use Exception; use Illuminate\Contracts\Container\BindingResolutionException; -use Illuminate\Support\Carbon; -use OGame\Factories\PlanetServiceFactory; -use OGame\GameObjects\Models\UnitCollection; -use OGame\Models\Message; use OGame\Models\Resources; -use OGame\Services\FleetMissionService; use Tests\FleetDispatchTestCase; /**