Added Game and Player match history. Also adjusted how players get deleted from a match.

This commit is contained in:
Vari 2024-07-31 21:36:51 +02:00
parent 2887872316
commit 179adc7518
14 changed files with 289 additions and 54 deletions

1
dist/.env.example vendored
View File

@ -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
View File

@ -18,4 +18,5 @@ yarn-error.log
/.fleet
_ide_helper.php
_ide_helper_models.php
.phpstorm.meta.php
/public/game-files

View File

@ -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,33 +211,68 @@ public function playerEndOfMatch(PlayerEndOfMatchRequest $request)
if($user === null)
return response('User not found.', 404);
$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))
return;
$playerData = $user->playerData();
$characterData = $playerData->characterDataForCharacter($request->characterGroup->getCharacter());
$experienceSum = 0;
foreach ($request->experienceEvents as $experienceEvent) {
$characterData->addExperience($experienceEvent['amount']);
$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':
$playerData->currency_a += $earnedCurrency['amount'];
$gainedCurrencyA += $earnedCurrency['amount'];
break;
case 'CurrencyB':
$playerData->currency_b += $earnedCurrency['amount'];
$gainedCurrencyB += $earnedCurrency['amount'];
break;
case 'CurrencyC':
$playerData->currency_c += $earnedCurrency['amount'];
$gainedCurrencyC += $earnedCurrency['amount'];
}
}
$playerData->currency_a += $gainedCurrencyA;
$playerData->currency_b += $gainedCurrencyB;
$playerData->currency_c += $gainedCurrencyC;
++$playerData->readout_version;
$playerData->save();
$this->removeUserFromGame($user, $game);
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);
}
// We dont really know what the game wants except for a json object called "player".
return json_encode(['player' => []], JSON_FORCE_OBJECT);

View File

@ -24,7 +24,9 @@ public function handle(Request $request, Closure $next): Response
if (!Str::contains($request->userAgent(), 'TheExit'))
return $next($request);
if(config('database.enable-query-logging'))
DB::enableQueryLog();
$response = $next($request);
$log = new stdClass();

View File

@ -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');

View 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();
}
}

View 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();
}
}

View File

@ -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
View File

@ -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",

View File

@ -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

View 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');
}
};

View 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');
}
};

View File

@ -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']);
});