mirror of
https://github.com/Deathgarden-Rebirth/Deathgarden_Rebirth-Rewrite.git
synced 2026-03-21 18:04:46 -05:00
Added Game and Player match history. Also adjusted how players get deleted from a match.
This commit is contained in:
parent
2887872316
commit
179adc7518
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,3 @@
|
|||
.idea
|
||||
.vscode
|
||||
.fleet
|
||||
.fleet
|
||||
|
|
|
|||
1
dist/.env.example
vendored
1
dist/.env.example
vendored
|
|
@ -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
|
||||
|
|
|
|||
1
dist/.gitignore
vendored
1
dist/.gitignore
vendored
|
|
@ -18,4 +18,5 @@ yarn-error.log
|
|||
/.fleet
|
||||
_ide_helper.php
|
||||
_ide_helper_models.php
|
||||
.phpstorm.meta.php
|
||||
/public/game-files
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
4
dist/app/Http/Middleware/AccessLogger.php
vendored
4
dist/app/Http/Middleware/AccessLogger.php
vendored
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
28
dist/app/Models/Admin/Archive/ArchivedGame.php
vendored
Normal file
28
dist/app/Models/Admin/Archive/ArchivedGame.php
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Admin\Archive;
|
||||
|
||||
use App\Enums\Game\Faction;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @mixin IdeHelperArchivedGame
|
||||
*/
|
||||
class ArchivedGame extends Model
|
||||
{
|
||||
protected $casts = [
|
||||
'dominant_faction' => Faction::class,
|
||||
];
|
||||
|
||||
public function archivedPlayerProgressions(): HasMany
|
||||
{
|
||||
return $this->hasMany(ArchivedPlayerProgression::class);
|
||||
}
|
||||
|
||||
public static function archivedGameExists(string $matchId) {
|
||||
return ArchivedGame::where('id', '=', $matchId)->exists();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
68
dist/app/Models/Admin/Archive/ArchivedPlayerProgression.php
vendored
Normal file
68
dist/app/Models/Admin/Archive/ArchivedPlayerProgression.php
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Admin\Archive;
|
||||
|
||||
use App\Enums\Game\Characters;
|
||||
use App\Enums\Game\CharacterState;
|
||||
use App\Models\Game\Matchmaking\Game;
|
||||
use App\Models\User\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @mixin IdeHelperArchivedPlayerProgression
|
||||
*/
|
||||
class ArchivedPlayerProgression extends Model
|
||||
{
|
||||
protected $casts = [
|
||||
'played_character' => 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();
|
||||
}
|
||||
}
|
||||
15
dist/app/Models/Game/Matchmaking/Game.php
vendored
15
dist/app/Models/Game/Matchmaking/Game.php
vendored
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
36
dist/composer.lock
generated
vendored
36
dist/composer.lock
generated
vendored
|
|
@ -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",
|
||||
|
|
|
|||
3
dist/config/database.php
vendored
3
dist/config/database.php
vendored
|
|
@ -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
|
||||
|
|
|
|||
45
dist/database/migrations/2024_07_31_185616_create_archived_player_progressions_table.php
vendored
Normal file
45
dist/database/migrations/2024_07_31_185616_create_archived_player_progressions_table.php
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Game\Characters;
|
||||
use App\Enums\Game\CharacterState;
|
||||
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::create('archived_player_progressions', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
30
dist/database/migrations/2024_07_31_185625_create_archived_games_table.php
vendored
Normal file
30
dist/database/migrations/2024_07_31_185625_create_archived_games_table.php
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?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::create('archived_games', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
2
dist/routes/deathgardenApi.php
vendored
2
dist/routes/deathgardenApi.php
vendored
|
|
@ -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']);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user