From f1bb2c79d23fea0a5317bcb2413248903efeafad Mon Sep 17 00:00:00 2001 From: ScuffedNewt Date: Thu, 12 Oct 2023 15:43:55 +0100 Subject: [PATCH] add guaranteed (pity) loot drops to tables --- app/Helpers/AssetHelpers.php | 2 +- .../Admin/Data/LootTableController.php | 2 +- app/Models/Loot/Loot.php | 2 +- app/Models/Loot/LootTable.php | 62 ++++++++++++++++++- app/Models/User/User.php | 7 +++ app/Models/User/UserLootDropProgress.php | 50 +++++++++++++++ app/Services/LootService.php | 26 +++++++- ...12308_add_guaranteed_loot_drops_tables.php | 49 +++++++++++++++ .../create_edit_loot_table.blade.php | 53 ++++++++++++++++ 9 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 app/Models/User/UserLootDropProgress.php create mode 100644 database/migrations/2023_10_12_112308_add_guaranteed_loot_drops_tables.php diff --git a/app/Helpers/AssetHelpers.php b/app/Helpers/AssetHelpers.php index c35f2dd195..43fff8283d 100644 --- a/app/Helpers/AssetHelpers.php +++ b/app/Helpers/AssetHelpers.php @@ -270,7 +270,7 @@ function fillUserAssets($assets, $sender, $recipient, $logType, $data) { // Roll on any loot tables if (isset($assets['loot_tables'])) { foreach ($assets['loot_tables'] as $table) { - $assets = mergeAssetsArrays($assets, $table['asset']->roll($table['quantity'])); + $assets = mergeAssetsArrays($assets, $table['asset']->roll($table['quantity'], $recipient ?? null)); } unset($assets['loot_tables']); } diff --git a/app/Http/Controllers/Admin/Data/LootTableController.php b/app/Http/Controllers/Admin/Data/LootTableController.php index 592d99bd07..08f0964354 100644 --- a/app/Http/Controllers/Admin/Data/LootTableController.php +++ b/app/Http/Controllers/Admin/Data/LootTableController.php @@ -88,7 +88,7 @@ public function postCreateEditLootTable(Request $request, LootService $service, $id ? $request->validate(LootTable::$updateRules) : $request->validate(LootTable::$createRules); $data = $request->only([ 'name', 'display_name', 'rewardable_type', 'rewardable_id', 'quantity', 'weight', - 'criteria', 'rarity', + 'criteria', 'rarity', 'rolls', 'guaranteed_loot_ids' ]); if ($id && $service->updateLootTable(LootTable::find($id), $data)) { flash('Loot table updated successfully.')->success(); diff --git a/app/Models/Loot/Loot.php b/app/Models/Loot/Loot.php index fe1470a0eb..be8788deff 100644 --- a/app/Models/Loot/Loot.php +++ b/app/Models/Loot/Loot.php @@ -12,7 +12,7 @@ class Loot extends Model { */ protected $fillable = [ 'loot_table_id', 'rewardable_type', 'rewardable_id', - 'quantity', 'weight', 'data', + 'quantity', 'weight', 'data', 'is_guaranteed', ]; /** diff --git a/app/Models/Loot/LootTable.php b/app/Models/Loot/LootTable.php index 4af2ec7750..a497bb45ac 100644 --- a/app/Models/Loot/LootTable.php +++ b/app/Models/Loot/LootTable.php @@ -13,7 +13,7 @@ class LootTable extends Model { * @var array */ protected $fillable = [ - 'name', 'display_name', + 'name', 'display_name', 'rolls', ]; /** @@ -55,6 +55,13 @@ public function loot() { return $this->hasMany('App\Models\Loot\Loot', 'loot_table_id'); } + /** + * Get the guaranteed loot data for this loot table. + */ + public function guaranteedLoot() { + return $this->hasMany('App\Models\Loot\LootTableGuaranteedDrop', 'loot_table_id'); + } + /********************************************************************************************** ACCESSORS @@ -110,9 +117,48 @@ public function getAdminPowerAttribute() { * * @return \Illuminate\Support\Collection */ - public function roll($quantity = 1) { + public function roll($quantity = 1, $user = null) { $rewards = createAssetsArray(); + // check if there is a user (for pity drops) + if ($user && $this->rolls > 0) { + // check if user has a progress entry for this loot table + $progress = $user->lootDropProgresses()->where('loot_table_id', $this->id)->first(); + if (!$progress) { + // create a new progress entry + $progress = $user->lootDropProgresses()->create([ + 'loot_table_id' => $this->id, + 'rolls' => 1, + ]); + } + // check if user has rolled enough times to get a guaranteed drop + if ($progress->rolls >= $this->rolls - 1) { // -1 so that the amount of times rolled exactly matches the amount of rolls needed for a guaranteed drop + // roll on guaranteed drops + $guaranteed = $this->loot->where('is_guaranteed', true); + foreach ($guaranteed as $g) { + // If this is chained to another loot table, roll on that table + if ($g->rewardable_type == 'LootTable') { + $rewards = mergeAssetsArrays($rewards, $g->reward->roll($g->quantity)); + } elseif ($g->rewardable_type == 'ItemCategory' || $g->rewardable_type == 'ItemCategoryRarity') { + $rewards = mergeAssetsArrays($rewards, $this->rollCategory($g->rewardable_id, $g->quantity, ($g->data['criteria'] ?? null), ($g->data['rarity'] ?? null))); + } elseif ($g->rewardable_type == 'ItemRarity') { + $rewards = mergeAssetsArrays($rewards, $this->rollRarityItem($g->quantity, $g->data['criteria'], $g->data['rarity'])); + } else { + addAsset($rewards, $g->reward, $g->quantity); + } + } + // reset user's progress + $progress->rolls = 0; + $progress->save(); + // return rewards + return $rewards; + } + else { + $progress->rolls++; + $progress->save(); + } + } + $loot = $this->loot; $totalWeight = 0; foreach ($loot as $l) { @@ -230,4 +276,16 @@ public function rollRarityItem($quantity, $criteria, $rarity) { return $rewards; } + + /** + * Returns loot as a paired rewardable name and id + */ + public function getLoot() + { + $loots = []; + foreach ($this->loot as $loot) { + $loots[$loot->rewardable_id] = $loot->reward->name; + } + return $loots; + } } diff --git a/app/Models/User/User.php b/app/Models/User/User.php index 7408ab286a..2e7342347d 100644 --- a/app/Models/User/User.php +++ b/app/Models/User/User.php @@ -185,6 +185,13 @@ public function commentLikes() { return $this->hasMany('App\Models\CommentLike'); } + /** + * Get the user's loot drop progresses + */ + public function lootDropProgresses() { + return $this->hasMany('App\Models\User\UserLootDropProgress'); + } + /********************************************************************************************** SCOPES diff --git a/app/Models/User/UserLootDropProgress.php b/app/Models/User/UserLootDropProgress.php new file mode 100644 index 0000000000..2300eaf1e1 --- /dev/null +++ b/app/Models/User/UserLootDropProgress.php @@ -0,0 +1,50 @@ +belongsTo('App\Models\User\User'); + } + + /** + * Get the loot table this progress belongs to. + */ + public function lootTable() { + return $this->belongsTo('App\Models\Loot\LootTable'); + } +} diff --git a/app/Services/LootService.php b/app/Services/LootService.php index 26deb58c02..8c36d4a29b 100644 --- a/app/Services/LootService.php +++ b/app/Services/LootService.php @@ -5,6 +5,7 @@ use App\Models\Loot\Loot; use App\Models\Loot\LootTable; use App\Models\Prompt\PromptReward; +use App\Models\User\UserLootDropProgress; use DB; use Illuminate\Support\Arr; @@ -100,10 +101,11 @@ public function updateLootTable($table, $data) { } } } + if (!isset($data['rolls']) || $data['rolls'] <= 0) $data['rolls'] = null; - $table->update(Arr::only($data, ['name', 'display_name'])); + $table->update(Arr::only($data, ['name', 'display_name', 'rolls'])); - $this->populateLootTable($table, Arr::only($data, ['rewardable_type', 'rewardable_id', 'quantity', 'weight', 'criteria', 'rarity'])); + $this->populateLootTable($table, Arr::only($data, ['rewardable_type', 'rewardable_id', 'quantity', 'weight', 'criteria', 'rarity', 'guaranteed_loot_ids'])); return $this->commitReturn($table); } catch (\Exception $e) { @@ -132,6 +134,12 @@ public function deleteLootTable($table) { } $table->loot()->delete(); + // delete all user progress for this table, if they exist + if (UserLootDropProgress::where('loot_table_id', $table->id)->exists()) { + UserLootDropProgress::where('loot_table_id', $table->id)->get()->each(function ($progress) { + $progress->delete(); + }); + } $table->delete(); return $this->commitReturn(true); @@ -160,6 +168,18 @@ private function populateLootTable($table, $data) { ]; } + // check if the loot id is in the guaranteed loot ids, + // and that table has a rolls value > 0 + if (isset($data['guaranteed_loot_ids']) && in_array($data['rewardable_id'][$key], $data['guaranteed_loot_ids']) && $table->rolls > 0) { + // check if this loot is the smallest weight of this id in the array + $isSmallestWeight = true; + foreach ($data['weight'] as $weight) { + if ($weight < $data['weight'][$key]) { + $isSmallestWeight = false; + } + } + } + Loot::create([ 'loot_table_id' => $table->id, 'rewardable_type' => $type, @@ -167,6 +187,8 @@ private function populateLootTable($table, $data) { 'quantity' => $data['quantity'][$key], 'weight' => $data['weight'][$key], 'data' => isset($lootData) ? json_encode($lootData) : null, + 'is_guaranteed' => isset($data['guaranteed_loot_ids']) && in_array($data['rewardable_id'][$key], + $data['guaranteed_loot_ids']) && $table->rolls > 0 && $isSmallestWeight ?? false, ]); } } diff --git a/database/migrations/2023_10_12_112308_add_guaranteed_loot_drops_tables.php b/database/migrations/2023_10_12_112308_add_guaranteed_loot_drops_tables.php new file mode 100644 index 0000000000..61cad1c3e1 --- /dev/null +++ b/database/migrations/2023_10_12_112308_add_guaranteed_loot_drops_tables.php @@ -0,0 +1,49 @@ +boolean('is_guaranteed')->default(false); + }); + + Schema::table('loot_tables', function (Blueprint $table) { + $table->integer('rolls')->default(null)->nullable(); + }); + + Schema::create('user_loot_drop_progress', function (Blueprint $table) { + $table->id(); + $table->integer('user_id'); + $table->integer('loot_table_id'); + $table->integer('rolls'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + Schema::table('loots', function (Blueprint $table) { + $table->dropColumn('is_guaranteed'); + }); + Schema::table('loot_tables', function (Blueprint $table) { + $table->dropColumn('rolls'); + }); + Schema::dropIfExists('user_loot_drop_progress'); + } +}; diff --git a/resources/views/admin/loot_tables/create_edit_loot_table.blade.php b/resources/views/admin/loot_tables/create_edit_loot_table.blade.php index 806f3e928a..2d8bfa3b7f 100644 --- a/resources/views/admin/loot_tables/create_edit_loot_table.blade.php +++ b/resources/views/admin/loot_tables/create_edit_loot_table.blade.php @@ -96,12 +96,52 @@ + @if ($table->id) +

Guaranteed (Pity) Rewards

+

Guaranteed rewards are rewards that will be given if the loot table is rolled a certain number of times without giving any of the specified rewards. +
This is useful for preventing unlucky streaks, and can be used to guarantee a reward after a certain number of rolls.

+

Please note that if you have multiple of the same loot as a reward in the loot table (example: different quantities of currencies) + the pity reward will always be given to the loot with the lowest weight.

+ +
+ {{-- number of rolls --}} + {!! Form::label('rolls', 'Number of Rolls') !!} + {!! Form::text('rolls', $table->rolls ?? 1, ['class' => 'form-control', 'min' => 0]) !!} +
+
+
+
+ +
+ +
+
+ @foreach($table->loot->where('is_guaranteed', 1) as $guaranteed) +
+ {!! Form::select('guaranteed_loot_ids[]', $table->getLoot(), $guaranteed->rewardable_id, ['class' => 'form-control selectize', 'placeholder' => 'Select Loot']) !!} +
+ @endforeach +
+
+ + @endif +
{!! Form::submit($table->id ? 'Edit' : 'Create', ['class' => 'btn btn-primary']) !!}
{!! Form::close() !!} +
+
+ {!! Form::select('guaranteed_loot_ids[]', $table->getLoot(), null, ['class' => 'form-control selectize', 'placeholder' => 'Select Loot']) !!} +
+
+
@@ -143,6 +183,7 @@

If you have made any modifications to the loot table contents above, be sure to save it (click the Edit button) before testing.

Please note that due to the nature of probability, as long as there is a chance, there will always be the possibility of rolling improbably good or bad results. This is not indicative of the code being buggy or poor game balance. Be cautious when adjusting values based on a small sample size, including but not limited to test rolls and a small amount of user reports.

+

Guaranteed rewards will not be given when testing, as it is user specific.

{!! Form::label('quantity', 'Number of Rolls') !!} {!! Form::text('quantity', 1, ['class' => 'form-control', 'id' => 'rollQuantity']) !!} @@ -158,6 +199,18 @@ @parent