Added updateMetadataGroup:

- Moved initOrGetGroup and updateMetadataGroup into own MetadataController.php becauseits really big and cluttered the PlayerController.php

- Added Challenges and pciked challenges for the characters

- Added that when the game sends us an update for the Character metadata, it automatically creates unknown challenges in the database and sets the picked challenges for this characterData
This commit is contained in:
Vari 2024-02-26 11:23:32 +01:00
parent 43d584a5a9
commit 1360bb7d44
15 changed files with 435 additions and 37 deletions

View File

@ -0,0 +1,16 @@
<?php
namespace App\Enums\Api;
enum UpdateMetadataReason: string
{
case CharacterDirty = 'Character dirty';
case OnCloseLoadout = 'OnCloseLoadoout';
case CharacterOverrideEvent = 'OnRequestCharacterOverrideEvent';
case SetLastPlayedFaction = 'SetLastPlayedFaction';
case SetLastPlayerCharacterId = 'SetLastPlayedCharacterId';
}

View File

@ -19,6 +19,17 @@ public function getTag()
return 'Hunter.'.$this->value;
}
public static function tryFromTag(string $tag): Hunter|null
{
return match ($tag) {
'Hunter.Stalker' => Hunter::Stalker,
'Hunter.Poacher' => Hunter::Poacher,
'Hunter.Inquisitor' => Hunter::Inquisitor,
'Hunter.Mass' => Hunter::Mass,
default => null,
};
}
public function getGroupType(): ItemGroupType
{
return match ($this) {

31
dist/app/Enums/Game/MetadataGroup.php vendored Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Enums\Game;
enum MetadataGroup: string
{
case Sawbones = 'RunnerGroupA';
case Inked = 'RunnerGroupB';
case Ghost = 'RunnerGroupC';
case Switch = 'RunnerGroupD';
case Fog = 'RunnerGroupE';
case Dash = 'RunnerGroupF';
case Stalker = 'HunterGroupA';
case Inquisitor = 'HunterGroupB';
case Poacher = 'HunterGroupC';
case Veteran = 'HunterGroupD';
case Profile = 'ProfileMetadata';
case Player = 'PlayerMetadata';
case Runner = 'RunnerMetadata';
case Hunter = 'HunterMetadata';
public function getCharacter(): Characters|null
{
return ItemGroupType::tryFrom($this->value)->getCharacter();
}
}

View File

@ -24,6 +24,19 @@ public function getTag(): string
return 'Runner.'.$this->value;
}
public static function tryFromTag(string $tag): Runner|null
{
return match ($tag) {
'Runner.Smoke' => Runner::Smoke,
'Runner.Ink' => Runner::Ink,
'Runner.Ghost' => Runner::Ghost,
'Runner.Sawbones' => Runner::Sawbones,
'Runner.Switch' => Runner::Switch,
'Runner.Dash' => Runner::Dash,
default => null,
};
}
public function getGroupType(): ItemGroupType
{
return match ($this) {

View File

@ -26,11 +26,14 @@ public static function convertFromUuidToHexCollection(Collection|array $collecti
* @param Collection|array $collection
* @return Collection|UuidInterface[]
*/
public static function convertFromHexToUuidCollecton(Collection|array $collection): Collection {
public static function convertFromHexToUuidCollecton(Collection|array $collection, bool $toString = false): Collection {
$new = [];
foreach ($collection as $id) {
$new[] = Uuid::fromHexadecimal(new Hexadecimal($id));
if($toString)
$new[] = Uuid::fromHexadecimal(new Hexadecimal($id))->toString();
else
$new[] = Uuid::fromHexadecimal(new Hexadecimal($id));
}
return collect($new);

View File

@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers\Api\Player;
use App\Enums\Api\UpdateMetadataReason;
use App\Enums\Game\Faction;
use App\Enums\Game\Hunter;
use App\Enums\Game\ItemGroupType;
use App\Enums\Game\MetadataGroup;
use App\Enums\Game\Runner;
use App\Helper\Uuid\UuidHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Player\InitOrGetGroupsRequest;
use App\Http\Requests\Api\Player\UpdateMetadataGroupRequest;
use App\Http\Responses\Api\Player\InitOrGetGroupsResponse;
use App\Http\Responses\Api\Player\UpdateMetadataResponse;
use App\Models\Game\Challenge;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Uuid;
class MetadataController extends Controller
{
public function initOrGetGroups(InitOrGetGroupsRequest $request)
{
$response = new InitOrGetGroupsResponse();
$user = Auth::user();
foreach (ItemGroupType::cases() as $group) {
if ($group == ItemGroupType::None)
continue;
if (!$request->skipProgressionGroups) {
if ($group->getCharacter() !== false)
$response->addCharacterProgressionGroup($group->getCharacter(), $user);
else
$response->addFactionProgression($group, $user);
}
if (!$request->skipMetadataGroups) {
if ($group->getCharacter() !== false)
$response->addCharacterMetadataGroup($group->getCharacter(), $user);
}
}
$response->addGeneralMetadata($user);
return json_encode($response);
}
public function updateMetadataGroup(UpdateMetadataGroupRequest $request)
{
switch ($request->group) {
// I dont know if the game ever tries to update the profile metadata since it's just the number of all
// experience in your account. Well se it in the logs if it ever tries.
case MetadataGroup::Profile:
throw new \Exception('To be implemented');
case MetadataGroup::Player:
return $this->handleUpdatePlayerMetadata($request);
case MetadataGroup::Hunter:
case MetadataGroup::Runner:
return $this->handleFactionMetadata($request);
default:
return $this->handleUpdateCharacterMetadata($request);
}
}
private function handleUpdateCharacterMetadata(UpdateMetadataGroupRequest &$request): string|bool
{
$user = Auth::user();
$characterData = $user->playerData()->characterDataForCharacter($request->group->getCharacter());
$convertedIds = UuidHelper::convertFromHexToUuidCollecton($request->metadata['equipment'], true);
$characterData->equipment()->sync($convertedIds);
$convertedIds = UuidHelper::convertFromHexToUuidCollecton($request->metadata['equippedPerks'], true);
$characterData->equippedPerks()->sync($convertedIds);
$convertedIds = UuidHelper::convertFromHexToUuidCollecton($request->metadata['equippedWeapons'], true);
$characterData->equippedWeapons()->sync($convertedIds);
$convertedIds = UuidHelper::convertFromHexToUuidCollecton($request->metadata['equippedBonuses'], true);
$characterData->equippedBonuses()->sync($convertedIds);
$characterData->pickedChallenges()->detach();
foreach ($request->metadata['pickedChallenges'] as $picked) {
$itemId = Uuid::fromHexadecimal(new Hexadecimal($picked['itemId']));
foreach ($picked['list'] as $challenge) {
$challengeId = Uuid::fromHexadecimal(new Hexadecimal($challenge['challengeId']));
$completionValue = $challenge['challengeCompletionValue'];
$assetPath = $challenge['challengeAsset'];
$foundChallenge = Challenge::firstOrCreate(
[
'id' => $challengeId->toString(),
],
[
'id' => $challengeId->toString(),
'completion_value' => $completionValue,
'asset_path' => $assetPath,
]
);
$characterData->pickedChallenges()->attach($foundChallenge->id, [
'catalog_item_id' => $itemId->toString(),
]);
}
}
$response = new UpdateMetadataResponse(
$user->id,
MetadataGroup::Player,
$characterData->readout_version
);
return json_encode($response);
}
private function handleUpdatePlayerMetadata(UpdateMetadataGroupRequest &$request): string|bool
{
$user = Auth::user();
$playerData = $user->playerData();
$playerData->last_faction = Faction::tryFrom($request->metadata['lastPlayedFaction']);
$playerData->last_runner = Runner::tryFromTag($request->metadata['lastPlayedRunnerId']['tagName']);
$playerData->last_hunter = Hunter::tryFromTag($request->metadata['lastPlayedHunterId']['tagName']);
$playerData->has_played_tutorial = $request->metadata['shouldPlayWithoutContextualHelp'];
$playerData->has_played_dg_1 = $request->metadata['hasPlayedDeathGarden1'];
++$playerData->readout_version;
$playerData->save();
$response = new UpdateMetadataResponse(
$user->id,
MetadataGroup::Player,
$playerData->readout_version
);
return json_encode($response);
}
private function handleFactionMetadata(UpdateMetadataGroupRequest &$request): string|bool
{
throw new \Exception('Handle Update Faction Metadata not implemented yet');
return false;
}
}

View File

@ -2,13 +2,9 @@
namespace App\Http\Controllers\Api\Player;
use App\Enums\Game\ItemGroupType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Player\GetInventoryRequest;
use App\Http\Requests\Api\Player\InitOrGetGroupsRequest;
use App\Http\Responses\Api\Player\GetBanStatusResponse;
use App\Http\Responses\Api\Player\InitOrGetGroupsResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Ramsey\Uuid\Uuid;
@ -24,33 +20,6 @@ public function getBanStatus()
return json_encode(new GetBanStatusResponse(true, $user->ban));
}
public function initOrGetGroups(InitOrGetGroupsRequest $request)
{
$response = new InitOrGetGroupsResponse();
$user = Auth::user();
foreach (ItemGroupType::cases() as $group) {
if ($group == ItemGroupType::None)
continue;
if (!$request->skipProgressionGroups) {
if ($group->getCharacter() !== false)
$response->addCharacterProgressionGroup($group->getCharacter(), $user);
else
$response->addFactionProgression($group, $user);
}
if (!$request->skipMetadataGroups) {
if($group->getCharacter() !== false)
$response->addCharacterMetadataGroup($group->getCharacter(), $user);
}
}
$response->addGeneralMetadata($user);
return json_encode($response);
}
public function getInventory(GetInventoryRequest $request)
{
$user = Auth::user();

View File

@ -0,0 +1,59 @@
<?php
namespace App\Http\Requests\Api\Player;
use App\Enums\Api\UpdateMetadataReason;
use App\Enums\Game\MetadataGroup;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class UpdateMetadataGroupRequest extends FormRequest
{
public MetadataGroup $group;
public ?UpdateMetadataReason $reason;
public int $version;
public array $metadata;
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'data.reason' => 'required',
'data.version' => 'required|int',
'data.metadata' => 'required|array',
'data.objectId' => ['required', Rule::enum(MetadataGroup::class)]
];
}
protected function passedValidation()
{
$this->group = MetadataGroup::tryFrom($this->input('data.objectId'));
$this->version = $this->input('data.version');
$this->metadata = $this->input('data.metadata');
$this->reason = UpdateMetadataReason::tryFrom($this->input('data.reason'));
if ($this->reason === null) {
$message = 'Update Metadata with unknown reason: '.$this->input('data.reason');
Log::channel('dg_requests_errors')->warning($message."\n".$this->input('data'));
}
}
}

View File

@ -6,10 +6,12 @@
use App\Enums\Game\Characters;
use App\Enums\Game\ItemGroupType;
use App\Helper\Uuid\UuidHelper;
use App\Models\Game\Challenge;
use App\Models\Game\CharacterData;
use App\Models\User\PlayerData;
use App\Models\User\User;
use JsonSerializable;
use Ramsey\Uuid\Uuid;
class InitOrGetGroupsResponse
{
@ -79,6 +81,43 @@ public function addCharacterMetadataGroup(Characters $character, User $user): vo
$newGroup->equippedPowers = $itemConfigClass::getDefaultPowers();
$newGroup->prestigeLevel = $characterData->prestige_level;
$pickedChallenges = $characterData->pickedChallenges;
$resultChallenges = collect();
foreach ($pickedChallenges as $picked) {
// Because a pciked challenge for a specific item can have multiple challenges,
// we look if there is already an entry with the item id.
$foundKey = $resultChallenges->search(function ($value, $key) use ($picked) {
return $value['itemId'] === Uuid::fromString($picked->pivot->catalog_item_id)->getHex()->toString();
});
// if there is not, we just create it entirely and fill the list with the current challenge
if($foundKey === false) {
$newEntry = [
'itemId' => $picked->pivot->catalog_item_id,
'list' => [
[
'challengeId' => $picked->id,
'challengeCompletionValue' => $picked->completion_value,
'challengeAsset' => $picked->asset_path,
]
]
];
$resultChallenges->add($newEntry);
}
// and if there is we just add the challenge to the list of the item.
else {
$resultChallenges[$foundKey]['list'] = [
'challengeId' => $picked->id,
'challengeCompletionValue' => $picked->completion_value,
'challengeAsset' => $picked->asset_path,
];
}
}
$newGroup->pickedChallenges = $resultChallenges->toArray();
$newGroup->version = $characterData->readout_version;
$newGroup->objectId = $character->getgroup();
@ -137,7 +176,7 @@ class MetadataGroup implements JsonSerializable
public array $equippedBonuses;
public array $pickedChallenges = [];
public array $pickedChallenges;
public array $equippedPowers;

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Responses\Api\Player;
class UpdateMetadataResponse
{
public int $schemaVarsion = 1;
public array $data = [];
public function __construct(
public string $userId,
public \App\Enums\Game\MetadataGroup $objectId,
public int $version,
public string $stateName = 'MetadataGroups',
)
{
}
}

21
dist/app/Models/Game/Challenge.php vendored Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Models\Game;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @mixin IdeHelperChallenge
*/
class Challenge extends Model
{
use HasFactory, HasUuids;
protected $fillable = [
'id',
'completion_value',
'asset_path',
];
}

View File

@ -5,11 +5,10 @@
use App\Classes\Character\CharacterItemConfig;
use App\Enums\Game\Characters;
use App\Helper\Uuid\UuidHelper;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Ramsey\Uuid\Uuid;
/**
@ -52,6 +51,20 @@ public function equippedWeapons(): BelongsToMany
return $this->belongsToMany(CatalogItem::class,'character_data_equipped_weapons');
}
public function pickedChallenges(): BelongsToMany
{
return $this->belongsToMany(Challenge::class, 'character_data_picked_challenge')
->withPivot('catalog_item_id');
}
public function getPicketChallengeForItem(Uuid $uuid): Collection
{
return $this->pickedChallenges()
->where('catalog_item_id', '=', $uuid->toString())
->withPivot('catalog_item_id')
->get();
}
public static function getExperienceForLevel(int $level): int
{
--$level;

View File

@ -86,6 +86,20 @@
'replace_placeholders' => true,
],
'challenge' => [
'driver' => 'single',
'path' => storage_path('logs/challenges/challenge.log'),
'level' => 'info',
'replace_placeholders' => true,
],
'challenge_new' => [
'driver' => 'single',
'path' => storage_path('logs/challenges/challenge_new.log'),
'level' => 'info',
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),

View File

@ -0,0 +1,38 @@
<?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('challenges', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->unsignedInteger('completion_value');
$table->string('asset_path');
$table->timestamps();
});
Schema::create('character_data_picked_challenge', function (Blueprint $table) {
$table->id();
$table->foreignId('character_data_id')->constrained('character_data')->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignUuid('catalog_item_id')->constrained();
$table->foreignUuid('challenge_id')->constrained();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('character_data_picked_challenge');
Schema::dropIfExists('challenges');
}
};

View File

@ -3,6 +3,7 @@
use App\Http\Controllers\Api\Auth\SteamAuthController;
use App\Http\Controllers\Api\Eula\EulaConsentController;
use App\Http\Controllers\Api\Player\CurrencyController;
use App\Http\Controllers\Api\Player\MetadataController;
use App\Http\Controllers\Api\Player\ModifierCenterController;
use App\Http\Controllers\Api\Player\PlayerController;
use App\Http\Controllers\Api\VersionController;
@ -38,7 +39,8 @@
Route::get('players/ban/status', [PlayerController::class, 'getBanStatus']);
Route::get('modifierCenter/modifiers/me', [ModifierCenterController::class, 'modifiersMe']);
Route::post('extensions/progression/initOrGetGroups', [PlayerController::class, 'initOrGetGroups']);
Route::post('extensions/progression/initOrGetGroups', [MetadataController::class, 'initOrGetGroups']);
Route::post('extensions/progression/updateMetadataGroup', [MetadataController::class, 'updateMetadataGroup']);
Route::get('wallet/currencies', [CurrencyController::class, 'getCurrencies']);