Added 1v4, 1v5 matchmaking timeout as a setting in the match configuration admin panel.

This commit is contained in:
Vari 2025-07-10 01:04:46 +02:00
parent 0e4d0969d9
commit 62f0be4398
7 changed files with 126 additions and 41 deletions

View File

@ -5,6 +5,7 @@
use App\Classes\Matchmaking\MatchmakingPlayerCount;
use App\Enums\Game\Matchmaking\MatchmakingSide;
use App\Enums\Game\Matchmaking\MatchStatus;
use App\Models\Admin\MatchmakingSettings;
use App\Models\Game\Matchmaking\Game;
use App\Models\Game\Matchmaking\MatchConfiguration;
use App\Models\Game\Matchmaking\QueuedPlayer;
@ -19,11 +20,6 @@ class ProcessMatchmaking extends Command
{
public static int $repeatTimeSeconds = 20;
/**
* How long the matchmaking should wait when only one 1v4 or 1v5 could be made before actually making it.
*/
const ONE_VS_FOUR_AND_FIVE_WAIT_TIME = 10;
const ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY = 'matchmaking_attempt_1v4_1v5';
/**
@ -47,6 +43,7 @@ class ProcessMatchmaking extends Command
*/
public function handle(): void
{
$matchmakingSettings = MatchmakingSettings::get();
// Select all queued Players/party leaders, descending by party size
$players = QueuedPlayer::withCount('followingUsers')
->sharedLock()
@ -96,7 +93,7 @@ public function handle(): void
if (Cache::has(static::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY)) {
/** @var Carbon $firstAttempt */
$firstAttempt = Cache::get(static::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY);
if ($firstAttempt->diffInSeconds(Carbon::now()) < static::ONE_VS_FOUR_AND_FIVE_WAIT_TIME){
if ($firstAttempt->diffInSeconds(Carbon::now()) < $matchmakingSettings->matchWaitingTime){
return;
}
Cache::forget(static::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY);

View File

@ -17,6 +17,7 @@
use App\Models\Admin\Archive\ArchivedGame;
use App\Models\Admin\Archive\ArchivedPlayerProgression;
use App\Models\Admin\CurrencyMultipliers;
use App\Models\Admin\MatchmakingSettings;
use App\Models\Admin\Versioning\CurrentGameVersion;
use App\Models\Game\Matchmaking\Game;
use App\Models\Game\Matchmaking\QueuedPlayer;
@ -40,10 +41,10 @@ public function getRegions()
public function queue(QueueRequest $request)
{
if($request->category !== CurrentGameVersion::get()?->gameVersion)
if ($request->category !== CurrentGameVersion::get()?->gameVersion)
abort(403, 'Too old mod version');
if($request->checkOnly)
if ($request->checkOnly)
return json_encode($this->checkQueueStatus($request));
return json_encode($this->addPlayerToQueue($request));
@ -53,20 +54,20 @@ public function cancelQueue(MatchmakingRequest $request)
{
$user = Auth::user();
if($user->id !== $request->playerId || $request->endState !== 'Cancelled')
if ($user->id !== $request->playerId || $request->endState !== 'Cancelled')
return response('', 204);
$lock = Cache::lock(static::QUEUE_LOCK, 10);
try {
$lock->block(20 ,function () use (&$user) {
$lock->block(20, function () use (&$user) {
// Delete the player from the Queue
QueuedPlayer::where('user_id', '=', $user->id)->delete();
// And also from any game they are matched for.
DB::table('game_user')->where('user_id', '=', $user->id)->delete();
});
} catch (LockTimeoutException $e) {
Log::channel('matchmaking')->emergency('Queue Cancel: Could not acquire Lock for canceling user '.$user->id.'('.$user->last_known_username.')');
Log::channel('matchmaking')->emergency('Queue Cancel: Could not acquire Lock for canceling user ' . $user->id . '(' . $user->last_known_username . ')');
} finally {
$lock->release();
}
@ -78,7 +79,7 @@ public function matchInfo(string $matchId)
{
$foundGame = Game::find($matchId);
if($foundGame === null)
if ($foundGame === null)
return ['status' => 'Error', 'message' => 'Match not found.'];
$user = Auth::user();
@ -103,24 +104,24 @@ public function register(RegisterMatchRequest $request, string $matchId)
return json_encode(MatchData::fromGame($foundGame));
}
public function close($matchId)
{
$foundGame = Game::find($matchId);
$foundGame->status = MatchStatus::Closed;
$foundGame->save();
public function close($matchId)
{
$foundGame = Game::find($matchId);
$foundGame->status = MatchStatus::Closed;
$foundGame->save();
return json_encode(MatchData::fromGame($foundGame));
}
return json_encode(MatchData::fromGame($foundGame));
}
/*
* Set a game to Quit state, Maybe exists but unsure since it never showed up in the request logs.
*/
public function quit($matchId)
{
$foundGame = Game::find($matchId);
public function quit($matchId)
{
$foundGame = Game::find($matchId);
$user = Auth::user();
if($foundGame->creator === $user) {
if ($foundGame->creator === $user) {
$foundGame->status = MatchStatus::Destroyed;
$foundGame->save();
}
@ -129,17 +130,17 @@ public function quit($matchId)
}
return response(null, 204);
}
}
public function kill($matchId)
{
$foundGame = Game::find($matchId);
public function kill($matchId)
{
$foundGame = Game::find($matchId);
$response = json_encode(MatchData::fromGame($foundGame));
$foundGame->delete();
return $response;
}
return $response;
}
public function seedFileGet(string $gameVersion, string $seed, string $mapName)
{
@ -156,7 +157,7 @@ public function deleteUserFromMatch(Game $match, User $user)
$requestUser = Auth::user();
// Block request if it doesn't come from the host
if($match->creator->id != $requestUser->id)
if ($match->creator->id != $requestUser->id)
return response('Action not allowed, you are not the creator of the match.', 403);
$this->removeUserFromGame($user, $match);
@ -166,13 +167,13 @@ public function deleteUserFromMatch(Game $match, User $user)
public function endOfMatch(EndOfMatchRequest $request)
{
if(ArchivedGame::archivedGameExists($request->matchId))
if (ArchivedGame::archivedGameExists($request->matchId))
return response('Match Already Closed', 209);
$game = Game::find($request->matchId);
$user = Auth::user();
if($game->creator != $user)
if ($game->creator != $user)
return response('you are not the creator of the match.', 403);
$game->status = MatchStatus::Killed;
@ -187,7 +188,7 @@ public function playerEndOfMatch(PlayerEndOfMatchRequest $request)
$user = Auth::user();
$game = Game::find($request->matchId);
if($game === null)
if ($game === null)
return response('Match not found.', 404);
if ($game->creator != $user)
@ -195,21 +196,21 @@ public function playerEndOfMatch(PlayerEndOfMatchRequest $request)
$user = User::find($request->playerId);
if($user === null)
if ($user === null)
return response('User not found.', 404);
$lock = Cache::lock('playerEndOfMatch'.$user->id);
$lock = Cache::lock('playerEndOfMatch' . $user->id);
try {
// Lock the saving of the playerdata and stuff because the game can send multiple calls sometimes
$lock->block(10 ,function () use (&$user, &$request, &$game) {
if(ArchivedPlayerProgression::archivedPlayerExists($game->id, $user->id))
if (ArchivedPlayerProgression::archivedPlayerExists($game->id, $user->id))
return;
$playerData = $user->playerData();
$characterData = $playerData->characterDataForCharacter($request->characterGroup->getCharacter());
if($request->hasQuit)
if ($request->hasQuit)
$playerData->quitterState->addQuitterPenalty();
else
$playerData->quitterState->addStayedMatch($playerData);
@ -379,10 +380,11 @@ protected function removeUserFromGame(User $user, Game $game)
protected static function getETA(): int {
if(Cache::has(ProcessMatchmaking::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY)) {
$matchmakingSettings = MatchmakingSettings::get();
/** @var Carbon $firstAttempt */
$firstAttempt = Cache::get(ProcessMatchmaking::ONE_VS_FOUR_AND_VS_FIVE_FIRST_ATTEMPT_CACHE_KEY);
$predictedMatchTime = $firstAttempt->copy();
while ($firstAttempt->diffInSeconds($predictedMatchTime) < ProcessMatchmaking::ONE_VS_FOUR_AND_FIVE_WAIT_TIME) {
while ($firstAttempt->diffInSeconds($predictedMatchTime) < $matchmakingSettings->matchWaitingTime) {
$predictedMatchTime->addSeconds(ProcessMatchmaking::$repeatTimeSeconds);
}

View File

@ -7,10 +7,12 @@
use App\Http\Requests\Api\Admin\Tools\SaveCurrencyConfiguration;
use App\Http\Requests\Api\Admin\Tools\SaveLauncherMessageRequest;
use App\Http\Requests\Api\Admin\Tools\SaveMatchConfigurationRequest;
use App\Http\Requests\Api\Admin\Tools\SaveMatchmakingConfigurationRequest;
use App\Http\Requests\Api\Admin\Tools\SaveVersioningRequest;
use App\Models\Admin\CurrencyMultipliers;
use App\Models\Admin\ExperienceMultipliers;
use App\Models\Admin\LauncherMessage;
use App\Models\Admin\MatchmakingSettings;
use App\Models\Admin\Versioning\CurrentCatalogVersion;
use App\Models\Admin\Versioning\CurrentContentVersion;
use App\Models\Admin\Versioning\CurrentGameVersion;
@ -73,4 +75,20 @@ public function saveCurrency(SaveCurrencyConfiguration $request)
return back();
}
public function saveMatchmaking(SaveMatchmakingConfigurationRequest $request) {
$matchmakingConfig = MatchmakingSettings::get();
try {
$matchmakingConfig->matchWaitingTime = $request->matchmakingWaitingTime;
$matchmakingConfig->save();
Session::flash('alert-success', 'Matchmaking configuration saved successfully.');
}
catch (\Exception $e) {
Session::flash('alert-error', 'Matchmaking configuration could not be saved, something went wrong: ' . $e->getMessage());
}
return back();
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Requests\Api\Admin\Tools;
use Illuminate\Foundation\Http\FormRequest;
class SaveMatchmakingConfigurationRequest extends FormRequest
{
public int $matchmakingWaitingTime;
public function rules(): array {
return [
'matchmakingWaitingTime' => ['required', 'integer'],
];
}
protected function passedValidation()
{
$this->matchmakingWaitingTime = (int)$this->input('matchmakingWaitingTime');
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Models\Admin;
use App\Models\AbstractFileBasedModel;
class MatchmakingSettings extends AbstractFileBasedModel
{
const FILE_NAME = 'matchmakingSettings';
const CACHE_DURATION = 86400;
/**
* How long the matchmaking should wait when only one 1v4 or 1v5 could be made before actually making it.
*/
public int $matchWaitingTime = 10;
protected static function getDefault(): ?static
{
return new static();
}
}

View File

@ -3,14 +3,15 @@
$experienceMultipliers = \App\Models\Admin\ExperienceMultipliers::get();
$currencyMultipliers = \App\Models\Admin\CurrencyMultipliers::get();
$matchmakingSettings = \App\Models\Admin\MatchmakingSettings::get();
@endphp
<x-layouts.admin>
<div class="container mx-auto border bg-slate-800 border-slate-500 rounded-xl p-4 my-6">
<div class="flex flex-col gap-4 px-4 lg:[&>form>div]:px-52 [&>form>div]:flex [&>form>div]:justify-between [&>form>div]:items-center [&>form_label]:text-xl">
<form action="{{ route('match-configuration.save.experience') }}" method="post">
<form action="{{ route('match-configuration.save.experience') }}" method="post" class="[&>div]:m-2">
@csrf
<span class="headline">
<span class="headline mb-2">
Experience Multipliers
</span>
@ -139,7 +140,7 @@
Save
</x-inputs.button>
</form>
<form action="{{ route('match-configuration.save.currency') }}" method="post" class="mt-8">
<form action="{{ route('match-configuration.save.currency') }}" method="post" class="mt-8 [&>div]:m-2">
@csrf
<span class="headline">
Currency Multipliers
@ -190,6 +191,29 @@
Save
</x-inputs.button>
</form>
<form action="{{ route('match-configuration.save.matchmaking') }}" method="post" class="mt-8 [&>div]:m-2">
@csrf
<span class="headline mb-2">
Matchmaking Settings
</span>
<div>
<label for="matchmakingWaitingTime">
1v4 and 1v5 matchmaking wait duration
</label>
<x-inputs.number
id="matchmakingWaitingTime"
name="matchmakingWaitingTime"
value="{{ $matchmakingSettings->matchWaitingTime }}"
step="1"
required
/>
</div>
<x-inputs.button class="save mt-2">
Save
</x-inputs.button>
</form>
</div>
</div>
</x-layouts.admin>

View File

@ -56,6 +56,7 @@
Route::get('match-configuration', [MatchConfigurationController::class, 'index'])->name(MatchConfigurationController::class);
Route::post('match-configuration/experience', [MatchConfigurationController::class, 'saveExperience'])->name('match-configuration.save.experience');
Route::post('match-configuration/currency', [MatchConfigurationController::class, 'saveCurrency'])->name('match-configuration.save.currency');
Route::post('match-configuration/matchmaking', [MatchConfigurationController::class, 'saveMatchmaking'])->name('match-configuration.save.matchmaking');
Route::fallback(function () {
return redirect(route('admin.dashboard'));