Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Factories/PlanetServiceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down
30 changes: 25 additions & 5 deletions app/GameObjects/Services/Properties/CapacityPropertyService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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',
Expand All @@ -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'][] = [
Expand All @@ -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;
}

/**
Expand Down
105 changes: 105 additions & 0 deletions app/Http/Controllers/Admin/ActivityLogsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

namespace OGame\Http\Controllers\Admin;

use Illuminate\Http\Request;
use Illuminate\View\View;
use OGame\Factories\GameMissionFactory;
use OGame\Http\Controllers\OGameController;
use OGame\Models\BuildingQueue;
use OGame\Models\FleetMission;
use OGame\Models\Planet;
use OGame\Models\ResearchQueue;
use OGame\Models\UnitQueue;
use OGame\Models\User;
use OGame\Services\ObjectService;

class ActivityLogsController extends OGameController
{
private const PER_PAGE = 50;

/**
* Shows recent game activity for admin monitoring.
*/
public function index(Request $request): View
{
$tab = $request->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()]),
]);
}
}
147 changes: 147 additions & 0 deletions app/Http/Controllers/Admin/CronTasksController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php

namespace OGame\Http\Controllers\Admin;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Str;
use Illuminate\View\View;
use OGame\Http\Controllers\OGameController;
use Throwable;

class CronTasksController extends OGameController
{
/**
* Allowed artisan command signatures that can be run from the admin UI.
*
* @var list<string>
*/
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',
];

/**
* Friendly labels for known scheduler commands.
*
* @var array<string, string>
*/
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<string, string>
*/
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.
*/
public function index(): View
{
$tasks = [];

foreach ($this->resolveSchedule()->events() as $event) {
$command = $this->extractCommandName($event->command ?? '');
$tasks[] = [
'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,
'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()]));
}
}

/**
* 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
// or: 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;
}
}
6 changes: 3 additions & 3 deletions app/Http/Controllers/Admin/DeveloperShortcutsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]);

Expand Down Expand Up @@ -248,7 +248,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,
]);

Expand Down Expand Up @@ -381,7 +381,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',
]);
Expand Down
Loading
Loading