From 836df549b0730e9d4cc443055c1294b37e9c8e0d Mon Sep 17 00:00:00 2001 From: root Date: Mon, 20 Jul 2026 00:58:39 +0000 Subject: [PATCH 1/6] feat(admin): add remaining server settings and admin tools (#167) Wire configurable probe capacity, deuterium consumption, systems per galaxy, and fix initial dark matter / universe name persistence. Add activity logs and cron task admin pages, and honor universe bounds across galaxy, fleet, phalanx, and planet relocation UIs. Co-authored-by: Cursor --- app/Factories/PlanetServiceFactory.php | 2 +- .../Properties/CapacityPropertyService.php | 30 ++- .../Admin/ActivityLogsController.php | 105 ++++++++ .../Controllers/Admin/CronTasksController.php | 103 ++++++++ .../Admin/DeveloperShortcutsController.php | 6 +- .../Admin/ServerSettingsController.php | 14 +- app/Http/Controllers/FleetController.php | 23 +- app/Http/Controllers/GalaxyController.php | 24 +- app/Http/Controllers/PhalanxController.php | 8 +- app/Http/Controllers/PlanetMoveController.php | 11 + .../Controllers/ServerSettingsController.php | 3 + app/Observers/UserObserver.php | 4 +- app/Services/CoordinateDistanceCalculator.php | 10 +- app/Services/FleetMissionService.php | 9 +- app/Services/PlanetMoveService.php | 12 +- app/Services/SettingsService.php | 46 +++- .../views/ingame/admin/activitylogs.blade.php | 224 ++++++++++++++++++ .../views/ingame/admin/crontasks.blade.php | 72 ++++++ .../ingame/admin/developershortcuts.blade.php | 8 +- .../ingame/admin/serversettings.blade.php | 37 ++- resources/views/ingame/fleet/index.blade.php | 11 +- resources/views/ingame/galaxy/index.blade.php | 2 +- .../views/ingame/layouts/admin-menu.blade.php | 2 + resources/views/ingame/layouts/main.blade.php | 4 +- .../ingame/serversettings/overlay.blade.php | 11 +- routes/web.php | 9 + .../AdminServerSettingsOptionsTest.php | 133 +++++++++++ tests/Feature/AdminTest.php | 2 + tests/Unit/UnitCollectionTest.php | 10 +- 29 files changed, 880 insertions(+), 55 deletions(-) create mode 100644 app/Http/Controllers/Admin/ActivityLogsController.php create mode 100644 app/Http/Controllers/Admin/CronTasksController.php create mode 100644 resources/views/ingame/admin/activitylogs.blade.php create mode 100644 resources/views/ingame/admin/crontasks.blade.php create mode 100644 tests/Feature/AdminServerSettingsOptionsTest.php diff --git a/app/Factories/PlanetServiceFactory.php b/app/Factories/PlanetServiceFactory.php index 79e4c18d4..04fe29819 100644 --- a/app/Factories/PlanetServiceFactory.php +++ b/app/Factories/PlanetServiceFactory.php @@ -335,7 +335,7 @@ public function determineNewPlanetPosition(): Coordinate // System is full, move to next system $system++; - if ($system > UniverseConstants::MAX_SYSTEM_COUNT) { + if ($system > $this->settings->numberOfSystems()) { // Galaxy is full, move to next galaxy $system = UniverseConstants::MIN_SYSTEM; $galaxy++; diff --git a/app/GameObjects/Services/Properties/CapacityPropertyService.php b/app/GameObjects/Services/Properties/CapacityPropertyService.php index cb34ab659..719b99372 100644 --- a/app/GameObjects/Services/Properties/CapacityPropertyService.php +++ b/app/GameObjects/Services/Properties/CapacityPropertyService.php @@ -6,6 +6,7 @@ use OGame\GameObjects\Services\Properties\Abstracts\ObjectPropertyService; use OGame\Services\CharacterClassService; use OGame\Services\PlayerService; +use OGame\Services\SettingsService; /** * Class CapacityPropertyService. @@ -24,14 +25,16 @@ class CapacityPropertyService extends ObjectPropertyService */ public function calculateProperty(PlayerService $player): GameObjectPropertyDetails { + $baseValue = $this->resolveBaseValue(); + $bonusPercentage = $this->getBonusPercentage($player); // Use integer arithmetic to avoid floating point precision issues - $bonusValue = intdiv($this->base_value * $bonusPercentage, 100); + $bonusValue = intdiv($baseValue * $bonusPercentage, 100); - $totalValue = $this->base_value + $bonusValue; + $totalValue = $baseValue + $bonusValue; $breakdown = [ - 'rawValue' => $this->base_value, + 'rawValue' => $baseValue, 'bonuses' => [ [ 'type' => 'Research bonus', @@ -46,7 +49,7 @@ public function calculateProperty(PlayerService $player): GameObjectPropertyDeta $classBonus = $this->getCharacterClassCargoBonus($player); if ($classBonus > 0) { // Use integer arithmetic to avoid floating point precision issues - $classBonusValue = intdiv($this->base_value * $classBonus, 100); + $classBonusValue = intdiv($baseValue * $classBonus, 100); $totalValue += $classBonusValue; $breakdown['bonuses'][] = [ @@ -57,7 +60,24 @@ public function calculateProperty(PlayerService $player): GameObjectPropertyDeta $breakdown['totalValue'] = $totalValue; } - return new GameObjectPropertyDetails($this->base_value, $bonusValue, $totalValue, $breakdown); + return new GameObjectPropertyDetails($baseValue, $bonusValue, $totalValue, $breakdown); + } + + /** + * Resolve the effective base cargo capacity, applying universe settings + * such as espionage probe capacity when enabled. + */ + private function resolveBaseValue(): int + { + if ($this->parent_object->machine_name === 'espionage_probe') { + $settingsService = app(SettingsService::class); + if ($settingsService->espionageProbeCapacityOn()) { + // Classic OGame: probes gain 5 cargo capacity when the setting is on. + return 5; + } + } + + return $this->base_value; } /** diff --git a/app/Http/Controllers/Admin/ActivityLogsController.php b/app/Http/Controllers/Admin/ActivityLogsController.php new file mode 100644 index 000000000..0a401b19f --- /dev/null +++ b/app/Http/Controllers/Admin/ActivityLogsController.php @@ -0,0 +1,105 @@ +input('tab', 'fleets'); + if (!in_array($tab, ['fleets', 'buildings', 'units', 'research'], true)) { + $tab = 'fleets'; + } + + $fleets = null; + $buildings = null; + $units = null; + $research = null; + $users = collect(); + $planets = collect(); + + if ($tab === 'fleets') { + $fleets = FleetMission::query() + ->orderByDesc('id') + ->paginate(self::PER_PAGE) + ->withQueryString(); + + $userIds = $fleets->pluck('user_id')->unique(); + $users = User::whereIn('id', $userIds)->pluck('username', 'id'); + } elseif ($tab === 'buildings') { + $buildings = BuildingQueue::query() + ->orderByDesc('id') + ->paginate(self::PER_PAGE) + ->withQueryString(); + + $planetIds = $buildings->pluck('planet_id')->unique(); + $planets = Planet::whereIn('id', $planetIds)->get()->keyBy('id'); + $users = User::whereIn('id', $planets->pluck('user_id')->unique())->pluck('username', 'id'); + } elseif ($tab === 'units') { + $units = UnitQueue::query() + ->orderByDesc('id') + ->paginate(self::PER_PAGE) + ->withQueryString(); + + $planetIds = $units->pluck('planet_id')->unique(); + $planets = Planet::whereIn('id', $planetIds)->get()->keyBy('id'); + $users = User::whereIn('id', $planets->pluck('user_id')->unique())->pluck('username', 'id'); + } else { + $research = ResearchQueue::query() + ->orderByDesc('id') + ->paginate(self::PER_PAGE) + ->withQueryString(); + + $planetIds = $research->pluck('planet_id')->unique(); + $planets = Planet::whereIn('id', $planetIds)->get()->keyBy('id'); + $users = User::whereIn('id', $planets->pluck('user_id')->unique())->pluck('username', 'id'); + } + + $objectNames = []; + foreach (ObjectService::getBuildingObjects() as $object) { + $objectNames[$object->id] = $object->title; + } + foreach (ObjectService::getStationObjects() as $object) { + $objectNames[$object->id] = $object->title; + } + foreach (ObjectService::getResearchObjects() as $object) { + $objectNames[$object->id] = $object->title; + } + foreach (ObjectService::getShipObjects() as $object) { + $objectNames[$object->id] = $object->title; + } + foreach (ObjectService::getDefenseObjects() as $object) { + $objectNames[$object->id] = $object->title; + } + + return view('ingame.admin.activitylogs', [ + 'tab' => $tab, + 'fleets' => $fleets, + 'buildings' => $buildings, + 'units' => $units, + 'research' => $research, + 'users' => $users, + 'planets' => $planets, + 'objectNames' => $objectNames, + 'missionTypeLabels' => collect(GameMissionFactory::getAllMissions()) + ->mapWithKeys(fn ($mission, $id) => [$id => $mission::getName()]), + ]); + } +} diff --git a/app/Http/Controllers/Admin/CronTasksController.php b/app/Http/Controllers/Admin/CronTasksController.php new file mode 100644 index 000000000..df90957e5 --- /dev/null +++ b/app/Http/Controllers/Admin/CronTasksController.php @@ -0,0 +1,103 @@ + + */ + private const ALLOWED_COMMANDS = [ + 'ogamex:scheduler:generate-highscores', + 'ogamex:scheduler:generate-alliance-highscores', + 'ogamex:scheduler:generate-highscore-ranks', + 'ogamex:scheduler:reset-debris-fields', + 'ogamex:scheduler:cleanup-wreckfields', + 'ogamex:scheduler:delete-old-messages', + 'ogamex:scheduler:darkmatter-regenerate', + ]; + + /** + * Shows scheduled cron tasks and allows manual runs. + */ + public function index(Schedule $schedule): View + { + $tasks = []; + + foreach ($schedule->events() as $event) { + $command = $this->extractCommandName($event->command ?? ''); + $tasks[] = [ + 'expression' => $event->expression, + 'description' => $event->description ?: $command, + 'command' => $command, + 'next_due' => $event->nextRunDate()->format('Y-m-d H:i:s'), + 'without_overlapping' => (bool)$event->withoutOverlapping, + 'runnable' => in_array($command, self::ALLOWED_COMMANDS, true), + ]; + } + + return view('ingame.admin.crontasks', [ + 'tasks' => $tasks, + ]); + } + + /** + * Manually run a scheduled command. + */ + public function run(Request $request): RedirectResponse + { + $validated = $request->validate([ + 'command' => 'required|string|max:255', + ]); + + $command = $validated['command']; + + if (!in_array($command, self::ALLOWED_COMMANDS, true)) { + return redirect()->route('admin.crontasks.index') + ->with('error', __('This command cannot be run from the admin panel.')); + } + + try { + Artisan::call($command); + $output = trim(Artisan::output()); + $message = __('Command ran successfully: :command', ['command' => $command]); + if ($output !== '') { + $message .= ' — ' . Str::limit($output, 200); + } + + return redirect()->route('admin.crontasks.index')->with('success', $message); + } catch (Throwable $e) { + return redirect()->route('admin.crontasks.index') + ->with('error', __('Failed to run command: :error', ['error' => $e->getMessage()])); + } + } + + /** + * Extract a readable command identifier from a scheduled event command string. + */ + private function extractCommandName(string $command): string + { + // Typical format: '/usr/bin/php artisan ogamex:scheduler:generate-highscores' + if (preg_match("/artisan\s+([^\s']+)/", $command, $matches)) { + return $matches[1]; + } + + // Some Laravel versions store the signature/FQCN directly. + if (str_starts_with($command, 'ogamex:')) { + return $command; + } + + return $command; + } +} diff --git a/app/Http/Controllers/Admin/DeveloperShortcutsController.php b/app/Http/Controllers/Admin/DeveloperShortcutsController.php index adbc482e0..decb7fda0 100644 --- a/app/Http/Controllers/Admin/DeveloperShortcutsController.php +++ b/app/Http/Controllers/Admin/DeveloperShortcutsController.php @@ -180,7 +180,7 @@ public function updateResources(Request $request, PlayerService $playerService, // Validate coordinates $validated = $request->validate([ 'galaxy' => 'required|integer|min:1|max:' . $settingsService->numberOfGalaxies(), - 'system' => 'required|integer|min:1|max:' . UniverseConstants::MAX_SYSTEM_COUNT, + 'system' => 'required|integer|min:1|max:' . $settingsService->numberOfSystems(), 'position' => 'required|integer|min:1|max:' . UniverseConstants::MAX_PLANET_POSITION, ]); @@ -244,7 +244,7 @@ public function createAtCoords(Request $request, PlanetServiceFactory $planetSer // Validate coordinates $validated = $request->validate([ 'galaxy' => 'required|integer|min:1|max:' . $settingsService->numberOfGalaxies(), - 'system' => 'required|integer|min:1|max:' . UniverseConstants::MAX_SYSTEM_COUNT, + 'system' => 'required|integer|min:1|max:' . $settingsService->numberOfSystems(), 'position' => 'required|integer|min:1|max:' . UniverseConstants::MAX_PLANET_POSITION, ]); @@ -377,7 +377,7 @@ public function updateDarkMatter(Request $request, PlanetServiceFactory $planetS // Validate coordinates and amount $validated = $request->validate([ 'galaxy' => 'required|integer|min:1|max:' . $settingsService->numberOfGalaxies(), - 'system' => 'required|integer|min:1|max:' . UniverseConstants::MAX_SYSTEM_COUNT, + 'system' => 'required|integer|min:1|max:' . $settingsService->numberOfSystems(), 'position' => 'required|integer|min:1|max:' . UniverseConstants::MAX_PLANET_POSITION, 'dark_matter' => 'required', ]); diff --git a/app/Http/Controllers/Admin/ServerSettingsController.php b/app/Http/Controllers/Admin/ServerSettingsController.php index a36b701e5..cd452641d 100644 --- a/app/Http/Controllers/Admin/ServerSettingsController.php +++ b/app/Http/Controllers/Admin/ServerSettingsController.php @@ -35,6 +35,8 @@ public function index(PlayerService $player, SettingsService $settingsService): 'universe_name' => $settingsService->universeName(), 'planet_fields_bonus' => $settingsService->planetFieldsBonus(), 'dark_matter_bonus' => $settingsService->darkMatterBonus(), + 'espionage_probe_capacity_on' => $settingsService->espionageProbeCapacityOn(), + 'deuterium_consumption' => $settingsService->deuteriumConsumption(), 'alliance_combat_system_on' => $settingsService->allianceCombatSystemOn(), 'alliance_cooldown_days' => $settingsService->allianceCooldownDays(), 'debris_field_from_ships' => $settingsService->debrisFieldFromShips(), @@ -49,6 +51,7 @@ public function index(PlayerService $player, SettingsService $settingsService): 'ignore_empty_systems_on' => $settingsService->ignoreEmptySystemsOn(), 'ignore_inactive_systems_on' => $settingsService->ignoreInactiveSystemsOn(), 'number_of_galaxies' => $settingsService->numberOfGalaxies(), + 'number_of_systems' => $settingsService->numberOfSystems(), 'battle_engine' => $settingsService->battleEngine(), 'dark_matter_regen_enabled' => (bool)$settingsService->get('dark_matter_regen_enabled', 0), 'dark_matter_regen_amount' => (int)$settingsService->get('dark_matter_regen_amount', 150000), @@ -96,9 +99,13 @@ public function update(SettingsService $settingsService): RedirectResponse $settingsService->set('basic_income_energy', request('basic_income_energy')); $settingsService->set('registration_planet_amount', request('registration_planet_amount')); + $settingsService->set('universe_name', request('universe_name', 'Universe')); $settingsService->set('planet_fields_bonus', request('planet_fields_bonus')); - $settingsService->set('dark_matter_bonus', request('dark_matter_bonus')); + // Persist as dark_matter_initial so registration (UserObserver) picks it up. + $settingsService->set('dark_matter_initial', max(0, (int)request('dark_matter_bonus', 8000))); + $settingsService->set('espionage_probe_capacity_on', request('espionage_probe_capacity_on', 0)); + $settingsService->set('deuterium_consumption', request('deuterium_consumption', '1.0')); $settingsService->set('alliance_combat_system_on', request('alliance_combat_system_on', 0)); $settingsService->set('alliance_cooldown_days', request('alliance_cooldown_days', 3)); $settingsService->set('debris_field_from_ships', request('debris_field_from_ships')); @@ -113,7 +120,10 @@ public function update(SettingsService $settingsService): RedirectResponse $settingsService->set('ignore_empty_systems_on', request('ignore_empty_systems_on', 0)); $settingsService->set('ignore_inactive_systems_on', request('ignore_inactive_systems_on', 0)); - $settingsService->set('number_of_galaxies', request('number_of_galaxies')); + $numberOfGalaxies = max(4, min(9, (int)request('number_of_galaxies', 9))); + $numberOfSystems = max(1, min(499, (int)request('number_of_systems', \OGame\GameConstants\UniverseConstants::MAX_SYSTEM_COUNT))); + $settingsService->set('number_of_galaxies', $numberOfGalaxies); + $settingsService->set('number_of_systems', $numberOfSystems); $settingsService->set('battle_engine', request('battle_engine')); diff --git a/app/Http/Controllers/FleetController.php b/app/Http/Controllers/FleetController.php index 25e9111af..22cc2e7ea 100644 --- a/app/Http/Controllers/FleetController.php +++ b/app/Http/Controllers/FleetController.php @@ -288,8 +288,9 @@ public function dispatchCheckTarget(PlayerService $currentPlayer, PlanetServiceF { $currentPlanet = $currentPlayer->planets->current(); - // Pre-multiply fuel by character class modifier so JS and PHP use the same values. - $fuelMultiplier = $characterClassService->getDeuteriumConsumptionMultiplier($currentPlayer->getUser()); + // Pre-multiply fuel by character class + universe modifiers so JS and PHP use the same values. + $fuelMultiplier = $characterClassService->getDeuteriumConsumptionMultiplier($currentPlayer->getUser()) + * $settingsService->deuteriumConsumption(); // Return ships data for this planet taking into account the current planet's properties and research levels. $shipsData = []; @@ -311,7 +312,7 @@ public function dispatchCheckTarget(PlayerService $currentPlayer, PlanetServiceF $targetType = (int)request()->input('type'); // Validate coordinates against universe bounds - $coordinateError = $this->validateCoordinates($galaxy, $system, $position, $settingsService->numberOfGalaxies()); + $coordinateError = $this->validateCoordinates($galaxy, $system, $position, $settingsService->numberOfGalaxies(), $settingsService->numberOfSystems()); if ($coordinateError !== null) { return response()->json([ 'status' => 'failure', @@ -517,7 +518,7 @@ public function dispatchSendFleet(PlayerService $player, FleetMissionService $fl return $this->validationErrorResponse(__('Fleet speed must be between 10% and 100% in 5% increments.')); } - $coordinateError = $this->validateCoordinates($galaxy, $system, $position, $settingsService->numberOfGalaxies()); + $coordinateError = $this->validateCoordinates($galaxy, $system, $position, $settingsService->numberOfGalaxies(), $settingsService->numberOfSystems()); if ($coordinateError !== null) { return $this->validationErrorResponse($coordinateError); } @@ -676,7 +677,7 @@ public function dispatchSendMiniFleet(PlayerService $player, FleetMissionService } // Validate coordinates against universe bounds - $coordinateError = $this->validateCoordinates($galaxy, $system, $position, $settingsService->numberOfGalaxies()); + $coordinateError = $this->validateCoordinates($galaxy, $system, $position, $settingsService->numberOfGalaxies(), $settingsService->numberOfSystems()); if ($coordinateError !== null) { return response()->json([ 'response' => [ @@ -1069,9 +1070,10 @@ public function joinUnion(PlayerService $player, FleetUnionService $fleetUnionSe */ public function getAvailableUnions(PlayerService $player, FleetUnionService $fleetUnionService): JsonResponse { + $settingsService = resolve(SettingsService::class); $validated = request()->validate([ - 'galaxy' => 'required|integer|min:1|max:9', - 'system' => 'required|integer|min:1|max:499', + 'galaxy' => 'required|integer|min:1|max:' . $settingsService->numberOfGalaxies(), + 'system' => 'required|integer|min:1|max:' . $settingsService->numberOfSystems(), 'position' => 'required|integer|min:1|max:15', 'planet_type' => 'required|integer|in:1,2', ]); @@ -1207,9 +1209,10 @@ private function getUnitsFromRequest(PlanetService $planet): UnitCollection * @param int $system * @param int $position * @param int $maxGalaxies + * @param int $maxSystems * @return string|null Error message if invalid, null if valid */ - private function validateCoordinates(int $galaxy, int $system, int $position, int $maxGalaxies): string|null + private function validateCoordinates(int $galaxy, int $system, int $position, int $maxGalaxies, int $maxSystems): string|null { if ($galaxy < UniverseConstants::MIN_GALAXY || $galaxy > $maxGalaxies) { return __('Invalid galaxy coordinate. Must be between :min and :max.', [ @@ -1218,10 +1221,10 @@ private function validateCoordinates(int $galaxy, int $system, int $position, in ]); } - if ($system < UniverseConstants::MIN_SYSTEM || $system > UniverseConstants::MAX_SYSTEM_COUNT) { + if ($system < UniverseConstants::MIN_SYSTEM || $system > $maxSystems) { return __('Invalid system coordinate. Must be between :min and :max.', [ 'min' => UniverseConstants::MIN_SYSTEM, - 'max' => UniverseConstants::MAX_SYSTEM_COUNT, + 'max' => $maxSystems, ]); } diff --git a/app/Http/Controllers/GalaxyController.php b/app/Http/Controllers/GalaxyController.php index bdefac998..c1dc5d7c6 100644 --- a/app/Http/Controllers/GalaxyController.php +++ b/app/Http/Controllers/GalaxyController.php @@ -69,6 +69,13 @@ public function index(Request $request, PlayerService $player, SettingsService $ $system = (int)$system_qs; } + $maxGalaxies = $settingsService->numberOfGalaxies(); + $maxSystems = $settingsService->numberOfSystems(); + + // Clamp coordinates to configured universe bounds. + $galaxy = max(1, min($maxGalaxies, $galaxy)); + $system = max(1, min($maxSystems, $system)); + return view('ingame.galaxy.index')->with([ 'current_galaxy' => $galaxy, 'current_system' => $system, @@ -77,7 +84,8 @@ public function index(Request $request, PlayerService $player, SettingsService $ 'interplanetary_missiles_count' => $planet->getObjectAmount('interplanetary_missile'), 'used_slots' => 0, 'max_slots' => 1, - 'max_galaxies' => $settingsService->numberOfGalaxies(), + 'max_galaxies' => $maxGalaxies, + 'max_systems' => $maxSystems, 'is_in_vacation_mode' => $player->isInVacationMode(), 'planet_relocation_cost' => (int)$settingsService->get('planet_relocation_cost', 240000), ]); @@ -680,7 +688,7 @@ private function createExpeditionDebrisRow(int $galaxy, int $system, int $positi * @param PhalanxService $phalanxService * @return JsonResponse */ - public function ajax(Request $request, PlayerService $player, PlanetServiceFactory $planetServiceFactory, PhalanxService $phalanxService): JsonResponse + public function ajax(Request $request, PlayerService $player, PlanetServiceFactory $planetServiceFactory, PhalanxService $phalanxService, SettingsService $settingsService): JsonResponse { $this->playerService = $player; $this->planetServiceFactory = $planetServiceFactory; @@ -693,8 +701,10 @@ public function ajax(Request $request, PlayerService $player, PlanetServiceFacto } $planet = $player->planets->current(); - $galaxy = $request->input('galaxy'); - $system = $request->input('system'); + $maxGalaxies = $settingsService->numberOfGalaxies(); + $maxSystems = $settingsService->numberOfSystems(); + $galaxy = max(1, min($maxGalaxies, (int)$request->input('galaxy'))); + $system = max(1, min($maxSystems, (int)$request->input('system'))); $galaxyContent = $this->getGalaxyArray($galaxy, $system, $player, $planetServiceFactory, $phalanxService); $slotsColonized = $this->calculateColonizedSlots($galaxyContent); @@ -921,15 +931,15 @@ public function missileAttackOverlay(Request $request, PlayerService $player, Pl * @param PlanetServiceFactory $planetServiceFactory * @return JsonResponse */ - public function missileAttack(Request $request, PlayerService $player, PlanetServiceFactory $planetServiceFactory): JsonResponse + public function missileAttack(Request $request, PlayerService $player, PlanetServiceFactory $planetServiceFactory, SettingsService $settingsService): JsonResponse { $this->playerService = $player; $this->planetServiceFactory = $planetServiceFactory; // Validate input $validated = $request->validate([ - 'galaxy' => 'required|integer|min:1', - 'system' => 'required|integer|min:1', + 'galaxy' => 'required|integer|min:1|max:' . $settingsService->numberOfGalaxies(), + 'system' => 'required|integer|min:1|max:' . $settingsService->numberOfSystems(), 'position' => 'required|integer|min:1|max:15', 'type' => 'required|integer', 'missile_count' => 'required|integer|min:0', diff --git a/app/Http/Controllers/PhalanxController.php b/app/Http/Controllers/PhalanxController.php index 799d45251..17b506c45 100644 --- a/app/Http/Controllers/PhalanxController.php +++ b/app/Http/Controllers/PhalanxController.php @@ -11,6 +11,7 @@ use OGame\Models\Resources; use OGame\Services\PhalanxService; use OGame\Services\PlayerService; +use OGame\Services\SettingsService; class PhalanxController extends OGameController { @@ -21,15 +22,16 @@ class PhalanxController extends OGameController * @param PlayerService $player * @param PlanetServiceFactory $planetServiceFactory * @param PhalanxService $phalanxService + * @param SettingsService $settingsService * @return JsonResponse * @throws Exception */ - public function scan(Request $request, PlayerService $player, PlanetServiceFactory $planetServiceFactory, PhalanxService $phalanxService): JsonResponse + public function scan(Request $request, PlayerService $player, PlanetServiceFactory $planetServiceFactory, PhalanxService $phalanxService, SettingsService $settingsService): JsonResponse { // Validate request $request->validate([ - 'galaxy' => 'required|integer|min:1', - 'system' => 'required|integer|min:1|max:' . UniverseConstants::MAX_SYSTEM_COUNT, + 'galaxy' => 'required|integer|min:1|max:' . $settingsService->numberOfGalaxies(), + 'system' => 'required|integer|min:1|max:' . $settingsService->numberOfSystems(), 'position' => 'required|integer|min:1|max:' . UniverseConstants::MAX_PLANET_POSITION, ]); diff --git a/app/Http/Controllers/PlanetMoveController.php b/app/Http/Controllers/PlanetMoveController.php index 814cb19da..5e0282c37 100644 --- a/app/Http/Controllers/PlanetMoveController.php +++ b/app/Http/Controllers/PlanetMoveController.php @@ -38,6 +38,17 @@ public function move(Request $request, PlayerService $player, PlanetServiceFacto $system = (int) $request->input('system'); $position = (int) $request->input('position'); + $maxGalaxies = $settingsService->numberOfGalaxies(); + $maxSystems = $settingsService->numberOfSystems(); + if ($galaxy < 1 || $galaxy > $maxGalaxies || $system < 1 || $system > $maxSystems || $position < 1 || $position > 15) { + return response()->json([ + 'error' => __('Invalid coordinates. Galaxy must be 1–:maxGalaxy, system 1–:maxSystem, position 1–15.', [ + 'maxGalaxy' => $maxGalaxies, + 'maxSystem' => $maxSystems, + ]), + ]); + } + $targetCoordinate = new Coordinate($galaxy, $system, $position); // Validate the target position is empty. diff --git a/app/Http/Controllers/ServerSettingsController.php b/app/Http/Controllers/ServerSettingsController.php index 58df6cee6..deb2acb38 100644 --- a/app/Http/Controllers/ServerSettingsController.php +++ b/app/Http/Controllers/ServerSettingsController.php @@ -23,6 +23,8 @@ public function overlay(SettingsService $settingsService): View 'research_speed' => $settingsService->researchSpeed(), 'planet_fields_bonus' => $settingsService->planetFieldsBonus(), 'dark_matter_bonus' => $settingsService->darkMatterBonus(), + 'espionage_probe_capacity_on' => $settingsService->espionageProbeCapacityOn(), + 'deuterium_consumption' => $settingsService->deuteriumConsumption(), 'alliance_combat_system_on' => $settingsService->allianceCombatSystemOn(), 'debris_field_from_ships' => $settingsService->debrisFieldFromShips(), 'debris_field_from_defense' => $settingsService->debrisFieldFromDefense(), @@ -31,6 +33,7 @@ public function overlay(SettingsService $settingsService): View 'ignore_empty_systems_on' => $settingsService->ignoreEmptySystemsOn(), 'ignore_inactive_systems_on' => $settingsService->ignoreInactiveSystemsOn(), 'number_of_galaxies' => $settingsService->numberOfGalaxies(), + 'number_of_systems' => $settingsService->numberOfSystems(), 'dark_matter_regen_enabled' => (bool)$settingsService->get('dark_matter_regen_enabled', 0), 'dark_matter_regen_amount' => (int)$settingsService->get('dark_matter_regen_amount', 150000), 'dark_matter_regen_period' => (int)$settingsService->get('dark_matter_regen_period', 604800), diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index b42279179..ee1d0040e 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -29,8 +29,8 @@ public function __construct( */ public function created(User $user): void { - // Credit initial Dark Matter amount - $initialAmount = (int)$this->settingsService->get('dark_matter_initial', 8000); + // Credit initial Dark Matter amount (shared with admin "Dark Matter bonus" setting) + $initialAmount = $this->settingsService->darkMatterBonus(); if ($initialAmount > 0) { $this->darkMatterService->credit( diff --git a/app/Services/CoordinateDistanceCalculator.php b/app/Services/CoordinateDistanceCalculator.php index 16fcc7797..46cb9a919 100644 --- a/app/Services/CoordinateDistanceCalculator.php +++ b/app/Services/CoordinateDistanceCalculator.php @@ -41,13 +41,14 @@ public function getNumEmptySystems(Coordinate $from, Coordinate $to): int } $diffSystems = abs($from->system - $to->system); + $maxSystems = $this->settingsService->numberOfSystems(); // Check if donut galaxy wrapping provides a shorter path - $altDiff = UniverseConstants::MAX_SYSTEM_COUNT - $diffSystems; + $altDiff = $maxSystems - $diffSystems; if ($altDiff < $diffSystems) { // Path wraps around, split into two segments $split1 = new Coordinate($from->galaxy, UniverseConstants::MIN_SYSTEM, UniverseConstants::MAX_PLANET_POSITION); - $split2 = new Coordinate($to->galaxy, UniverseConstants::MAX_SYSTEM_COUNT, UniverseConstants::MAX_PLANET_POSITION); + $split2 = new Coordinate($to->galaxy, $maxSystems, UniverseConstants::MAX_PLANET_POSITION); return $this->getNumEmptySystemsAux($split1, $to) + $this->getNumEmptySystemsAux($split2, $from); } @@ -100,13 +101,14 @@ public function getNumInactiveSystems(Coordinate $from, Coordinate $to): int } $diffSystems = abs($from->system - $to->system); + $maxSystems = $this->settingsService->numberOfSystems(); // Check if donut galaxy wrapping provides a shorter path - $altDiff = UniverseConstants::MAX_SYSTEM_COUNT - $diffSystems; + $altDiff = $maxSystems - $diffSystems; if ($altDiff < $diffSystems) { // Path wraps around, split into two segments $split1 = new Coordinate($from->galaxy, UniverseConstants::MIN_SYSTEM, UniverseConstants::MAX_PLANET_POSITION); - $split2 = new Coordinate($to->galaxy, UniverseConstants::MAX_SYSTEM_COUNT, UniverseConstants::MAX_PLANET_POSITION); + $split2 = new Coordinate($to->galaxy, $maxSystems, UniverseConstants::MAX_PLANET_POSITION); return $this->getNumInactiveSystemsAux($split1, $to) + $this->getNumInactiveSystemsAux($split2, $from); } diff --git a/app/Services/FleetMissionService.php b/app/Services/FleetMissionService.php index f9682255e..786cb74df 100644 --- a/app/Services/FleetMissionService.php +++ b/app/Services/FleetMissionService.php @@ -8,7 +8,6 @@ use OGame\Enums\FleetSpeedType; use OGame\Factories\GameMissionFactory; use OGame\Factories\PlanetServiceFactory; -use OGame\GameConstants\UniverseConstants; use OGame\GameMessages\AcsDefendArrivalHost; use OGame\GameMessages\AcsDefendArrivalSender; use OGame\GameMissions\Abstracts\GameMission; @@ -131,7 +130,7 @@ public function calculateFleetMissionDistance(PlanetService $fromPlanet, Coordin // If the system are different if ($diffSystem != 0) { - $diff2 = abs($diffSystem - UniverseConstants::MAX_SYSTEM_COUNT); + $diff2 = abs($diffSystem - $this->settingsService->numberOfSystems()); $deltaSystem = 0; if ($diff2 < $diffSystem) { @@ -208,6 +207,12 @@ public function calculateConsumption(PlanetService $fromPlanet, UnitCollection $ $consumptionMultiplier = $characterClassService->getDeuteriumConsumptionMultiplier($fromPlanet->getPlayer()->getUser()); $consumption = (int)($consumption * $consumptionMultiplier); + // Apply universe-wide deuterium consumption multiplier (0.5–1.0) + $universeConsumptionMultiplier = $this->settingsService->deuteriumConsumption(); + if ($universeConsumptionMultiplier !== 1.0) { + $consumption = (int)round($consumption * $universeConsumptionMultiplier); + } + return $consumption; } diff --git a/app/Services/PlanetMoveService.php b/app/Services/PlanetMoveService.php index d536e2e27..db2cc1e60 100644 --- a/app/Services/PlanetMoveService.php +++ b/app/Services/PlanetMoveService.php @@ -361,19 +361,27 @@ public function processDueMoves( /** * Calculate a simple distance between two coordinates for ship transfer duration. + * Uses donut wrapping against configured universe size. */ private function calculateCoordinateDistance(Coordinate $from, Coordinate $to): int { + $settings = resolve(SettingsService::class); + $maxGalaxies = $settings->numberOfGalaxies(); + $maxSystems = $settings->numberOfSystems(); + $diffGalaxy = abs($from->galaxy - $to->galaxy); $diffSystem = abs($from->system - $to->system); $diffPlanet = abs($from->position - $to->position); if ($diffGalaxy != 0) { - return $diffGalaxy * 20000; + $wrappedGalaxy = abs($diffGalaxy - $maxGalaxies); + return min($diffGalaxy, $wrappedGalaxy) * 20000; } if ($diffSystem != 0) { - return $diffSystem * 5 * 19 + 2700; + $wrappedSystem = abs($diffSystem - $maxSystems); + $deltaSystem = max(min($diffSystem, $wrappedSystem), 1); + return $deltaSystem * 5 * 19 + 2700; } if ($diffPlanet != 0) { diff --git a/app/Services/SettingsService.php b/app/Services/SettingsService.php index a9290fc28..b6443369f 100644 --- a/app/Services/SettingsService.php +++ b/app/Services/SettingsService.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Date; use OGame\Factories\GameMissionFactory; +use OGame\GameConstants\UniverseConstants; use OGame\Models\Setting; /** @@ -84,9 +85,12 @@ public function set(string $key, string|int $value): void $this->loadFromDatabase(); } + // Normalize to string so int/float request values compare correctly with DB strings. + $value = (string)$value; + // Check if to be saved value is actually different from current one. $currentValue = $this->get($key, ''); - if (!empty($currentValue) && $currentValue === $value) { + if ($currentValue !== '' && $currentValue === $value) { // To be saved value is same as current value, skip update to prevent unnecessary db call. return; } @@ -219,13 +223,53 @@ public function planetFieldsBonus(): int /** * Returns the amount of dark matter given for a new player. * + * Uses dark_matter_initial (seeded / used by UserObserver). Falls back to + * legacy dark_matter_bonus for older installs that only have that key. + * * @return int */ public function darkMatterBonus(): int { + $initial = $this->get('dark_matter_initial', ''); + if ($initial !== '') { + return (int)$initial; + } + return (int)$this->get('dark_matter_bonus', 8000); } + /** + * Returns whether espionage probes have cargo capacity enabled. + * When enabled, each probe has a cargo capacity of 5. Default is off. + * + * @return bool + */ + public function espionageProbeCapacityOn(): bool + { + return (bool)$this->get('espionage_probe_capacity_on', 0); + } + + /** + * Returns the universe-wide deuterium consumption multiplier for fleets. + * Allowed values typically range from 0.5 to 1.0. Default is 1.0. + * + * @return float + */ + public function deuteriumConsumption(): float + { + return (float)$this->get('deuterium_consumption', '1.0'); + } + + /** + * Returns the number of systems per galaxy. + * + * @return int + */ + public function numberOfSystems(): int + { + return (int)$this->get('number_of_systems', UniverseConstants::MAX_SYSTEM_COUNT); + } + /** * Returns the status of the Alliance Combat System. * diff --git a/resources/views/ingame/admin/activitylogs.blade.php b/resources/views/ingame/admin/activitylogs.blade.php new file mode 100644 index 000000000..fa8feb664 --- /dev/null +++ b/resources/views/ingame/admin/activitylogs.blade.php @@ -0,0 +1,224 @@ +@extends('ingame.layouts.main') + +@section('content') + +
+
+

@lang('Activity logs')

+
+ +
+
+

@lang('Activity logs')

+
+
+
+

+ @lang('Recent constructions, fleet missions, and research activity.') +

+ +

+ @lang('Fleets') + @lang('Buildings') + @lang('Shipyard') + @lang('Research') +

+ + @if ($tab === 'fleets' && $fleets) +
+ + + + + + + + + + + + + + + @forelse ($fleets as $mission) + + + + + + + + + + + @empty + + @endforelse + +
ID@lang('Player')@lang('Mission')@lang('From')@lang('To')@lang('Departure')@lang('Arrival')@lang('Status')
{{ $mission->id }}{{ $users[$mission->user_id] ?? ('#'.$mission->user_id) }}{{ $missionTypeLabels[$mission->mission_type] ?? $mission->mission_type }}{{ $mission->galaxy_from }}:{{ $mission->system_from }}:{{ $mission->position_from }}{{ $mission->galaxy_to }}:{{ $mission->system_to }}:{{ $mission->position_to }}{{ $mission->time_departure ? date('Y-m-d H:i:s', $mission->time_departure) : '-' }}{{ $mission->time_arrival ? date('Y-m-d H:i:s', $mission->time_arrival) : '-' }} + @if ($mission->canceled) + @lang('Canceled') + @elseif ($mission->processed) + @lang('Processed') + @else + @lang('Active') + @endif +
@lang('No fleet missions found.')
+
{{ $fleets->links() }}
+
+ @endif + + @if ($tab === 'buildings' && $buildings) +
+ + + + + + + + + + + + + + @forelse ($buildings as $queue) + @php $planet = $planets[$queue->planet_id] ?? null; @endphp + + + + + + + + + + @empty + + @endforelse + +
ID@lang('Player')@lang('Planet')@lang('Building')@lang('Target level')@lang('End')@lang('Status')
{{ $queue->id }}{{ $planet ? ($users[$planet->user_id] ?? ('#'.$planet->user_id)) : '-' }} + @if ($planet) + {{ $planet->name }} [{{ $planet->galaxy }}:{{ $planet->system }}:{{ $planet->planet }}] + @else + #{{ $queue->planet_id }} + @endif + {{ $objectNames[$queue->object_id] ?? ('#'.$queue->object_id) }}{{ $queue->object_level_target }}{{ $queue->time_end ? date('Y-m-d H:i:s', $queue->time_end) : '-' }} + @if ($queue->canceled) + @lang('Canceled') + @elseif ($queue->processed) + @lang('Processed') + @elseif ($queue->building) + @lang('Building') + @else + @lang('Queued') + @endif +
@lang('No building queues found.')
+
{{ $buildings->links() }}
+
+ @endif + + @if ($tab === 'units' && $units) +
+ + + + + + + + + + + + + + @forelse ($units as $queue) + @php $planet = $planets[$queue->planet_id] ?? null; @endphp + + + + + + + + + + @empty + + @endforelse + +
ID@lang('Player')@lang('Planet')@lang('Unit')@lang('Amount')@lang('End')@lang('Status')
{{ $queue->id }}{{ $planet ? ($users[$planet->user_id] ?? ('#'.$planet->user_id)) : '-' }} + @if ($planet) + {{ $planet->name }} [{{ $planet->galaxy }}:{{ $planet->system }}:{{ $planet->planet }}] + @else + #{{ $queue->planet_id }} + @endif + {{ $objectNames[$queue->object_id] ?? ('#'.$queue->object_id) }}{{ $queue->object_amount }}{{ $queue->time_end ? date('Y-m-d H:i:s', $queue->time_end) : '-' }} + @if ($queue->processed) + @lang('Processed') + @else + @lang('Active') + @endif +
@lang('No shipyard queues found.')
+
{{ $units->links() }}
+
+ @endif + + @if ($tab === 'research' && $research) +
+ + + + + + + + + + + + + + @forelse ($research as $queue) + @php $planet = $planets[$queue->planet_id] ?? null; @endphp + + + + + + + + + + @empty + + @endforelse + +
ID@lang('Player')@lang('Planet')@lang('Research')@lang('Target level')@lang('End')@lang('Status')
{{ $queue->id }}{{ $planet ? ($users[$planet->user_id] ?? ('#'.$planet->user_id)) : '-' }} + @if ($planet) + {{ $planet->name }} [{{ $planet->galaxy }}:{{ $planet->system }}:{{ $planet->planet }}] + @else + #{{ $queue->planet_id }} + @endif + {{ $objectNames[$queue->object_id] ?? ('#'.$queue->object_id) }}{{ $queue->object_level_target }}{{ $queue->time_end ? date('Y-m-d H:i:s', $queue->time_end) : '-' }} + @if ($queue->canceled) + @lang('Canceled') + @elseif ($queue->processed) + @lang('Processed') + @elseif ($queue->building) + @lang('Researching') + @else + @lang('Queued') + @endif +
@lang('No research queues found.')
+
{{ $research->links() }}
+
+ @endif +
+
+
+
+ +@endsection diff --git a/resources/views/ingame/admin/crontasks.blade.php b/resources/views/ingame/admin/crontasks.blade.php new file mode 100644 index 000000000..9920ab230 --- /dev/null +++ b/resources/views/ingame/admin/crontasks.blade.php @@ -0,0 +1,72 @@ +@extends('ingame.layouts.main') + +@section('content') + + @if (session('success')) + + @endif + @if (session('error')) + + @endif + +
+
+

@lang('Cron tasks')

+
+ +
+
+

@lang('Cron tasks')

+
+
+
+

+ @lang('Scheduled server tasks. You can run allowed tasks manually for testing or recovery.') +

+ +
+ + + + + + + + + + + + @forelse ($tasks as $task) + + + + + + + + @empty + + @endforelse + +
@lang('Command')@lang('Schedule')@lang('Next run')@lang('Overlap protection')@lang('Action')
+ {{ $task['description'] }}
+ {{ $task['command'] }} +
{{ $task['expression'] }}{{ $task['next_due'] }}{{ $task['without_overlapping'] ? __('Yes') : __('No') }} + @if ($task['runnable'] && !empty($task['command'])) +
+ {{ csrf_field() }} + + +
+ @else + - + @endif +
@lang('No scheduled tasks found.')
+
+
+
+
+
+ +@endsection diff --git a/resources/views/ingame/admin/developershortcuts.blade.php b/resources/views/ingame/admin/developershortcuts.blade.php index 31998c9bf..052243809 100644 --- a/resources/views/ingame/admin/developershortcuts.blade.php +++ b/resources/views/ingame/admin/developershortcuts.blade.php @@ -124,7 +124,7 @@
+ value="{{ $currentPlanet->getPlanetCoordinates()->system }}" min="1" max="{{ $settings->numberOfSystems() }}" name="system">
@@ -167,7 +167,7 @@ class="textInput w100 textCenter textBeefy"
+ value="{{ $currentPlanet->getPlanetCoordinates()->system }}" min="1" max="{{ $settings->numberOfSystems() }}" name="system">
@@ -218,7 +218,7 @@ class="textInput w100 textCenter textBeefy"
+ value="{{ $currentPlanet->getPlanetCoordinates()->system }}" min="1" max="{{ $settings->numberOfSystems() }}" name="system">
@@ -269,7 +269,7 @@ class="textInput w100 textCenter textBeefy"
+ value="{{ $currentPlanet->getPlanetCoordinates()->system }}" min="1" max="{{ $settings->numberOfSystems() }}" name="system">
diff --git a/resources/views/ingame/admin/serversettings.blade.php b/resources/views/ingame/admin/serversettings.blade.php index ffa9a809d..2c6ea58ff 100644 --- a/resources/views/ingame/admin/serversettings.blade.php +++ b/resources/views/ingame/admin/serversettings.blade.php @@ -76,6 +76,30 @@
+
+ +
+ +
+
@lang('Universe-wide fleet deuterium consumption multiplier. Default is 1.')
+
+
+ +
+ + + + +
+
@lang('When enabled, each espionage probe has a cargo capacity of 5. Off by default.')
+

@lang('Note: basic income values below are multiplied by economy speed.')

@@ -118,10 +142,11 @@
- +
+
@lang('Dark Matter given to new players on registration. Typical range: 8000–25000.')
@@ -468,10 +493,11 @@ -
+
+
+ +
+ +
+
@lang('Default is 499. Changing this affects fleet distance calculations and galaxy navigation.')
+
diff --git a/resources/views/ingame/fleet/index.blade.php b/resources/views/ingame/fleet/index.blade.php index 7fbf756ba..0664cc89e 100644 --- a/resources/views/ingame/fleet/index.blade.php +++ b/resources/views/ingame/fleet/index.blade.php @@ -146,7 +146,7 @@ "id": 210, "name": "Espionage Probe", "baseFuelCapacity": 5, - "baseCargoCapacity": 0, + "baseCargoCapacity": {{ $settings->espionageProbeCapacityOn() ? 5 : 0 }}, "fuelConsumption": 1, "speed": 150000000 }, @@ -160,6 +160,13 @@ } }; + // Apply universe deuterium consumption to static fallback ship data + // (checkTarget response already includes this multiplier). + var UNIVERSE_DEUTERIUM_CONSUMPTION = {{ $settings->deuteriumConsumption() }}; + Object.keys(shipsData).forEach(function (id) { + shipsData[id].fuelConsumption = shipsData[id].fuelConsumption * UNIVERSE_DEUTERIUM_CONSUMPTION; + }); + var speed = 100 var PLAYER_ID_SPACE = 99999; @@ -167,7 +174,7 @@ var DONUT_GALAXY = 1; var DONUT_SYSTEM = 1; var MAX_GALAXY = {{ $settings->numberOfGalaxies() }}; - var MAX_SYSTEM = {{ \OGame\GameConstants\UniverseConstants::MAX_SYSTEM_COUNT }}; + var MAX_SYSTEM = {{ $settings->numberOfSystems() }}; var MAX_POSITION = {{ \OGame\GameConstants\UniverseConstants::EXPEDITION_POSITION }}; var SPEEDFAKTOR_FLEET_PEACEFUL = {{ $settings->fleetSpeedPeaceful() }}; var SPEEDFAKTOR_FLEET_WAR = {{ $settings->fleetSpeedWar() }}; diff --git a/resources/views/ingame/galaxy/index.blade.php b/resources/views/ingame/galaxy/index.blade.php index a42d4879a..75bffee15 100644 --- a/resources/views/ingame/galaxy/index.blade.php +++ b/resources/views/ingame/galaxy/index.blade.php @@ -33,7 +33,7 @@ var galaxy = {{ $current_galaxy }}; var system = {{ $current_system }}; var maxGalaxies = {{ $max_galaxies }}; - var maxSystems = 499; + var maxSystems = {{ $max_systems }}; var spionageAmount = 3; var contentLink = "{{ route('galaxy.ajax') }}"; var galaxyContentLink = "{{ route('galaxy.ajax') }}"; diff --git a/resources/views/ingame/layouts/admin-menu.blade.php b/resources/views/ingame/layouts/admin-menu.blade.php index fdaef6717..2bd83b592 100644 --- a/resources/views/ingame/layouts/admin-menu.blade.php +++ b/resources/views/ingame/layouts/admin-menu.blade.php @@ -83,6 +83,8 @@
  • Developer shortcuts
  • Server settings
  • Fleet Timing
  • +
  • Activity logs
  • +
  • Cron tasks
  • Rules & Legal
  • Server Administration
  • diff --git a/resources/views/ingame/layouts/main.blade.php b/resources/views/ingame/layouts/main.blade.php index 60576fec9..a6a416267 100644 --- a/resources/views/ingame/layouts/main.blade.php +++ b/resources/views/ingame/layouts/main.blade.php @@ -28,7 +28,7 @@ - + @@ -45,7 +45,7 @@ - {{ config('app.name', 'Laravel') }} + {{ $settings->universeName() }} @vite(['resources/css/ingame.css', 'resources/js/ingame.js']) diff --git a/resources/views/ingame/serversettings/overlay.blade.php b/resources/views/ingame/serversettings/overlay.blade.php index ef82fa088..61cca03d1 100644 --- a/resources/views/ingame/serversettings/overlay.blade.php +++ b/resources/views/ingame/serversettings/overlay.blade.php @@ -28,7 +28,12 @@
    - @lang('Fleet Deuterium consumption reduction:') 30% + @lang('Fleet Deuterium consumption:') x{{ $deuterium_consumption }} +
    +
    + + @lang('Espionage probe capacity') +
    @@ -56,6 +61,10 @@ @lang('Number of galaxies:') {{ $number_of_galaxies }}
    +
    + + @lang('Number of systems:') {{ $number_of_systems }} +
    @lang('Planet field bonus:') {{ $planet_fields_bonus }} diff --git a/routes/web.php b/routes/web.php index 65dc6074b..41bff3a28 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,8 @@ name('admin.fleettiming.fast-forward-all'); Route::post('/admin/fleet-timing/reduce', [FleetTimingController::class, 'reduceTime'])->name('admin.fleettiming.reduce'); + // Activity logs (constructions, fleets, research) + Route::get('/admin/activity-logs', [ActivityLogsController::class, 'index'])->name('admin.activitylogs.index'); + + // Cron / scheduled task management + Route::get('/admin/cron-tasks', [CronTasksController::class, 'index'])->name('admin.crontasks.index'); + Route::post('/admin/cron-tasks/run', [CronTasksController::class, 'run'])->name('admin.crontasks.run'); + // Server administration (multi-account detection, bans) Route::get('/admin/server-administration', [ServerAdministrationController::class, 'index'])->name('admin.server-administration.index'); Route::post('/admin/server-administration/ban', [ServerAdministrationController::class, 'ban'])->name('admin.server-administration.ban'); diff --git a/tests/Feature/AdminServerSettingsOptionsTest.php b/tests/Feature/AdminServerSettingsOptionsTest.php new file mode 100644 index 000000000..c4d5edd09 --- /dev/null +++ b/tests/Feature/AdminServerSettingsOptionsTest.php @@ -0,0 +1,133 @@ +artisan('ogamex:admin:assign-role', ['username' => auth()->user()->username]); + + $response = $this->post('/admin/server-settings', [ + '_token' => csrf_token(), + 'universe_name' => 'TestUniverse', + 'fleet_speed_war' => 1, + 'fleet_speed_holding' => 1, + 'fleet_speed_peaceful' => 1, + 'economy_speed' => 1, + 'research_speed' => 1, + 'basic_income_metal' => 30, + 'basic_income_crystal' => 15, + 'basic_income_deuterium' => 0, + 'basic_income_energy' => 0, + 'registration_planet_amount' => 1, + 'planet_fields_bonus' => 0, + 'dark_matter_bonus' => 12000, + 'espionage_probe_capacity_on' => 1, + 'deuterium_consumption' => '0.7', + 'alliance_combat_system_on' => 1, + 'alliance_cooldown_days' => 3, + 'debris_field_from_ships' => 30, + 'debris_field_from_defense' => 0, + 'maximum_moon_chance' => 20, + 'number_of_galaxies' => 4, + 'number_of_systems' => 100, + 'battle_engine' => 'php', + 'hamill_probability' => 1000, + ]); + + $response->assertRedirect(route('admin.serversettings.index')); + + /** @var SettingsService $settings */ + $settings = app(SettingsService::class); + + $this->assertSame('TestUniverse', $settings->universeName()); + $this->assertSame(12000, $settings->darkMatterBonus()); + $this->assertSame('12000', $settings->get('dark_matter_initial')); + $this->assertTrue($settings->espionageProbeCapacityOn()); + $this->assertSame(0.7, $settings->deuteriumConsumption()); + $this->assertSame(4, $settings->numberOfGalaxies()); + $this->assertSame(100, $settings->numberOfSystems()); + } + + /** + * Espionage probes gain cargo capacity of 5 when the setting is enabled. + */ + public function testEspionageProbeCapacitySettingAffectsCargo(): void + { + /** @var SettingsService $settings */ + $settings = app(SettingsService::class); + $settings->set('espionage_probe_capacity_on', 0); + + $probe = ObjectService::getShipObjectByMachineName('espionage_probe'); + $this->assertSame(0, $probe->properties->capacity->calculate($this->planetService->getPlayer())->totalValue); + + $settings->set('espionage_probe_capacity_on', 1); + $this->assertSame(5, $probe->properties->capacity->calculate($this->planetService->getPlayer())->totalValue); + } + + /** + * Universe deuterium consumption multiplier reduces fleet fuel usage. + */ + public function testDeuteriumConsumptionMultiplierAffectsFleetFuel(): void + { + $this->planetAddUnit('small_cargo', 10); + $this->planetAddResources(new \OGame\Models\Resources(0, 0, 100000, 0)); + + /** @var SettingsService $settings */ + $settings = app(SettingsService::class); + $settings->set('deuterium_consumption', '1.0'); + + /** @var FleetMissionService $fleetMissionService */ + $fleetMissionService = app(FleetMissionService::class); + + $units = new UnitCollection(); + $units->addUnit(ObjectService::getShipObjectByMachineName('small_cargo'), 10); + $target = new Coordinate(1, 2, 3); + + $fullConsumption = $fleetMissionService->calculateConsumption( + $this->planetService, + $units, + $target, + 0, + 10 + ); + + $settings->set('deuterium_consumption', '0.5'); + $halfConsumption = $fleetMissionService->calculateConsumption( + $this->planetService, + $units, + $target, + 0, + 10 + ); + + $this->assertGreaterThan(0, $fullConsumption); + $this->assertSame((int)round($fullConsumption * 0.5), $halfConsumption); + } + + /** + * Activity logs and cron tasks admin pages are accessible to admins. + */ + public function testAdminActivityLogsAndCronPagesAccessible(): void + { + $this->artisan('ogamex:admin:assign-role', ['username' => auth()->user()->username]); + + $this->get('/admin/activity-logs')->assertStatus(200)->assertSee('Activity logs'); + $this->get('/admin/activity-logs?tab=buildings')->assertStatus(200); + $this->get('/admin/cron-tasks')->assertStatus(200)->assertSee('Cron tasks'); + } +} diff --git a/tests/Feature/AdminTest.php b/tests/Feature/AdminTest.php index 965b144f4..efd87f336 100644 --- a/tests/Feature/AdminTest.php +++ b/tests/Feature/AdminTest.php @@ -16,6 +16,8 @@ class AdminTest extends AccountTestCase '/admin/server-settings', '/admin/developer-shortcuts', '/admin/server-administration', + '/admin/activity-logs', + '/admin/cron-tasks', ]; /** diff --git a/tests/Unit/UnitCollectionTest.php b/tests/Unit/UnitCollectionTest.php index 12db89b1a..65873e66e 100644 --- a/tests/Unit/UnitCollectionTest.php +++ b/tests/Unit/UnitCollectionTest.php @@ -5,6 +5,7 @@ use Exception; use OGame\GameObjects\Models\Units\UnitCollection; use OGame\Services\ObjectService; +use OGame\Services\SettingsService; use Tests\UnitTestCase; /** @@ -22,6 +23,9 @@ protected function setUp(): void { parent::setUp(); $this->setUpPlanetService(); + + // Default universe setting: probes have no cargo capacity. + app(SettingsService::class)->set('espionage_probe_capacity_on', 0); } /** @@ -219,11 +223,15 @@ public function testMixedFleetWithEspionageProbesCargoCapacity(): void $unitCollection->addUnit(ObjectService::getShipObjectByMachineName('small_cargo'), 10); $unitCollection->addUnit(ObjectService::getShipObjectByMachineName('espionage_probe'), 5); - // Cargo: 10*5000 + 5*0 = 50,000 + // Cargo: 10*5000 + 5*0 = 50,000 (probe capacity off) $this->assertEquals(50000, $unitCollection->getTotalCargoCapacity($this->playerService)); // Fuel: 10*5000 + 5*5 = 50,025 $this->assertEquals(50025, $unitCollection->getTotalFuelCapacity($this->playerService)); + + // When probe capacity is enabled, each probe adds 5 cargo. + app(SettingsService::class)->set('espionage_probe_capacity_on', 1); + $this->assertEquals(50025, $unitCollection->getTotalCargoCapacity($this->playerService)); } /** From 6d40d9377a595c0aae41aa9b2d7f375a954e1047 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 20 Jul 2026 01:03:40 +0000 Subject: [PATCH 2/6] fix(admin): load schedule definitions on cron tasks page Schedule events from routes/console.php are only registered during Artisan bootstrap, so the web admin UI saw an empty list. Load them explicitly when serving the cron tasks page. Co-authored-by: Cursor --- .../Controllers/Admin/CronTasksController.php | 26 ++++++++++++++++--- .../AdminServerSettingsOptionsTest.php | 6 ++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Admin/CronTasksController.php b/app/Http/Controllers/Admin/CronTasksController.php index df90957e5..2bec637f6 100644 --- a/app/Http/Controllers/Admin/CronTasksController.php +++ b/app/Http/Controllers/Admin/CronTasksController.php @@ -31,11 +31,11 @@ class CronTasksController extends OGameController /** * Shows scheduled cron tasks and allows manual runs. */ - public function index(Schedule $schedule): View + public function index(): View { $tasks = []; - foreach ($schedule->events() as $event) { + foreach ($this->resolveSchedule()->events() as $event) { $command = $this->extractCommandName($event->command ?? ''); $tasks[] = [ 'expression' => $event->expression, @@ -83,13 +83,31 @@ public function run(Request $request): RedirectResponse } } + /** + * Resolve the application schedule, loading console schedule definitions + * when running in an HTTP context (where routes/console.php is not auto-loaded). + */ + private function resolveSchedule(): Schedule + { + $schedule = app(Schedule::class); + + if (count($schedule->events()) === 0) { + // Schedule:: definitions live in routes/console.php and are only + // registered automatically during Artisan bootstrap. + require base_path('routes/console.php'); + } + + return $schedule; + } + /** * Extract a readable command identifier from a scheduled event command string. */ private function extractCommandName(string $command): string { - // Typical format: '/usr/bin/php artisan ogamex:scheduler:generate-highscores' - if (preg_match("/artisan\s+([^\s']+)/", $command, $matches)) { + // Typical format: '/usr/bin/php' 'artisan' ogamex:scheduler:generate-highscores + // or: php artisan ogamex:scheduler:generate-highscores + if (preg_match("/artisan['\"]?\s+['\"]?([^\s'\"]+)/", $command, $matches)) { return $matches[1]; } diff --git a/tests/Feature/AdminServerSettingsOptionsTest.php b/tests/Feature/AdminServerSettingsOptionsTest.php index c4d5edd09..ed0b9bb6e 100644 --- a/tests/Feature/AdminServerSettingsOptionsTest.php +++ b/tests/Feature/AdminServerSettingsOptionsTest.php @@ -128,6 +128,10 @@ public function testAdminActivityLogsAndCronPagesAccessible(): void $this->get('/admin/activity-logs')->assertStatus(200)->assertSee('Activity logs'); $this->get('/admin/activity-logs?tab=buildings')->assertStatus(200); - $this->get('/admin/cron-tasks')->assertStatus(200)->assertSee('Cron tasks'); + $this->get('/admin/cron-tasks') + ->assertStatus(200) + ->assertSee('Cron tasks') + ->assertSee('ogamex:scheduler:generate-highscores') + ->assertDontSee('No scheduled tasks found'); } } From a5e6d6112956ae2c878b47ba94e41e905cdf5895 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 20 Jul 2026 01:05:51 +0000 Subject: [PATCH 3/6] style(admin): declutter cron tasks page layout Replace the dense table with spaced task rows and friendlier labels for schedule cadence and command names. Co-authored-by: Cursor --- .../Controllers/Admin/CronTasksController.php | 30 ++++- .../views/ingame/admin/crontasks.blade.php | 105 ++++++++++++------ 2 files changed, 97 insertions(+), 38 deletions(-) diff --git a/app/Http/Controllers/Admin/CronTasksController.php b/app/Http/Controllers/Admin/CronTasksController.php index 2bec637f6..fe6daef42 100644 --- a/app/Http/Controllers/Admin/CronTasksController.php +++ b/app/Http/Controllers/Admin/CronTasksController.php @@ -28,6 +28,32 @@ class CronTasksController extends OGameController 'ogamex:scheduler:darkmatter-regenerate', ]; + /** + * Friendly labels for known scheduler commands. + * + * @var array + */ + private const COMMAND_LABELS = [ + 'ogamex:scheduler:generate-highscores' => 'Generate player highscores', + 'ogamex:scheduler:generate-alliance-highscores' => 'Generate alliance highscores', + 'ogamex:scheduler:generate-highscore-ranks' => 'Generate highscore ranks', + 'ogamex:scheduler:reset-debris-fields' => 'Reset empty debris fields', + 'ogamex:scheduler:cleanup-wreckfields' => 'Clean up wreck fields', + 'ogamex:scheduler:delete-old-messages' => 'Delete old messages', + 'ogamex:scheduler:darkmatter-regenerate' => 'Dark Matter regeneration', + ]; + + /** + * Human-readable schedule summaries for common cron expressions. + * + * @var array + */ + private const SCHEDULE_LABELS = [ + '*/5 * * * *' => 'Every 5 minutes', + '0 * * * *' => 'Hourly', + '0 1 * * 1' => 'Weekly (Monday 01:00)', + ]; + /** * Shows scheduled cron tasks and allows manual runs. */ @@ -38,8 +64,8 @@ public function index(): View foreach ($this->resolveSchedule()->events() as $event) { $command = $this->extractCommandName($event->command ?? ''); $tasks[] = [ - 'expression' => $event->expression, - 'description' => $event->description ?: $command, + 'expression' => self::SCHEDULE_LABELS[$event->expression] ?? $event->expression, + 'description' => self::COMMAND_LABELS[$command] ?? ($event->description ?: $command), 'command' => $command, 'next_due' => $event->nextRunDate()->format('Y-m-d H:i:s'), 'without_overlapping' => (bool)$event->withoutOverlapping, diff --git a/resources/views/ingame/admin/crontasks.blade.php b/resources/views/ingame/admin/crontasks.blade.php index 9920ab230..901a5a13c 100644 --- a/resources/views/ingame/admin/crontasks.blade.php +++ b/resources/views/ingame/admin/crontasks.blade.php @@ -3,12 +3,56 @@ @section('content') @if (session('success')) - + @endif @if (session('error')) - + @endif + +

    @lang('Cron tasks')

    @@ -21,49 +65,38 @@

    - @lang('Scheduled server tasks. You can run allowed tasks manually for testing or recovery.') + @lang('Scheduled server tasks. Run a task manually for testing or recovery.')

    -
    - - - - - - - - - - - - @forelse ($tasks as $task) - - - - - - - - @empty - - @endforelse - -
    @lang('Command')@lang('Schedule')@lang('Next run')@lang('Overlap protection')@lang('Action')
    - {{ $task['description'] }}
    - {{ $task['command'] }} -
    {{ $task['expression'] }}{{ $task['next_due'] }}{{ $task['without_overlapping'] ? __('Yes') : __('No') }} + @if (empty($tasks)) +

    @lang('No scheduled tasks found.')

    + @else +
    + @foreach ($tasks as $task) +
    +

    {{ $task['description'] }}

    +

    {{ $task['command'] }}

    +
    + @lang('Schedule'): {{ $task['expression'] }} + @lang('Next run'): {{ $task['next_due'] }} + @if ($task['without_overlapping']) + @lang('Overlap protection'): @lang('On') + @endif +
    +
    @if ($task['runnable'] && !empty($task['command'])) -
    + {{ csrf_field() }}
    - @else - - @endif -
    @lang('No scheduled tasks found.')
    -
    +
    +
    + @endforeach +
    + @endif
    From 97934500aecdfaa52b4870c79258a55b9ad3f8d6 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 20 Jul 2026 01:16:54 +0000 Subject: [PATCH 4/6] fix(ui): show configured universe name on login page Replace the hardcoded translated "1. Universe" option and page title with SettingsService::universeName(). Co-authored-by: Cursor --- resources/views/outgame/layouts/main.blade.php | 4 ++-- tests/Feature/AdminServerSettingsOptionsTest.php | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/resources/views/outgame/layouts/main.blade.php b/resources/views/outgame/layouts/main.blade.php index 876b7506b..26072a7c3 100644 --- a/resources/views/outgame/layouts/main.blade.php +++ b/resources/views/outgame/layouts/main.blade.php @@ -32,7 +32,7 @@ content="OGameX - The legendary game in the space! Discover the universe together with thousands of players."/> - {{ config('app.name', 'Laravel') }} + {{ app(\OGame\Services\SettingsService::class)->universeName() }} @vite(['resources/css/outgame.css', 'resources/js/outgame.js']) @@ -200,7 +200,7 @@ class="browserimg ie">IE 8+
    diff --git a/tests/Feature/AdminServerSettingsOptionsTest.php b/tests/Feature/AdminServerSettingsOptionsTest.php index ed0b9bb6e..b5d93f461 100644 --- a/tests/Feature/AdminServerSettingsOptionsTest.php +++ b/tests/Feature/AdminServerSettingsOptionsTest.php @@ -119,6 +119,22 @@ public function testDeuteriumConsumptionMultiplierAffectsFleetFuel(): void $this->assertSame((int)round($fullConsumption * 0.5), $halfConsumption); } + /** + * Login page shows the configured universe name. + */ + public function testLoginPageShowsConfiguredUniverseName(): void + { + /** @var SettingsService $settings */ + $settings = app(SettingsService::class); + $settings->set('universe_name', 'Alpha Centauri'); + + $this->post('/logout'); + + $this->get('/login') + ->assertStatus(200) + ->assertSee('Alpha Centauri'); + } + /** * Activity logs and cron tasks admin pages are accessible to admins. */ From e69276d15298b960f798ab1793e1473d7a1c23d0 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 24 Jul 2026 22:11:08 +0000 Subject: [PATCH 5/6] fix: import UniverseConstants to satisfy Rector Co-authored-by: Cursor --- app/Http/Controllers/Admin/ServerSettingsController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/ServerSettingsController.php b/app/Http/Controllers/Admin/ServerSettingsController.php index cd452641d..4e5731207 100644 --- a/app/Http/Controllers/Admin/ServerSettingsController.php +++ b/app/Http/Controllers/Admin/ServerSettingsController.php @@ -6,6 +6,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\View\View; use OGame\Enums\HighscoreTypeEnum; +use OGame\GameConstants\UniverseConstants; use OGame\Http\Controllers\OGameController; use OGame\Services\PlayerService; use OGame\Services\SettingsService; @@ -121,7 +122,7 @@ public function update(SettingsService $settingsService): RedirectResponse $settingsService->set('ignore_empty_systems_on', request('ignore_empty_systems_on', 0)); $settingsService->set('ignore_inactive_systems_on', request('ignore_inactive_systems_on', 0)); $numberOfGalaxies = max(4, min(9, (int)request('number_of_galaxies', 9))); - $numberOfSystems = max(1, min(499, (int)request('number_of_systems', \OGame\GameConstants\UniverseConstants::MAX_SYSTEM_COUNT))); + $numberOfSystems = max(1, min(499, (int)request('number_of_systems', UniverseConstants::MAX_SYSTEM_COUNT))); $settingsService->set('number_of_galaxies', $numberOfGalaxies); $settingsService->set('number_of_systems', $numberOfSystems); From 64d97a04ecc688de5f4f623c9d08b5ff60e32462 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 24 Jul 2026 22:12:58 +0000 Subject: [PATCH 6/6] fix: satisfy PHPStan null checks in admin settings tests Co-authored-by: Cursor --- .../AdminServerSettingsOptionsTest.php | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/Feature/AdminServerSettingsOptionsTest.php b/tests/Feature/AdminServerSettingsOptionsTest.php index b5d93f461..5998320dd 100644 --- a/tests/Feature/AdminServerSettingsOptionsTest.php +++ b/tests/Feature/AdminServerSettingsOptionsTest.php @@ -19,7 +19,11 @@ class AdminServerSettingsOptionsTest extends AccountTestCase */ public function testAdminCanSaveNewServerSettings(): void { - $this->artisan('ogamex:admin:assign-role', ['username' => auth()->user()->username]); + $authUser = auth()->user(); + if ($authUser === null) { + $this->fail('Not authenticated.'); + } + $this->artisan('ogamex:admin:assign-role', ['username' => $authUser->username]); $response = $this->post('/admin/server-settings', [ '_token' => csrf_token(), @@ -72,11 +76,16 @@ public function testEspionageProbeCapacitySettingAffectsCargo(): void $settings = app(SettingsService::class); $settings->set('espionage_probe_capacity_on', 0); + $player = $this->planetService->getPlayer(); + if ($player === null) { + $this->fail('Player not found.'); + } + $probe = ObjectService::getShipObjectByMachineName('espionage_probe'); - $this->assertSame(0, $probe->properties->capacity->calculate($this->planetService->getPlayer())->totalValue); + $this->assertSame(0, $probe->properties->capacity->calculate($player)->totalValue); $settings->set('espionage_probe_capacity_on', 1); - $this->assertSame(5, $probe->properties->capacity->calculate($this->planetService->getPlayer())->totalValue); + $this->assertSame(5, $probe->properties->capacity->calculate($player)->totalValue); } /** @@ -140,7 +149,11 @@ public function testLoginPageShowsConfiguredUniverseName(): void */ public function testAdminActivityLogsAndCronPagesAccessible(): void { - $this->artisan('ogamex:admin:assign-role', ['username' => auth()->user()->username]); + $authUser = auth()->user(); + if ($authUser === null) { + $this->fail('Not authenticated.'); + } + $this->artisan('ogamex:admin:assign-role', ['username' => $authUser->username]); $this->get('/admin/activity-logs')->assertStatus(200)->assertSee('Activity logs'); $this->get('/admin/activity-logs?tab=buildings')->assertStatus(200);