Files
Deathgarden_Rebirth-Rewrite/dist/app/Http/Controllers/Api/LeaderboardController.php

179 lines
6.9 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Enums\Game\Faction;
use App\Enums\Game\Leaderboard\Window;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Leaderboard\GetScoresRequest;
use App\Http\Responses\Api\Leaderboard\GetScoresResponse;
use App\Http\Responses\Api\Leaderboard\LeaderboardEntry;
use App\Models\Admin\Archive\ArchivedPlayerProgression;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
class LeaderboardController extends Controller
{
public function getScores(GetScoresRequest $request)
{
return match ($request->window) {
Window::PersonalBest => static::handlePersonalBestWindow($request),
Window::Lobby => static::handleLobbyWindow($request),
};
}
public static function handlePersonalBestWindow(GetScoresRequest $request)
{
$response = new GetScoresResponse();
$topScores = [];
$startDate = static::getCurrentLeaderboardStartDate();
static::getLeaderboardTopScores(5, $request->faction, $startDate)
->each(function (ArchivedPlayerProgression $item, int $rank) use (&$topScores) {
$topScores[] = new LeaderboardEntry(
$item->user,
$item->gained_experience,
$rank + 1,
);
});
foreach ($request->playerIds as $playerId) {
$entries = static::getLeaderboardEntriesForPlayer($playerId, $request->faction, $startDate);
$response->playerScores[$playerId] = [];
$entries->each(function (ArchivedPlayerProgression $item) use (&$response, $playerId) {
$response->playerScores[$playerId][] = new LeaderboardEntry(
$item->user,
$item->gained_experience,
$item->rank,
);
});
}
$response->topScores = $topScores;
$response->leaderboardSize = static::getLeaderboardSize($request->faction, $startDate);
$response->leaderboardReset = static::getCurrentLeaderboardEndDate();
return $response;
}
public static function handleLobbyWindow(GetScoresRequest $request)
{
$response = new GetScoresResponse();
$startDate = static::getCurrentLeaderboardStartDate();
static::getLeaderboardEntriesForPlayer($request->playerIds, $request->faction, $startDate)
->each(function (ArchivedPlayerProgression $item) use (&$response, &$lobbyScores) {
$response->playerScores[$item->user_id][] = new LeaderboardEntry(
$item->user,
$item->gained_experience,
$item->rank,
);
});
$response->leaderboardSize = static::getLeaderboardSize($request->faction, $startDate);
$response->leaderboardReset = static::getCurrentLeaderboardEndDate();
return $response;
}
/**
* @param string|array $userId Select the given user id. if its an array it only selects them and no one around.
* @param Faction $faction
* @param Carbon $startDate Player scores before this Time won't get selected.
* @param ?Carbon $endDate scores after this time won't be selected, defaults to now when null.
* @return Collection<int, ArchivedPlayerProgression>
*/
public static function getLeaderboardEntriesForPlayer(string|array $userId, Faction $faction, Carbon $startDate, ?Carbon $endDate = null): Collection
{
$endDate ??= Carbon::now();
$subSubQuery = static::getBaseSelectQuery($faction, $startDate, $endDate);
$query = ArchivedPlayerProgression::query();
$query->orderBy('gained_experience', 'desc');
$query->select(['user_id', 'gained_experience'])
->addSelect(DB::raw('ROW_NUMBER() OVER (ORDER BY gained_experience DESC) AS "rank"'));
$query->fromSub($subSubQuery, 'a');
$mainQuery = ArchivedPlayerProgression::query();
$mainQuery->fromSub($query, 'b');
if (is_array($userId)) {
$mainQuery->whereIn('user_id', $userId);
} else {
$mainQuery->where('rank', '<=', function (\Illuminate\Database\Query\Builder $whereQuery) use ($userId, $query) {
$whereQuery->where('user_id', $userId);
$whereQuery->fromSub($query, 'c');
$whereQuery->selectRaw('(c.rank + (CASE c.rank WHEN 1 THEN 4 WHEN 2 THEN 3 WHEN 3 THEN 2 ELSE 1 END))');
});
}
$mainQuery->orderByDesc('rank');
$mainQuery->limit(is_array($userId) ? count($userId) : 5);
return $mainQuery->get();
}
/**
* @param int $count
* @param Faction $faction
* @param Carbon $startDate Player scores before this Time won't get selected.
* @param ?Carbon $endDate scores after this time won't be selected, defaults to now when null.
* @return Collection<int, ArchivedPlayerProgression>
*/
public static function getLeaderboardTopScores(int $count, Faction $faction, Carbon $startDate, ?Carbon $endDate = null): Collection
{
$endDate ??= Carbon::now();
$query = static::getBaseSelectQuery($faction, $startDate, $endDate);
$query->limit($count);
return $query->get();
}
public static function getCurrentLeaderboardStartDate(): Carbon
{
return Carbon::today()->firstOfMonth()->addHours(20);
}
public static function getCurrentLeaderboardEndDate(): Carbon {
return Carbon::today()->addMonth()->firstOfMonth()->addHours(20);
}
/**
* @param Faction $faction
* @param Carbon $startDate Player scores before this Time won't get selected.
* @param ?Carbon $endDate scores after this time won't be selected, defaults to now when null.
* @return int
*/
public static function getLeaderboardSize(Faction $faction, Carbon $startDate, ?Carbon $endDate = null): int
{
$endDate ??= Carbon::now();
$query = ArchivedPlayerProgression::query();
$query->whereDate('created_at', '>=', $startDate);
$query->whereDate('created_at', '<=', $endDate);
$query->whereIn('played_character', $faction->getCharacterList());
$query->distinct('user_id');
$query->selectRaw('user_id');
return $query->count('user_id');
}
protected static function getBaseSelectQuery(Faction $faction, Carbon $startDate, Carbon $endDate): Builder
{
$subSubQuery = ArchivedPlayerProgression::query();
$subSubQuery->select(['user_id'])->addSelect(DB::raw('MAX(gained_experience) as gained_experience'));
$subSubQuery->whereDate('created_at', '>=', $startDate);
$subSubQuery->whereDate('created_at', '<=', $endDate);
$subSubQuery->whereIn('played_character', $faction->getCharacterList());
$subSubQuery->groupBy('user_id');
$subSubQuery->orderByDesc('gained_experience');
return $subSubQuery;
}
}