From af8f21e5596f55b7a26b970ad42098772a94e20f Mon Sep 17 00:00:00 2001 From: Vari Date: Mon, 16 Dec 2024 21:21:33 +0100 Subject: [PATCH 1/8] Work on timed challenges. --- dist/app/Enums/Game/ChallengeType.php | 11 ++++ .../Api/Player/ChallengeController.php | 54 +++++++++++++++++- .../Player/Challenges/GetChallengesEntry.php | 56 ++++++++++++++++++ dist/app/Models/Game/TimedChallenge.php | 57 +++++++++++++++++++ dist/app/Models/User/PlayerData.php | 5 ++ ...3_201112_create_timed_challenges_table.php | 36 ++++++++++++ ...reate_player_data_timed_chalenge_table.php | 33 +++++++++++ 7 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 dist/app/Enums/Game/ChallengeType.php create mode 100644 dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php create mode 100644 dist/app/Models/Game/TimedChallenge.php create mode 100644 dist/database/migrations/2024_12_03_201112_create_timed_challenges_table.php create mode 100644 dist/database/migrations/2024_12_03_203904_create_player_data_timed_chalenge_table.php diff --git a/dist/app/Enums/Game/ChallengeType.php b/dist/app/Enums/Game/ChallengeType.php new file mode 100644 index 0000000..1757517 --- /dev/null +++ b/dist/app/Enums/Game/ChallengeType.php @@ -0,0 +1,11 @@ + []]); + $user = Auth::user(); + $timedChallenges = TimedChallenge::where('start_time', '<', Carbon::now()) + ->where('end_time', '>', Carbon::now())->get(); + + $challenges = []; + $timedChallenges->each(function (TimedChallenge $challenge) use (&$challenges, $user) { + $challenges[] = new GetChallengesEntry( + $challenge->id, + $challenge->start_time, + $challenge->end_time, + $challenge->type, + $challenge->completion_value, + $challenge->faction, + $challenge->blueprint_path, + $challenge->rewards, + $challenge->hasPlayerClaimed($user->playerData()->id), + ); + }); + + return Response::json(['challenges' => $challenges]); } public function getProgressionBatch(GetChallengeProgressionBatchRequest $request) @@ -36,12 +61,23 @@ public function getProgressionBatch(GetChallengeProgressionBatchRequest $request $user = User::findOrFail($request->userId); $playerData = $user->playerData(); + $timedChallengeIds = []; + + foreach ($request->challengeIds as $index => $id) { + if(Str::startsWith($id, GetChallengesEntry::ID_PREFIX)) { + $timedChallengeIds[] = Str::remove(GetChallengesEntry::ID_PREFIX, $id); + unset($request->challengeIds[$index]); + } + } + $challengeIdsToCheck = UuidHelper::convertFromHexToUuidCollecton($request->challengeIds, true); /** @var Challenge[]|Collection $challengesToCheck */ $challengesToCheck = Challenge::findMany($challengeIdsToCheck); /** @var PickedChallenge[]|Collection $pickedChallengesToCheck */ $pickedChallengesToCheck = PickedChallenge::findMany($challengeIdsToCheck); + /** @var TimedChallenge[]|Collection $timedChallengesToCheck */ + $timedChallengesToCheck = TimedChallenge::findMany($timedChallengeIds); $response = new ChallengeProgressionBatchResponse(); @@ -57,6 +93,20 @@ public function getProgressionBatch(GetChallengeProgressionBatchRequest $request $response->progressionBatch[] = $newEntry; } + foreach ($timedChallengesToCheck as $challenge) { + $progress = $challenge->getProgressForPlayer($playerData->id); + + $entry = new ChallengeProgressionEntry( + GetChallengesEntry::ID_PREFIX . $challenge->id, + $challenge->progress >= $challenge->completion_value, + $progress, + ); + + $entry->rewardsClaimed = $challenge->rewards; + + $response->progressionBatch[] = $entry; + } + foreach ($pickedChallengesToCheck as $challenge) { $response->progressionBatch[] = new ChallengeProgressionEntry( Uuid::fromString($challenge->id)->getHex()->toString(), @@ -65,6 +115,8 @@ public function getProgressionBatch(GetChallengeProgressionBatchRequest $request ); } + foreach ($challengesToCheck as $challenge) {} + return json_encode($response); } diff --git a/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php b/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php new file mode 100644 index 0000000..6416927 --- /dev/null +++ b/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php @@ -0,0 +1,56 @@ +id; + } + + public function jsonSerialize(): mixed + { + $json = [ + 'lifetime' => + [ + 'creationTime' => $this->startTime->toJSON(), + 'expirationTime' => $this->endTime->toJSON(), + ], + 'challengeType' => $this->type, + 'challengeId' => $this->getChallengeId(), + 'challengeCompletionValue' => $this->completionValue, + 'faction' => $this->faction, + 'challengeBlueprint' => $this->challengeBlueprint, + ]; + + $rewards = []; + foreach ($this->rewards as &$reward) { + $reward['claimed'] = $this->claimed; + $reward['weight'] = 100; + $rewards[] = $reward; + } + $json['rewards'] = $rewards; + + return $json; + } +} \ No newline at end of file diff --git a/dist/app/Models/Game/TimedChallenge.php b/dist/app/Models/Game/TimedChallenge.php new file mode 100644 index 0000000..d894f32 --- /dev/null +++ b/dist/app/Models/Game/TimedChallenge.php @@ -0,0 +1,57 @@ + ChallengeType::class, + 'faction' => Faction::class, + 'start_time' => 'datetime', + 'end_time' => 'datetime', + 'rewards' => 'array', + ]; + + public function getProgressForPlayer(int $playerDataId): int + { + $foundProgress = $this->playerData()->where('id', '=', $playerDataId)->first(); + + if ($foundProgress !== null) + return $foundProgress->pivot->progress; + + // We create challange relation and just return 0 since you cannot have progress for the challenge we just linked. + $this->playerData()->attach($playerDataId); + return 0; + } + + public function hasPlayerClaimed(int $playerDataId): bool { + $foundPlayerData = $this->playerData()->where('id', '=', $playerDataId)->first(); + + if ($foundPlayerData !== null) + return $foundPlayerData->pivot->claimed; + + return false; + } + + public function playerData(): BelongsToMany + { + return $this->belongsToMany(PlayerData::class)->withPivot(['progress', 'claimed']); + } + + public static function currentChallenges(): Eloquent|Builder { + return static::where('start_time', '>', Carbon::now()) + ->where('end_time', '<', Carbon::now()); + } +} diff --git a/dist/app/Models/User/PlayerData.php b/dist/app/Models/User/PlayerData.php index fc5af26..2afa6b9 100644 --- a/dist/app/Models/User/PlayerData.php +++ b/dist/app/Models/User/PlayerData.php @@ -12,6 +12,7 @@ use App\Models\Game\Challenge; use App\Models\Game\CharacterData; use App\Models\Game\QuitterState; +use App\Models\Game\TimedChallenge; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -143,6 +144,10 @@ public function challenges(): BelongsToMany return $this->belongsToMany(Challenge::class)->withPivot(['progress']); } + public function timedChallenges(): BelongsToMany { + return $this->belongsToMany(TimedChallenge::class)->withPivot(['progress', 'claimed']); + } + public function quitterState(): HasOne { return $this->hasOne(QuitterState::class); diff --git a/dist/database/migrations/2024_12_03_201112_create_timed_challenges_table.php b/dist/database/migrations/2024_12_03_201112_create_timed_challenges_table.php new file mode 100644 index 0000000..8bd2fef --- /dev/null +++ b/dist/database/migrations/2024_12_03_201112_create_timed_challenges_table.php @@ -0,0 +1,36 @@ +id(); + $table->enum('type', array_column(ChallengeType::cases(), 'value')); + $table->string('blueprint_path'); + $table->enum('faction', array_column(Faction::cases(), 'value')); + $table->integer('completion_value'); + $table->datetime('start_time'); + $table->datetime('end_time'); + $table->json('rewards')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('timed_challenges'); + } +}; diff --git a/dist/database/migrations/2024_12_03_203904_create_player_data_timed_chalenge_table.php b/dist/database/migrations/2024_12_03_203904_create_player_data_timed_chalenge_table.php new file mode 100644 index 0000000..792f6a1 --- /dev/null +++ b/dist/database/migrations/2024_12_03_203904_create_player_data_timed_chalenge_table.php @@ -0,0 +1,33 @@ +foreignId('timed_challenge_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); + $table->foreignId('player_data_id')->constrained('player_data')->cascadeOnDelete()->cascadeOnUpdate(); + $table->unsignedInteger('progress')->default(0); + $table->boolean('claimed')->default(false); + + $table->timestamps(); + + $table->unique(['timed_challenge_id', 'player_data_id'], 'unique_primary'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('player_data_timed_challenge'); + } +}; From 95edd6382b0f3784d4122e32b43c3173955234f3 Mon Sep 17 00:00:00 2001 From: Vari Date: Mon, 16 Dec 2024 21:52:50 +0100 Subject: [PATCH 2/8] Added Timed Challenge progression update + fix timestamp format. --- .../Api/Player/ChallengeController.php | 19 +++++++++++++++---- .../Player/Challenges/GetChallengesEntry.php | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/dist/app/Http/Controllers/Api/Player/ChallengeController.php b/dist/app/Http/Controllers/Api/Player/ChallengeController.php index 2f01250..928318b 100644 --- a/dist/app/Http/Controllers/Api/Player/ChallengeController.php +++ b/dist/app/Http/Controllers/Api/Player/ChallengeController.php @@ -151,8 +151,13 @@ public function executeChallengeProgressionBatch(ExecuteChallengeProgressionBatc protected function addProgressToChallenge(string $challengeId, int $newProgress, User $user): void { - /** @var Challenge|null $foundChallenge */ - $foundChallenge = $user->playerData()->challenges()->find($challengeId); + if(Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) { + $foundChallenge = Challenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId)); + } + else { + /** @var Challenge|null $foundChallenge */ + $foundChallenge = $user->playerData()->challenges()->find($challengeId); + } if($foundChallenge !== null) { $foundChallenge->playerData()->updateExistingPivot($user->playerData()->id, [ @@ -171,8 +176,14 @@ protected function addProgressToChallenge(string $challengeId, int $newProgress, protected function setChallengeAsCompleted(string $challengeId, User $user) { - /** @var Challenge|null $foundChallenge */ - $foundChallenge = $user->playerData()->challenges()->find($challengeId); + // If the Challenge id starts with the Prefix, handle it as a timed challenge. + if(Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) { + $foundChallenge = Challenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId)); + } + else { + /** @var Challenge|null $foundChallenge */ + $foundChallenge = $user->playerData()->challenges()->find($challengeId); + } if($foundChallenge !== null) { $foundChallenge->playerData()->updateExistingPivot($user->playerData()->id, [ diff --git a/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php b/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php index 6416927..99301f8 100644 --- a/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php +++ b/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php @@ -33,8 +33,8 @@ public function jsonSerialize(): mixed $json = [ 'lifetime' => [ - 'creationTime' => $this->startTime->toJSON(), - 'expirationTime' => $this->endTime->toJSON(), + 'creationTime' => $this->startTime->toIso8601ZuluString(), + 'expirationTime' => $this->endTime->toIso8601ZuluString(), ], 'challengeType' => $this->type, 'challengeId' => $this->getChallengeId(), From 68f157cb28dfaa307002351b93521e1a35366d05 Mon Sep 17 00:00:00 2001 From: Vari Date: Mon, 16 Dec 2024 22:04:38 +0100 Subject: [PATCH 3/8] Fix executeChallengeProgressionBatch id parsing with timed challenges --- .../Controllers/Api/Player/ChallengeController.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dist/app/Http/Controllers/Api/Player/ChallengeController.php b/dist/app/Http/Controllers/Api/Player/ChallengeController.php index 928318b..504fcca 100644 --- a/dist/app/Http/Controllers/Api/Player/ChallengeController.php +++ b/dist/app/Http/Controllers/Api/Player/ChallengeController.php @@ -135,7 +135,11 @@ public function executeChallengeProgressionBatch(ExecuteChallengeProgressionBatc $processedChallenges = []; foreach ($request->operations as $operation) { - $challengeId = Uuid::fromString($operation['challengeId'])->toString(); + $challengeId = $operation['challengeId']; + + if(!Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) { + $challengeId = Uuid::fromString($operation['challengeId'])->toString(); + } // Skip if we already processed this challenge because the completed ones are always first in the array. if(in_array($challengeId, $processedChallenges)) @@ -152,7 +156,7 @@ public function executeChallengeProgressionBatch(ExecuteChallengeProgressionBatc protected function addProgressToChallenge(string $challengeId, int $newProgress, User $user): void { if(Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) { - $foundChallenge = Challenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId)); + $foundChallenge = TimedChallenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId)); } else { /** @var Challenge|null $foundChallenge */ @@ -178,7 +182,7 @@ protected function setChallengeAsCompleted(string $challengeId, User $user) { // If the Challenge id starts with the Prefix, handle it as a timed challenge. if(Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) { - $foundChallenge = Challenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId)); + $foundChallenge = TimedChallenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId)); } else { /** @var Challenge|null $foundChallenge */ From 3e35dc2c7fd512e29a84ed24ee85e33890e5aff4 Mon Sep 17 00:00:00 2001 From: Vari Date: Tue, 17 Dec 2024 10:58:33 +0100 Subject: [PATCH 4/8] Added rewards logic to execute challenge progression batch for timed challenges. --- .../Api/Player/ChallengeController.php | 56 ++++++++++++++++++- dist/app/Models/Game/TimedChallenge.php | 21 +++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/dist/app/Http/Controllers/Api/Player/ChallengeController.php b/dist/app/Http/Controllers/Api/Player/ChallengeController.php index 504fcca..eade0ab 100644 --- a/dist/app/Http/Controllers/Api/Player/ChallengeController.php +++ b/dist/app/Http/Controllers/Api/Player/ChallengeController.php @@ -2,10 +2,12 @@ namespace App\Http\Controllers\Api\Player; +use App\Enums\Game\RewardType; use App\Helper\Uuid\UuidHelper; use App\Http\Controllers\Controller; use App\Http\Requests\Api\Player\ExecuteChallengeProgressionBatchRequest; use App\Http\Requests\Api\Player\GetChallengeProgressionBatchRequest; +use App\Http\Responses\Api\General\Reward; use App\Http\Responses\Api\Player\Challenges\ChallengeProgressionBatchResponse; use App\Http\Responses\Api\Player\Challenges\ChallengeProgressionEntry; use App\Http\Responses\Api\Player\Challenges\GetChallengesEntry; @@ -13,10 +15,12 @@ use App\Models\Game\Matchmaking\Game; use App\Models\Game\PickedChallenge; use App\Models\Game\TimedChallenge; +use App\Models\User\PlayerData; use App\Models\User\User; use Auth; use Carbon\Carbon; use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Response; @@ -180,9 +184,11 @@ protected function addProgressToChallenge(string $challengeId, int $newProgress, protected function setChallengeAsCompleted(string $challengeId, User $user) { + $isTimed = false; // If the Challenge id starts with the Prefix, handle it as a timed challenge. if(Str::startsWith($challengeId, GetChallengesEntry::ID_PREFIX)) { $foundChallenge = TimedChallenge::find(Str::remove(GetChallengesEntry::ID_PREFIX, $challengeId)); + $isTimed = true; } else { /** @var Challenge|null $foundChallenge */ @@ -190,9 +196,26 @@ protected function setChallengeAsCompleted(string $challengeId, User $user) } if($foundChallenge !== null) { - $foundChallenge->playerData()->updateExistingPivot($user->playerData()->id, [ + $pivotToEdit = [ 'progress' => $foundChallenge->completion_value, - ]); + ]; + + if($isTimed) { + $playerData = $user->playerData(); + $hasClaimed = $foundChallenge->hasPlayerClaimed($playerData->id); + + if(!$hasClaimed) { + $rewardsToAdd = $foundChallenge->getRewards(); + + foreach($rewardsToAdd as $reward) { + $this->addReward($reward, $playerData); + } + $playerData->save(); + $pivotToEdit['claimed'] = true; + } + } + + $foundChallenge->playerData()->updateExistingPivot($user->playerData()->id, $pivotToEdit); $foundChallenge->save(); return; } @@ -204,4 +227,33 @@ protected function setChallengeAsCompleted(string $challengeId, User $user) $foundChallenge->progress = $foundChallenge->completion_value; $foundChallenge->save(); } + + protected function addReward(Reward $reward, PlayerData &$playerData): void + { + if ($reward->type === RewardType::Currency) { + switch ($reward->id) { + case 'CurrencyA': + $playerData->currency_a += $reward->amount; + break; + case 'CurrencyB': + $playerData->currency_b += $reward->amount; + break; + case 'CurrencyC': + $playerData->currency_c += $reward->amount; + break; + default: + return; + } + } + else if ($reward->type === RewardType::Inventory) { + $itemUuid = Uuid::fromString($reward->id)->toString(); + + try { + // Since we can only have one occurrence of an item in the inventory, and an exception gets thrown when we try to add the same item twice + $playerData->inventory()->attach($itemUuid); + } catch (UniqueConstraintViolationException $e) { + + } + } + } } diff --git a/dist/app/Models/Game/TimedChallenge.php b/dist/app/Models/Game/TimedChallenge.php index d894f32..d6c1782 100644 --- a/dist/app/Models/Game/TimedChallenge.php +++ b/dist/app/Models/Game/TimedChallenge.php @@ -4,6 +4,8 @@ use App\Enums\Game\ChallengeType; use App\Enums\Game\Faction; +use App\Enums\Game\RewardType; +use App\Http\Responses\Api\General\Reward; use App\Models\User\PlayerData; use Carbon\Carbon; use Eloquent; @@ -45,6 +47,25 @@ public function hasPlayerClaimed(int $playerDataId): bool { return false; } + /** + * @return Reward[] + */ + public function getRewards(): array { + if ($this->rewards === null) + return []; + + $result = []; + foreach ($this->rewards as $reward) { + $result[] = new Reward( + RewardType::tryFrom($reward['type']), + $reward['amount'], + $reward['id'], + ); + } + + return $result; + } + public function playerData(): BelongsToMany { return $this->belongsToMany(PlayerData::class)->withPivot(['progress', 'claimed']); From 81794d44feedbcc9c2a9b920038eba35a743ebfa Mon Sep 17 00:00:00 2001 From: Vari Date: Wed, 18 Dec 2024 15:31:06 +0100 Subject: [PATCH 5/8] Added Scheduled command to generate Daily and Weekly Challenges. --- .../Classes/Factory/TimedChallengeFactory.php | 167 ++++++++++++++++++ .../Commands/GenerateTimedChallenges.php | 89 ++++++++++ dist/app/Console/Kernel.php | 1 + .../TimedChallengeFactoryException.php | 8 + dist/config/logging.php | 7 + 5 files changed, 272 insertions(+) create mode 100644 dist/app/Classes/Factory/TimedChallengeFactory.php create mode 100644 dist/app/Console/Commands/GenerateTimedChallenges.php create mode 100644 dist/app/Exceptions/TimedChallengeFactoryException.php diff --git a/dist/app/Classes/Factory/TimedChallengeFactory.php b/dist/app/Classes/Factory/TimedChallengeFactory.php new file mode 100644 index 0000000..94ef141 --- /dev/null +++ b/dist/app/Classes/Factory/TimedChallengeFactory.php @@ -0,0 +1,167 @@ + 1, + '/Game/Challenges/Daily/Challenge_Domination_Runner.Challenge_Domination_Runner' => 1, + '/Game/Challenges/Weekly/Challenge_Emotional_RunnerWeekly.Challenge_Emotional_RunnerWeekly' => 3, + '/Game/Challenges/Weekly/Challenge_Shields_RunnerWeekly.Challenge_Shields_RunnerWeekly' => 10 + ] ; + + const AVAILABLE_DAILY_HUNTER = [ + '/Game/Challenges/Daily/Challenge_Domination_Hunter.Challenge_Domination_Hunter' => 1, + '/Game/Challenges/Weekly/Challenge_DroneActivation_HunterWeekly.Challenge_DroneActivation_HunterWeekly' => 5, + '/Game/Challenges/Weekly/Challenge_Emotional_HunterWeekly.Challenge_Emotional_HunterWeekly' => 3, + '/Game/Challenges/Weekly/Challenge_Headshot_HunterWeekly.Challenge_Headshot_HunterWeekly' => 1, + '/Game/Challenges/Weekly/Challenge_InDenial_HunterWeekly.Challenge_InDenial_HunterWeekly' => 3, + ]; + + const AVAILABLE_WEEKLY_RUNNER = [ + '/Game/Challenges/Weekly/Challenge_BleedOut_RunnerWeekly.Challenge_BleedOut_RunnerWeekly' => 5, + '/Game/Challenges/Weekly/Challenge_Emotional_RunnerWeekly.Challenge_Emotional_RunnerWeekly' => 10, + '/Game/Challenges/Weekly/Challenge_Greed_RunnerWeekly.Challenge_Greed_RunnerWeekly' => 50, + '/Game/Challenges/Weekly/Challenge_Shields_RunnerWeekly.Challenge_Shields_RunnerWeekly' => 100, + '/Game/Challenges/Weekly/Challenge_SpeedCapture_RunnerWeekly.Challenge_SpeedCapture_RunnerWeekly' => 15, + '/Game/Challenges/Weekly/Challenge_UPs_RunnerWeekly.Challenge_UPs_RunnerWeekly' => 5, + '/Game/Challenges/Weekly/Challenge_Wasteful_RunnerWeekly.Challenge_Wasteful_RunnerWeekly' => 100 + ]; + + const AVAILABLE_WEEKLY_HUNTER = [ + '/Game/Challenges/Weekly/Challenge_ARB_Damage_HunterWeekly.Challenge_ARB_Damage_HunterWeekly' => 5000, + '/Game/Challenges/Weekly/Challenge_AssaultRifleWins_HunterWeekly.Challenge_AssaultRifleWins_HunterWeekly' => 100, + '/Game/Challenges/Weekly/Challenge_Damage_HunterWeekly.Challenge_Damage_HunterWeekly' => 5000, + '/Game/Challenges/Weekly/Challenge_DroneActivation_HunterWeekly.Challenge_DroneActivation_HunterWeekly' => 25, + '/Game/Challenges/Weekly/Challenge_Emotional_HunterWeekly.Challenge_Emotional_HunterWeekly' => 10, + '/Game/Challenges/Weekly/Challenge_Greed_HunterWeekly.Challenge_Greed_HunterWeekly' => 50, + '/Game/Challenges/Weekly/Challenge_Headshot_HunterWeekly.Challenge_Headshot_HunterWeekly' => 10, + '/Game/Challenges/Weekly/Challenge_HuntingShotgunWins_HunterWeekly.Challenge_HuntingShotgunWins_HunterWeekly' => 10, + '/Game/Challenges/Weekly/Challenge_InDenial_HunterWeekly.Challenge_InDenial_HunterWeekly' => 20, + '/Game/Challenges/Weekly/Challenge_LMGWins_HunterWeekly.Challenge_LMGWins_HunterWeekly' => 10, + '/Game/Challenges/Weekly/Challenge_Reveals_hunterWeekly.Challenge_Reveals_hunterWeekly' => 10, + '/Game/Challenges/Weekly/Challenge_RingOut_hunterWeekly.Challenge_RingOut_hunterWeekly' => 5, + '/Game/Challenges/Weekly/Challenge_Mines_HunterWeekly.Challenge_Mines_HunterWeekly' => 10, + '/Game/Challenges/Weekly/Challenge_ShotgunDowns_HunterWeekly.Challenge_ShotgunDowns_HunterWeekly' => 10, + '/Game/Challenges/Weekly/Challenge_Wasteful_HunterWeekly.Challenge_Wasteful_HunterWeekly' => 100, + ]; + + const DAILY_REWARDS = [ + 'CurrencyA' => [ + 'min' => 100, + 'max' => 250, + ], + 'CurrencyB' => [ + 'min' => 100, + 'max' => 250, + ], + 'CurrencyC' => [ + 'min' => 250, + 'max' => 500, + ], + ]; + + const WEEKLY_REWARDS = [ + 'CurrencyA' => [ + 'min' => 1000, + 'max' => 1500, + ], + 'CurrencyB' => [ + 'min' => 1000, + 'max' => 1500, + ], + 'CurrencyC' => [ + 'min' => 1500, + 'max' => 2000, + ], + ]; + + /** + * Makes a new TimedChallenge Instance that's not yet saved to the Database. + * + * @param Carbon $startTime + * @param Faction $faction + * @param ChallengeType $type + * @return void + * @throws TimedChallengeFactoryException + */ + public static function makeChallenge( + Carbon $startTime, + Faction $faction, + ChallengeType $type, + ): TimedChallenge + { + [$blueprintPath, $completionValue] = static::pickChallenge($faction, $type); + + $challenge = new TimedChallenge(); + $challenge->type = $type; + $challenge->faction = $faction; + $challenge->blueprint_path = $blueprintPath; + $challenge->completion_value = $completionValue; + + $challenge->start_time = $startTime; + + if($type === ChallengeType::Daily) + $challenge->end_time = $startTime->copy()->addDay(); + else + $challenge->end_time = $startTime->copy()->addWeek(); + + $challenge->rewards = static::pickReward($type); + + return $challenge; + } + + protected static function pickChallenge( + Faction $faction, + ChallengeType $type, + ): array { + $challengeArray = match ($faction) { + Faction::Hunter => static::AVAILABLE_DAILY_HUNTER, + Faction::Runner => static::AVAILABLE_DAILY_RUNNER, + default => null, + }; + + if($challengeArray === null) + throw new TimedChallengeFactoryException('Unallowed Faction ('. $faction->value .'), could not select a challenge.'); + + $blueprintPath = array_rand($challengeArray); + $completionValue = $challengeArray[$blueprintPath]; + + return [$blueprintPath, $completionValue]; + } + + /** + * @param ChallengeType $type + * @return Reward + * @throws TimedChallengeFactoryException + */ + protected static function pickReward( + ChallengeType $type, + ): Reward { + $rewardsArray = match ($type) { + ChallengeType::Daily => static::DAILY_REWARDS, + ChallengeType::Weekly => static::WEEKLY_REWARDS, + default => null, + }; + + if($rewardsArray === null) + throw new TimedChallengeFactoryException('Unallowed Challenge Type (' . $type->value . '), could not select a reward.'); + + $pickedReward = array_rand($rewardsArray); + + return new Reward( + RewardType::Currency, + round(rand($rewardsArray[$pickedReward]['min'], $rewardsArray[$pickedReward]['max']) / 10) * 10, + $pickedReward, + ); + } +} \ No newline at end of file diff --git a/dist/app/Console/Commands/GenerateTimedChallenges.php b/dist/app/Console/Commands/GenerateTimedChallenges.php new file mode 100644 index 0000000..f282743 --- /dev/null +++ b/dist/app/Console/Commands/GenerateTimedChallenges.php @@ -0,0 +1,89 @@ +setTime(static::INTERVAL_HOUR, static::INTERVAL_MINUTE); + $dailyTomorrow = $dailyToday->copy()->addDay(); + $currentWeekly = Carbon::parse('last '.static::WEEKLY_INTERVAL_DAY); + $currentWeekly->setTime(static::INTERVAL_HOUR, static::INTERVAL_MINUTE); + $nextWeekly = $currentWeekly->copy()->addWeek(); + + $dailys = [$dailyToday, $dailyTomorrow]; + $weeklys = [$currentWeekly, $nextWeekly]; + + $log = Log::channel('challengeCreation'); + + foreach ([Faction::Hunter, Faction::Runner] as $faction) { + foreach ($dailys as $time) { + $dailyExists = TimedChallenge::whereDate('start_time', $time) + ->where('type', ChallengeType::Daily) + ->where('faction', $faction) + ->exists(); + + if(!$dailyExists) { + try { + $newDaily = TimedChallengeFactory::makeChallenge($time, $faction, ChallengeType::Daily); + $newDaily->save(); + } catch (TimedChallengeFactoryException $e) { + $log->error($e->getMessage()); + } + } + } + + foreach ($weeklys as $time) { + $weeklyExists = TimedChallenge::whereDate('start_time', $time) + ->where('type', ChallengeType::Weekly) + ->where('faction', $faction) + ->exists(); + + if(!$weeklyExists) { + try { + $newDaily = TimedChallengeFactory::makeChallenge($time, $faction, ChallengeType::Weekly); + $newDaily->save(); + } catch (TimedChallengeFactoryException $e) { + $log->error($e->getMessage()); + } + } + } + } + } +} diff --git a/dist/app/Console/Kernel.php b/dist/app/Console/Kernel.php index e99c6e6..84f963e 100644 --- a/dist/app/Console/Kernel.php +++ b/dist/app/Console/Kernel.php @@ -15,6 +15,7 @@ protected function schedule(Schedule $schedule): void $schedule->command('model:prune')->daily(); $schedule->command('matchmaking:process')->everyFiveSeconds(); $schedule->command('matchmaking:cleanup')->everyFifteenSeconds(); + $schedule->command('app:generate-timed-challenges')->daily(); } /** diff --git a/dist/app/Exceptions/TimedChallengeFactoryException.php b/dist/app/Exceptions/TimedChallengeFactoryException.php new file mode 100644 index 0000000..21df59e --- /dev/null +++ b/dist/app/Exceptions/TimedChallengeFactoryException.php @@ -0,0 +1,8 @@ + true, ], + 'challengeCreation' => [ + 'driver' => 'single', + 'path' => storage_path('logs/challengeCreation.log'), + 'level' => 'info', + 'replace_placeholders' => true, + ], + 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), From 5b0c2432fd38f1a75af09ac887a7647179333d4b Mon Sep 17 00:00:00 2001 From: Miraak Date: Wed, 18 Dec 2024 15:35:00 +0100 Subject: [PATCH 6/8] Updated the Rewards for the Daily & Weekly challenges --- .../Classes/Factory/TimedChallengeFactory.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/dist/app/Classes/Factory/TimedChallengeFactory.php b/dist/app/Classes/Factory/TimedChallengeFactory.php index 94ef141..e254818 100644 --- a/dist/app/Classes/Factory/TimedChallengeFactory.php +++ b/dist/app/Classes/Factory/TimedChallengeFactory.php @@ -57,31 +57,31 @@ abstract class TimedChallengeFactory const DAILY_REWARDS = [ 'CurrencyA' => [ - 'min' => 100, - 'max' => 250, + 'min' => 300, + 'max' => 500, ], 'CurrencyB' => [ - 'min' => 100, - 'max' => 250, + 'min' => 600, + 'max' => 1000, ], 'CurrencyC' => [ - 'min' => 250, - 'max' => 500, + 'min' => 500, + 'max' => 900, ], ]; const WEEKLY_REWARDS = [ 'CurrencyA' => [ - 'min' => 1000, - 'max' => 1500, + 'min' => 2000, + 'max' => 3000, ], 'CurrencyB' => [ - 'min' => 1000, - 'max' => 1500, + 'min' => 4000, + 'max' => 6000, ], 'CurrencyC' => [ - 'min' => 1500, - 'max' => 2000, + 'min' => 3000, + 'max' => 5000, ], ]; @@ -164,4 +164,4 @@ protected static function pickReward( $pickedReward, ); } -} \ No newline at end of file +} From cd842f93f9c3244ea8a1c5185a7352a067020d6d Mon Sep 17 00:00:00 2001 From: Vari Date: Wed, 18 Dec 2024 17:30:14 +0100 Subject: [PATCH 7/8] Fix wrong variable name in getChallenges endpoint. --- .../Responses/Api/Player/Challenges/GetChallengesEntry.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php b/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php index 99301f8..624c63a 100644 --- a/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php +++ b/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php @@ -45,8 +45,8 @@ public function jsonSerialize(): mixed $rewards = []; foreach ($this->rewards as &$reward) { - $reward['claimed'] = $this->claimed; - $reward['weight'] = 100; + $rewards['claimed'] = $this->claimed; + $rewards['weight'] = 100; $rewards[] = $reward; } $json['rewards'] = $rewards; From 595ad29841689892af73872db03f7992be78a8c3 Mon Sep 17 00:00:00 2001 From: Vari Date: Wed, 18 Dec 2024 18:09:03 +0100 Subject: [PATCH 8/8] Fix some stuff with the challenge generation and the getChallenges endpoint. --- .../Classes/Factory/TimedChallengeFactory.php | 6 +-- .../Commands/GenerateTimedChallenges.php | 5 +++ dist/app/Enums/Game/RewardType.php | 6 +-- .../Api/Player/ChallengeController.php | 9 ++-- .../Api/Player/GetChallengesRequest.php | 42 +++++++++++++++++++ .../Player/Challenges/GetChallengesEntry.php | 4 +- 6 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 dist/app/Http/Requests/Api/Player/GetChallengesRequest.php diff --git a/dist/app/Classes/Factory/TimedChallengeFactory.php b/dist/app/Classes/Factory/TimedChallengeFactory.php index e254818..cd99ebd 100644 --- a/dist/app/Classes/Factory/TimedChallengeFactory.php +++ b/dist/app/Classes/Factory/TimedChallengeFactory.php @@ -115,7 +115,7 @@ public static function makeChallenge( else $challenge->end_time = $startTime->copy()->addWeek(); - $challenge->rewards = static::pickReward($type); + $challenge->rewards = [static::pickReward($type)]; return $challenge; } @@ -125,8 +125,8 @@ protected static function pickChallenge( ChallengeType $type, ): array { $challengeArray = match ($faction) { - Faction::Hunter => static::AVAILABLE_DAILY_HUNTER, - Faction::Runner => static::AVAILABLE_DAILY_RUNNER, + Faction::Hunter => $type === ChallengeType::Daily ? static::AVAILABLE_DAILY_HUNTER : static::AVAILABLE_WEEKLY_HUNTER, + Faction::Runner => $type === ChallengeType::Daily ? static::AVAILABLE_DAILY_RUNNER : static::AVAILABLE_WEEKLY_RUNNER, default => null, }; diff --git a/dist/app/Console/Commands/GenerateTimedChallenges.php b/dist/app/Console/Commands/GenerateTimedChallenges.php index f282743..fdec404 100644 --- a/dist/app/Console/Commands/GenerateTimedChallenges.php +++ b/dist/app/Console/Commands/GenerateTimedChallenges.php @@ -42,6 +42,11 @@ public function handle() { $dailyToday = Carbon::today(); $dailyToday->setTime(static::INTERVAL_HOUR, static::INTERVAL_MINUTE); + + // Put the current Daily a day in the past when the current time the job runs is still before the new Daily + if(Carbon::now()->isBefore($dailyToday)) + $dailyToday->subDay(); + $dailyTomorrow = $dailyToday->copy()->addDay(); $currentWeekly = Carbon::parse('last '.static::WEEKLY_INTERVAL_DAY); $currentWeekly->setTime(static::INTERVAL_HOUR, static::INTERVAL_MINUTE); diff --git a/dist/app/Enums/Game/RewardType.php b/dist/app/Enums/Game/RewardType.php index 4d191a1..334a36c 100644 --- a/dist/app/Enums/Game/RewardType.php +++ b/dist/app/Enums/Game/RewardType.php @@ -4,7 +4,7 @@ enum RewardType: string { - case Currency = 'Currency'; - case Inventory = 'Inventory'; - case Progression = 'Progression'; + case Currency = 'currency'; + case Inventory = 'inventory'; + case Progression = 'progression'; } diff --git a/dist/app/Http/Controllers/Api/Player/ChallengeController.php b/dist/app/Http/Controllers/Api/Player/ChallengeController.php index eade0ab..b723c18 100644 --- a/dist/app/Http/Controllers/Api/Player/ChallengeController.php +++ b/dist/app/Http/Controllers/Api/Player/ChallengeController.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Api\Player\ExecuteChallengeProgressionBatchRequest; use App\Http\Requests\Api\Player\GetChallengeProgressionBatchRequest; +use App\Http\Requests\Api\Player\GetChallengesRequest; use App\Http\Responses\Api\General\Reward; use App\Http\Responses\Api\Player\Challenges\ChallengeProgressionBatchResponse; use App\Http\Responses\Api\Player\Challenges\ChallengeProgressionEntry; @@ -36,11 +37,13 @@ class ChallengeController extends Controller * * @return false|string */ - public function getChallenges() + public function getChallenges(GetChallengesRequest $request) { - $user = Auth::user(); + $user = $request->user; $timedChallenges = TimedChallenge::where('start_time', '<', Carbon::now()) - ->where('end_time', '>', Carbon::now())->get(); + ->where('end_time', '>', Carbon::now()) + ->where('type', $request->type) + ->get(); $challenges = []; $timedChallenges->each(function (TimedChallenge $challenge) use (&$challenges, $user) { diff --git a/dist/app/Http/Requests/Api/Player/GetChallengesRequest.php b/dist/app/Http/Requests/Api/Player/GetChallengesRequest.php new file mode 100644 index 0000000..ff4658f --- /dev/null +++ b/dist/app/Http/Requests/Api/Player/GetChallengesRequest.php @@ -0,0 +1,42 @@ +|string> + */ + public function rules(): array + { + return [ + 'data.userId' => 'required|exists:users,id', + 'data.challengeType' => ['required', Rule::enum(ChallengeType::class)], + ]; + } + + public function passedValidation(): void + { + $this->user = User::find($this->input('data.userId')); + $this->type = ChallengeType::tryFrom($this->input('data.challengeType')); + } +} diff --git a/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php b/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php index 624c63a..99301f8 100644 --- a/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php +++ b/dist/app/Http/Responses/Api/Player/Challenges/GetChallengesEntry.php @@ -45,8 +45,8 @@ public function jsonSerialize(): mixed $rewards = []; foreach ($this->rewards as &$reward) { - $rewards['claimed'] = $this->claimed; - $rewards['weight'] = 100; + $reward['claimed'] = $this->claimed; + $reward['weight'] = 100; $rewards[] = $reward; } $json['rewards'] = $rewards;