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
47 changes: 47 additions & 0 deletions app/Http/Controllers/FacilitiesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,53 @@ public function halveBuilding(Request $request, PlayerService $player, HalvingSe
}
}

/**
* Complete a building queue item instantly using Dark Matter.
*
* @param Request $request
* @param PlayerService $player
* @param HalvingService $halvingService
* @return JsonResponse
*/
public function completeBuilding(Request $request, PlayerService $player, HalvingService $halvingService): JsonResponse
{
try {
$queueItemId = (int)$request->input('queue_item_id');

if ($queueItemId <= 0) {
return response()->json([
'success' => false,
'error' => true,
'message' => 'Invalid queue item ID',
'newAjaxToken' => csrf_token(),
]);
}

$result = $halvingService->completeBuilding(
$player->getUser(),
$queueItemId,
$player->planets->current()
);

session()->flash('success', __('You have successfully accelerated the order.'));

return response()->json([
'success' => true,
'error' => false,
'newAjaxToken' => csrf_token(),
'cost' => $result['cost'],
'new_balance' => $result['new_balance'],
]);
} catch (Exception $e) {
return response()->json([
'success' => false,
'error' => true,
'message' => $e->getMessage(),
'newAjaxToken' => csrf_token(),
]);
}
}

/**
* Start repairs for the wreck field.
*
Expand Down
2 changes: 2 additions & 0 deletions app/Models/BuildingQueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
* @property float $deuterium
* @property int $building
* @property int $processed
* @property int $dm_halved
* @property int $dm_completed
* @property int $canceled
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
Expand Down
1 change: 1 addition & 0 deletions app/Services/BuildingQueueService.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ public function retrieveQueue(PlanetService $planet): BuildingQueueListViewModel
$item['building'],
$item['object_level_target'],
$is_downgrade,
(bool)($item['dm_halved'] ?? false),
);

$list[] = $viewModel;
Expand Down
119 changes: 119 additions & 0 deletions app/Services/HalvingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ public function halveBuilding(User $user, int $queueItemId, PlanetService $plane
throw new Exception('Queue item not found or already completed');
}

if ($queueItem->dm_halved) {
throw new Exception('Queue item has already been halved. Use Complete to finish instantly.');
}

// Calculate new time values
// Cost is based on remaining time, reduction is 50% of original time
$timeValues = $this->calculateNewTimeValues(
Expand Down Expand Up @@ -201,6 +205,7 @@ public function halveBuilding(User $user, int $queueItemId, PlanetService $plane

// Update queue item time_end
$queueItem->time_end = $timeValues['new_time_end'];
$queueItem->dm_halved = 1;
$queueItem->save();

return [
Expand All @@ -215,6 +220,93 @@ public function halveBuilding(User $user, int $queueItemId, PlanetService $plane
return $result;
}

/**
* Complete a building queue item instantly using Dark Matter.
*
* This is used when the building has already been halved once. Instead of halving
* again, the queue is completed immediately.
*
* @param User $user The user performing the completion
* @param int $queueItemId The building queue item ID
* @param PlanetService $planet The planet service
* @return array{success: bool, cost: int, new_balance: int}
* @throws Exception If insufficient Dark Matter or invalid queue item
*/
public function completeBuilding(User $user, int $queueItemId, PlanetService $planet): array
{
/** @var array{success: bool, cost: int, new_balance: int} $result */
$result = DB::transaction(function () use ($user, $queueItemId, $planet) {
// Lock user row for Dark Matter balance
$lockedUser = User::where('id', $user->id)->lockForUpdate()->first();
if (!$lockedUser) {
throw new Exception('User not found');
}

// Lock and retrieve queue item
$queueItem = BuildingQueue::where('id', $queueItemId)
->where('planet_id', $planet->getPlanetId())
->where('processed', 0)
->where('canceled', 0)
->where('building', 1)
->lockForUpdate()
->first();

if (!$queueItem) {
throw new Exception('Queue item not found or already completed');
}

// Calculate cost based on remaining time
$currentTime = $this->getCurrentTimestamp();
$remainingTime = (int)$queueItem->time_end - $currentTime;

if ($remainingTime <= 0) {
throw new Exception('Queue item already completed');
}

$cost = $this->calculateHalvingCost($remainingTime, 'building');

// Check balance
if ($lockedUser->dark_matter < $cost) {
throw new Exception("Insufficient Dark Matter. Required: {$cost}, Available: {$lockedUser->dark_matter}");
}

// Debit Dark Matter
$lockedUser->dark_matter -= $cost;
$lockedUser->save();

// Record transaction
$object = ObjectService::getObjectById($queueItem->object_id);
$description = "Completing building: {$object->title} on planet {$planet->getPlanetName()} (ID: {$planet->getPlanetId()})";
$this->transactionService->recordTransaction(
$lockedUser,
-$cost,
DarkMatterTransactionType::HALVING->value,
$description,
$lockedUser->dark_matter
);

$originalTimeEnd = (int)$queueItem->time_end;

// Set time_end to now so the building queue processor grants the level immediately.
$queueItem->time_end = $currentTime;
if ((int)$queueItem->time_start > $currentTime) {
$queueItem->time_start = $currentTime;
}
$queueItem->dm_completed = 1;
$queueItem->save();

$this->updateFutureBuildingQueueItems($planet->getPlanetId(), $originalTimeEnd, $currentTime);

return [
'success' => true,
'cost' => $cost,
'new_balance' => $lockedUser->dark_matter,
];
});

return $result;
}

/**
* Halve a research queue item.
*
Expand Down Expand Up @@ -627,4 +719,31 @@ private function updateFutureQueueItems(int $planetId, int $oldTimeEnd, int $new
'time_end' => DB::raw("time_end - {$timeReduction}"),
]);
}

/**
* Update future building queue items for this planet to advance their time_start and time_end
* equally based on a issued halving/completing time reduction.
*
* @param int $planetId Planet whose building queue should be updated
* @param int $oldTimeEnd Original time_end of the modified item (before the change)
* @param int $newTimeEnd New time_end of the modified item (after the change)
* @return void
*/
private function updateFutureBuildingQueueItems(int $planetId, int $oldTimeEnd, int $newTimeEnd): void
{
if ($newTimeEnd >= $oldTimeEnd) {
return;
}

$timeReduction = $oldTimeEnd - $newTimeEnd;

BuildingQueue::where('planet_id', $planetId)
->where('processed', 0)
->where('canceled', 0)
->where('time_start', '>=', $oldTimeEnd)
->update([
'time_start' => DB::raw("time_start - {$timeReduction}"),
'time_end' => DB::raw("time_end - {$timeReduction}"),
]);
}
}
4 changes: 3 additions & 1 deletion app/ViewModels/Queue/BuildingQueueViewModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class BuildingQueueViewModel extends QueueViewModel
* @param bool $building
* @param int $level_target
* @param bool $is_downgrade
* @param bool $dm_halved
*/
public function __construct(
int $id,
Expand All @@ -25,7 +26,8 @@ public function __construct(
int $time_total,
public bool $building,
public int $level_target,
public bool $is_downgrade = false
public bool $is_downgrade = false,
public bool $dm_halved = false,
) {
parent::__construct($id, $object, $time_countdown, $time_total);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class () extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('building_queues', function (Blueprint $table) {
$table->tinyInteger('dm_halved')->default(0)->after('processed');
$table->tinyInteger('dm_completed')->default(0)->after('dm_halved');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('building_queues', function (Blueprint $table) {
$table->dropColumn(['dm_halved', 'dm_completed']);
});
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,40 @@
<td colspan="2">
@php
$halvingService = app(\OGame\Services\HalvingService::class);
$halvingCost = $halvingService->calculateHalvingCost($build_active->time_total, 'building');
$halvingCost = $halvingService->calculateHalvingCost($build_active->time_countdown, 'building');
$wouldComplete = $build_active->dm_halved;
@endphp
<a class="build-faster dark_highlight tooltipLeft js_hideTipOnMobile building "
title="Reduces construction time by 50% of the total construction time."
href="javascript:void(0);"
rel="{{ route('facilities.halvebuilding') }}?queue_item_id={{ $build_active->id }}">
<div class="build-faster-img" alt="Halve time"></div>
<span class="build-txt">Halve time</span>
<span class="dm_cost">Costs: {{ number_format($halvingCost) }} DM</span>
</a>
@if ($wouldComplete)
<a class="build-faster dark_highlight tooltipLeft js_hideTipOnMobile building tpd-hideOnClickOutside"
title="@lang('Instantly completes the current building construction.')"
href="javascript:void(0);"
rel="{{ route('facilities.completebuilding') }}?queue_item_id={{ $build_active->id }}">
<div class="build-finish-img" alt="@lang('Complete')"></div>
<span class="build-txt">@lang('Complete')</span>
<span class="dm_cost">@lang('Costs: :amount DM', ['amount' => number_format($halvingCost)])</span>
</a>
@else
<a class="build-faster dark_highlight tooltipLeft js_hideTipOnMobile building tpd-hideOnClickOutside"
title="@lang('Reduces construction time by 50% of the total construction time.')"
href="javascript:void(0);"
rel="{{ route('facilities.halvebuilding') }}?queue_item_id={{ $build_active->id }}">
<div class="build-faster-img" alt="@lang('Halve time')"></div>
<span class="build-txt">@lang('Halve time')</span>
<span class="dm_cost">@lang('Costs: :amount DM', ['amount' => number_format($halvingCost)])</span>
</a>
@endif
</td>
</tr>
</tbody>
</table>
<script type="text/javascript">
var cancelBuildListEntryUrl = '{{ route('resources.cancelbuildrequest') }}';
var questionbuilding = 'Do you want to reduce the construction time of the current construction project by 50% of the total construction time for <span style="font-weight: bold;">{{ number_format($halvingCost) }} Dark Matter</span>?';
@if ($wouldComplete)
var questionbuilding = '{!! __('Do you want to immediately complete the construction order for :dm_cost?', ['dm_cost' => '<span style="font-weight: bold;">' . number_format($halvingCost) . ' ' . __('Dark Matter') . '</span>']) !!}';
@else
var questionbuilding = '{!! __('Do you want to reduce the construction time of the current construction project by 50% of the total construction time (:time_reduction) for :dm_cost?', ['time_reduction' => \OGame\Facades\AppUtil::formatTimeDuration(intdiv($build_active->time_total, 2)), 'dm_cost' => '<span style="font-weight: bold;">' . number_format($halvingCost) . ' ' . __('Dark Matter') . '</span>']) !!}';
@endif
var pricebuilding = {{ $halvingCost }};
var cancelBuildListEntryUrl = '{{ route('resources.cancelbuildrequest') }}';
var referrerPage = $.deparam.querystring().page;

new CountdownTimer('buildingCountdown', {{ $build_active->time_countdown }}, '{{ url()->current() }}', null, true, 3)
Expand All @@ -88,4 +104,4 @@ function cancelbuilding(id, listId, question) {
</tr>
</tbody>
</table>
@endif
@endif
1 change: 1 addition & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
Route::post('/facilities/downgrade', [FacilitiesController::class, 'downgradeBuildRequest'])->name('facilities.downgrade');
Route::post('/facilities/cancel-buildrequest', [FacilitiesController::class, 'cancelBuildRequest'])->name('facilities.cancelbuildrequest');
Route::post('/ajax/facilities/halve-building', [FacilitiesController::class, 'halveBuilding'])->name('facilities.halvebuilding');
Route::post('/ajax/facilities/complete-building', [FacilitiesController::class, 'completeBuilding'])->name('facilities.completebuilding');
Route::post('/ajax/facilities/start-repairs', [FacilitiesController::class, 'startRepairs'])->name('facilities.startrepairs');
Route::post('/ajax/facilities/complete-repairs', [FacilitiesController::class, 'completeRepairs'])->name('facilities.completerepairs');
Route::post('/ajax/facilities/burn-wreck-field', [FacilitiesController::class, 'burnWreckField'])->name('facilities.burnwreckfield');
Expand Down
Loading
Loading