From 179adc7518d37af71c5702309f7b6010c8636a1b Mon Sep 17 00:00:00 2001 From: Vari Date: Wed, 31 Jul 2024 21:36:51 +0200 Subject: [PATCH] Added Game and Player match history. Also adjusted how players get deleted from a match. --- .gitignore | 2 +- dist/.env.example | 1 + dist/.gitignore | 1 + .../Api/Matchmaking/MatchmakingController.php | 101 ++++++++++++------ dist/app/Http/Middleware/AccessLogger.php | 4 +- .../Matchmaking/PlayerEndOfMatchRequest.php | 7 +- .../app/Models/Admin/Archive/ArchivedGame.php | 28 +++++ .../Archive/ArchivedPlayerProgression.php | 68 ++++++++++++ dist/app/Models/Game/Matchmaking/Game.php | 15 +++ dist/composer.lock | 36 +++---- dist/config/database.php | 3 + ...ate_archived_player_progressions_table.php | 45 ++++++++ ..._31_185625_create_archived_games_table.php | 30 ++++++ dist/routes/deathgardenApi.php | 2 +- 14 files changed, 289 insertions(+), 54 deletions(-) create mode 100644 dist/app/Models/Admin/Archive/ArchivedGame.php create mode 100644 dist/app/Models/Admin/Archive/ArchivedPlayerProgression.php create mode 100644 dist/database/migrations/2024_07_31_185616_create_archived_player_progressions_table.php create mode 100644 dist/database/migrations/2024_07_31_185625_create_archived_games_table.php diff --git a/.gitignore b/.gitignore index b49d47e..47538c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea .vscode -.fleet \ No newline at end of file +.fleet diff --git a/dist/.env.example b/dist/.env.example index 3460de5..a24ec23 100644 --- a/dist/.env.example +++ b/dist/.env.example @@ -14,6 +14,7 @@ DB_PORT=3306 DB_DATABASE=deathgarden_rebirth_dashboard DB_USERNAME=root DB_PASSWORD= +DB_ENABLE_QUERY_LOG=false BROADCAST_DRIVER=log CACHE_DRIVER=file diff --git a/dist/.gitignore b/dist/.gitignore index 47565e8..4b2cade 100644 --- a/dist/.gitignore +++ b/dist/.gitignore @@ -18,4 +18,5 @@ yarn-error.log /.fleet _ide_helper.php _ide_helper_models.php +.phpstorm.meta.php /public/game-files diff --git a/dist/app/Http/Controllers/Api/Matchmaking/MatchmakingController.php b/dist/app/Http/Controllers/Api/Matchmaking/MatchmakingController.php index e3d9ee7..6ac94cb 100644 --- a/dist/app/Http/Controllers/Api/Matchmaking/MatchmakingController.php +++ b/dist/app/Http/Controllers/Api/Matchmaking/MatchmakingController.php @@ -16,6 +16,8 @@ use App\Http\Responses\Api\Matchmaking\MatchProperties; use App\Http\Responses\Api\Matchmaking\QueueData; use App\Http\Responses\Api\Matchmaking\QueueResponse; +use App\Models\Admin\Archive\ArchivedGame; +use App\Models\Admin\Archive\ArchivedPlayerProgression; use App\Models\Game\CharacterData; use App\Models\Game\Matchmaking\Game; use App\Models\Game\Matchmaking\MatchConfiguration; @@ -162,21 +164,24 @@ public function seedFilePost(string $gameVersion, string $seed, string $mapName) return response('', 200); } - public function deleteUserFromMatch(string $matchId, string $userId) + public function deleteUserFromMatch(Game $game, User $user) { - $foundGame = Game::find($matchId); - $userToRemove = User::find($userId); $requestUser = Auth::user(); // Block request if it doesn't come from the host - if($foundGame === null || $foundGame->creator != $requestUser) + if($game->creator->id != $requestUser->id) return response('Action not allowed, you are not the creator of the match.', 403); - $foundGame->players()->detach($userToRemove); + $this->removeUserFromGame($user, $game); + + return json_encode(['success' => true]); } public function endOfMatch(EndOfMatchRequest $request) { + if(ArchivedGame::archivedGameExists($request->matchId)) + return response('Match Already Closed', 209); + $game = Game::find($request->matchId); $user = Auth::user(); @@ -185,6 +190,7 @@ public function endOfMatch(EndOfMatchRequest $request) $game->status = MatchStatus::Killed; $game->save(); + $game->archiveGame($request->dominantFaction); return json_encode(['success' => true]); } @@ -205,34 +211,69 @@ public function playerEndOfMatch(PlayerEndOfMatchRequest $request) if($user === null) return response('User not found.', 404); - $playerData = $user->playerData(); - $characterData = $playerData->characterDataForCharacter($request->characterGroup->getCharacter()); + $lock = Cache::lock('playerEndOfMatch'.$user->id); - foreach ($request->experienceEvents as $experienceEvent) { - $characterData->addExperience($experienceEvent['amount']); + 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)) + return; + + $playerData = $user->playerData(); + $characterData = $playerData->characterDataForCharacter($request->characterGroup->getCharacter()); + + $experienceSum = 0; + + foreach ($request->experienceEvents as $experienceEvent) { + $experienceSum += (int)$experienceEvent['amount']; + } + + $characterData->addExperience($experienceSum); + + ++$characterData->readout_version; + $characterData->save(); + + $gainedCurrencyA = 0; + $gainedCurrencyB = 0; + $gainedCurrencyC = 0; + + foreach ($request->earnedCurrencies as $earnedCurrency) { + switch ($earnedCurrency['currencyName']) { + case 'CurrencyA': + $gainedCurrencyA += $earnedCurrency['amount']; + break; + case 'CurrencyB': + $gainedCurrencyB += $earnedCurrency['amount']; + break; + case 'CurrencyC': + $gainedCurrencyC += $earnedCurrency['amount']; + } + } + + $playerData->currency_a += $gainedCurrencyA; + $playerData->currency_b += $gainedCurrencyB; + $playerData->currency_c += $gainedCurrencyC; + + ++$playerData->readout_version; + $playerData->save(); + + ArchivedPlayerProgression::archivePlayerProgression( + $game, + $user, + $request->hasQuit, + $request->characterGroup->getCharacter(), + $request->characterState, + $experienceSum, + $request->experienceEvents, + $gainedCurrencyA, + $gainedCurrencyB, + $gainedCurrencyC, + ); + }); + } catch (LockTimeoutException $e) { + return response('The Player end of match request for this user is currently being processed', 409); } - ++$characterData->readout_version; - $characterData->save(); - - foreach ($request->earnedCurrencies as $earnedCurrency) { - switch ($earnedCurrency['currencyName']) { - case 'CurrencyA': - $playerData->currency_a += $earnedCurrency['amount']; - break; - case 'CurrencyB': - $playerData->currency_b += $earnedCurrency['amount']; - break; - case 'CurrencyC': - $playerData->currency_c += $earnedCurrency['amount']; - } - } - - ++$playerData->readout_version; - $playerData->save(); - - $this->removeUserFromGame($user, $game); - // We dont really know what the game wants except for a json object called "player". return json_encode(['player' => []], JSON_FORCE_OBJECT); } diff --git a/dist/app/Http/Middleware/AccessLogger.php b/dist/app/Http/Middleware/AccessLogger.php index 35ce9cb..000e206 100644 --- a/dist/app/Http/Middleware/AccessLogger.php +++ b/dist/app/Http/Middleware/AccessLogger.php @@ -24,7 +24,9 @@ public function handle(Request $request, Closure $next): Response if (!Str::contains($request->userAgent(), 'TheExit')) return $next($request); - DB::enableQueryLog(); + if(config('database.enable-query-logging')) + DB::enableQueryLog(); + $response = $next($request); $log = new stdClass(); diff --git a/dist/app/Http/Requests/Api/Matchmaking/PlayerEndOfMatchRequest.php b/dist/app/Http/Requests/Api/Matchmaking/PlayerEndOfMatchRequest.php index b30ba80..fe15bd2 100644 --- a/dist/app/Http/Requests/Api/Matchmaking/PlayerEndOfMatchRequest.php +++ b/dist/app/Http/Requests/Api/Matchmaking/PlayerEndOfMatchRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests\Api\Matchmaking; +use App\Enums\Game\CharacterState; use App\Enums\Game\Faction; use App\Enums\Game\ItemGroupType; use Illuminate\Foundation\Http\FormRequest; @@ -22,7 +23,7 @@ class PlayerEndOfMatchRequest extends FormRequest public bool $hasQuit; - public string $characterState; + public CharacterState $characterState; public string $matchId; @@ -56,7 +57,7 @@ public function rules(): array 'data.playtime' => 'required|int', 'data.platform' => 'string', 'data.hasQuit' => 'required|bool', - 'data.characterState' => 'required|string', + 'data.characterState' => ['required', Rule::enum(CharacterState::class)], 'data.matchId' => 'required|string', 'data.matchGameMode' => 'required|string', 'data.experienceEvents' => 'present|array', @@ -73,7 +74,7 @@ protected function passedValidation() $this->playtime = $this->input('data.playtime', 0); $this->platform = $this->input('data.platform', 'None'); $this->hasQuit = $this->input('data.hasQuit'); - $this->characterState = $this->input('data.characterState'); + $this->characterState = CharacterState::tryFrom($this->input('data.characterState')); $this->matchId = $this->input('data.matchId'); $this->gamemode = $this->input('data.matchGameMode'); $this->experienceEvents = $this->input('data.experienceEvents'); diff --git a/dist/app/Models/Admin/Archive/ArchivedGame.php b/dist/app/Models/Admin/Archive/ArchivedGame.php new file mode 100644 index 0000000..a460552 --- /dev/null +++ b/dist/app/Models/Admin/Archive/ArchivedGame.php @@ -0,0 +1,28 @@ + Faction::class, + ]; + + public function archivedPlayerProgressions(): HasMany + { + return $this->hasMany(ArchivedPlayerProgression::class); + } + + public static function archivedGameExists(string $matchId) { + return ArchivedGame::where('id', '=', $matchId)->exists(); + } + + +} diff --git a/dist/app/Models/Admin/Archive/ArchivedPlayerProgression.php b/dist/app/Models/Admin/Archive/ArchivedPlayerProgression.php new file mode 100644 index 0000000..27b71c4 --- /dev/null +++ b/dist/app/Models/Admin/Archive/ArchivedPlayerProgression.php @@ -0,0 +1,68 @@ + Characters::class, + 'character_state' => CharacterState::class, + 'experience_events' => 'array', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function archivedGame(): BelongsTo { + return $this->belongsTo(ArchivedGame::class); + } + + public static function archivePlayerProgression( + Game $game, + User &$user, + bool $hasQuit, + Characters $playedCharacter, + CharacterState $characterState, + int $gainedExperience, + array $experienceEvents, + int $gainedCurrencyA, + int $gainedCurrencyB, + int $gainedCurrencyC, + ): void { + if(ArchivedPlayerProgression::archivedPlayerExists($game->id, $user->id)) + return; + + $archived = new ArchivedPlayerProgression(); + + $archived->user_id = $user->id; + $archived->archived_game_id = $game->id; + $archived->played_character = $playedCharacter; + $archived->character_state = $characterState; + $archived->has_quit = $hasQuit; + $archived->gained_experience = $gainedExperience; + $archived->experience_events = $experienceEvents; + $archived->gained_currency_a = $gainedCurrencyA; + $archived->gained_currency_b = $gainedCurrencyB; + $archived->gained_currency_c = $gainedCurrencyC; + $archived->save(); + } + + public static function archivedPlayerExists(string $matchId, string $userId): bool { + return ArchivedPlayerProgression::where('user_id', '=', $userId) + ->where('archived_game_id', '=', $matchId) + ->exists(); + } +} diff --git a/dist/app/Models/Game/Matchmaking/Game.php b/dist/app/Models/Game/Matchmaking/Game.php index 94de464..b5dbc22 100644 --- a/dist/app/Models/Game/Matchmaking/Game.php +++ b/dist/app/Models/Game/Matchmaking/Game.php @@ -3,8 +3,14 @@ namespace App\Models\Game\Matchmaking; use App\Classes\Matchmaking\MatchmakingPlayerCount; +use App\Enums\Game\Characters; +use App\Enums\Game\CharacterState; +use App\Enums\Game\Faction; use App\Enums\Game\Matchmaking\MatchmakingSide; use App\Enums\Game\Matchmaking\MatchStatus; +use App\Http\Requests\Api\Matchmaking\PlayerEndOfMatchRequest; +use App\Models\Admin\Archive\ArchivedGame; +use App\Models\Admin\Archive\ArchivedPlayerProgression; use App\Models\User\User; use Cache; use Illuminate\Database\Eloquent\Concerns\HasUuids; @@ -13,6 +19,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Facades\DB; +use Response; /** * @mixin IdeHelperGame @@ -94,4 +101,12 @@ public function remainingPlayerCount(): MatchmakingPlayerCount $config->runners - $currentRunnerCount, ); } + + public function archiveGame(Faction $dominantFaction): void + { + $archived = new ArchivedGame(); + $archived->id = $this->id; + $archived->dominant_faction = $dominantFaction; + $archived->save(); + } } diff --git a/dist/composer.lock b/dist/composer.lock index b82ec51..394b288 100644 --- a/dist/composer.lock +++ b/dist/composer.lock @@ -6236,39 +6236,39 @@ "packages-dev": [ { "name": "barryvdh/laravel-ide-helper", - "version": "v2.14.0", + "version": "v2.15.1", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "485c756f6cff408d6b273274c5e86112c3973d98" + "reference": "77831852bb7bc54f287246d32eb91274eaf87f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/485c756f6cff408d6b273274c5e86112c3973d98", - "reference": "485c756f6cff408d6b273274c5e86112c3973d98", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/77831852bb7bc54f287246d32eb91274eaf87f8b", + "reference": "77831852bb7bc54f287246d32eb91274eaf87f8b", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.6", "composer/class-map-generator": "^1.0", - "doctrine/dbal": "^2.6 || ^3", + "doctrine/dbal": "^2.6 || ^3.1.4", "ext-json": "*", - "illuminate/console": "^8 || ^9 || ^10", - "illuminate/filesystem": "^8 || ^9 || ^10", - "illuminate/support": "^8 || ^9 || ^10", + "illuminate/console": "^9 || ^10", + "illuminate/filesystem": "^9 || ^10", + "illuminate/support": "^9 || ^10", "nikic/php-parser": "^4.18 || ^5", - "php": "^7.3 || ^8.0", + "php": "^8.0", "phpdocumentor/type-resolver": "^1.1.0" }, "require-dev": { "ext-pdo_sqlite": "*", - "friendsofphp/php-cs-fixer": "^2", - "illuminate/config": "^8 || ^9 || ^10", - "illuminate/view": "^8 || ^9 || ^10", + "friendsofphp/php-cs-fixer": "^3", + "illuminate/config": "^9 || ^10", + "illuminate/view": "^9 || ^10", "mockery/mockery": "^1.4", - "orchestra/testbench": "^6 || ^7 || ^8", - "phpunit/phpunit": "^8.5 || ^9", - "spatie/phpunit-snapshot-assertions": "^3 || ^4", + "orchestra/testbench": "^7 || ^8", + "phpunit/phpunit": "^9", + "spatie/phpunit-snapshot-assertions": "^4", "vimeo/psalm": "^5.4" }, "suggest": { @@ -6277,7 +6277,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.14-dev" + "dev-master": "2.15-dev" }, "laravel": { "providers": [ @@ -6314,7 +6314,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.14.0" + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.15.1" }, "funding": [ { @@ -6326,7 +6326,7 @@ "type": "github" } ], - "time": "2024-02-05T08:16:36+00:00" + "time": "2024-02-15T14:23:20+00:00" }, { "name": "barryvdh/reflection-docblock", diff --git a/dist/config/database.php b/dist/config/database.php index 137ad18..ee1d057 100644 --- a/dist/config/database.php +++ b/dist/config/database.php @@ -17,6 +17,9 @@ 'default' => env('DB_CONNECTION', 'mysql'), + // Enables Query logging for logs the AccesLogger writes. + 'enable-query-logging' => env('DB_ENABLE_QUERY_LOG', false), + /* |-------------------------------------------------------------------------- | Database Connections diff --git a/dist/database/migrations/2024_07_31_185616_create_archived_player_progressions_table.php b/dist/database/migrations/2024_07_31_185616_create_archived_player_progressions_table.php new file mode 100644 index 0000000..40410f2 --- /dev/null +++ b/dist/database/migrations/2024_07_31_185616_create_archived_player_progressions_table.php @@ -0,0 +1,45 @@ +id(); + + // without constraints because we want it to persist when the related row gets deleted. + $table->foreignUuid('user_id'); + $table->foreignUuid('archived_game_id'); + + $table->boolean('has_quit'); + $table->enum('played_character', array_column(Characters::cases(), 'value')); + $table->enum('character_state', array_column(CharacterState::cases(), 'value')); + + $table->integer('gained_experience'); + $table->json('experience_events'); + + $table->integer('gained_currency_a'); + $table->integer('gained_currency_b'); + $table->integer('gained_currency_c'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('archived_player_progressions'); + } +}; diff --git a/dist/database/migrations/2024_07_31_185625_create_archived_games_table.php b/dist/database/migrations/2024_07_31_185625_create_archived_games_table.php new file mode 100644 index 0000000..085305a --- /dev/null +++ b/dist/database/migrations/2024_07_31_185625_create_archived_games_table.php @@ -0,0 +1,30 @@ +uuid('id')->primary(); + + $table->enum('dominant_faction',array_column(\App\Enums\Game\Faction::cases(), 'value')); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('archived_games'); + } +}; diff --git a/dist/routes/deathgardenApi.php b/dist/routes/deathgardenApi.php index 04fffaa..265e1ff 100644 --- a/dist/routes/deathgardenApi.php +++ b/dist/routes/deathgardenApi.php @@ -81,7 +81,7 @@ Route::put('match/{matchId}/Close', [MatchmakingController::class, 'close']); Route::put('match/{matchId}/Kill', [MatchmakingController::class, 'kill']); Route::put('match/{matchId}/Quit', [MatchmakingController::class, 'quit']); - Route::delete('match/{matchId}/user/{userId}', [MatchmakingController::class, 'deleteUserFromMatch']); + Route::delete('match/{match}/user/{user}', [MatchmakingController::class, 'deleteUserFromMatch']); Route::post('feedback', [ModerationController::class, 'playerReport']); });