Skip to content

Commit

Permalink
add guaranteed (pity) loot drops to tables
Browse files Browse the repository at this point in the history
  • Loading branch information
ScuffedNewt committed Oct 12, 2023
1 parent c645eb8 commit f1bb2c7
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 7 deletions.
2 changes: 1 addition & 1 deletion app/Helpers/AssetHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
}
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/Admin/Data/LootTableController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion app/Models/Loot/Loot.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
];

/**
Expand Down
62 changes: 60 additions & 2 deletions app/Models/Loot/LootTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class LootTable extends Model {
* @var array
*/
protected $fillable = [
'name', 'display_name',
'name', 'display_name', 'rolls',
];

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
}
7 changes: 7 additions & 0 deletions app/Models/User/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions app/Models/User/UserLootDropProgress.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace App\Models\User;

use App\Models\Model;

class UserLootDropProgress extends Model {
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'user_id', 'loot_table_id', 'rolls'
];

/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'user_loot_drop_progress';

/**
* The primary key of the model.
*
* @var string
*/
public $primaryKey = 'user_id';

/**********************************************************************************************
RELATIONS
**********************************************************************************************/

/**
* Get the user this set of progress belongs to.
*/
public function user() {
return $this->belongsTo('App\Models\User\User');
}

/**
* Get the loot table this progress belongs to.
*/
public function lootTable() {
return $this->belongsTo('App\Models\Loot\LootTable');
}
}
26 changes: 24 additions & 2 deletions app/Services/LootService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -160,13 +168,27 @@ 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,
'rewardable_id' => $data['rewardable_id'][$key] ?? 1,
'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,
]);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
Schema::table('loots', function (Blueprint $table) {
$table->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');
}
};
53 changes: 53 additions & 0 deletions resources/views/admin/loot_tables/create_edit_loot_table.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,52 @@
</tbody>
</table>

@if ($table->id)
<h3>Guaranteed (Pity) Rewards</h3>
<p>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.
<br>This is useful for preventing unlucky streaks, and can be used to guarantee a reward after a certain number of rolls.</p>
<p><b>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.</b></p>

<div class="form-group">
{{-- number of rolls --}}
{!! Form::label('rolls', 'Number of Rolls') !!}
{!! Form::text('rolls', $table->rolls ?? 1, ['class' => 'form-control', 'min' => 0]) !!}
</div>
<div class="form-group" id="guaranteedDrops">
<div class="row">
<div class="col-sm-4">
<label><h5>Rewards</h5></label>
</div>
<div class="col-sm-8">
<div class="text-right mb-3">
<a href="#" class="btn btn-info" id="addGuaranteed">Add Guaranteed Reward</a>
</div>
</div>
</div>
<div class="row mb-2">
@foreach($table->loot->where('is_guaranteed', 1) as $guaranteed)
<div class="col-sm-12">
{!! Form::select('guaranteed_loot_ids[]', $table->getLoot(), $guaranteed->rewardable_id, ['class' => 'form-control selectize', 'placeholder' => 'Select Loot']) !!}
</div>
@endforeach
</div>
</div>

@endif

<div class="text-right">
{!! Form::submit($table->id ? 'Edit' : 'Create', ['class' => 'btn btn-primary']) !!}
</div>

{!! Form::close() !!}

<div id="guaranteedRowData" class="hide">
<div class="col-sm-12">
{!! Form::select('guaranteed_loot_ids[]', $table->getLoot(), null, ['class' => 'form-control selectize', 'placeholder' => 'Select Loot']) !!}
</div>
</div>

<div id="lootRowData" class="hide">
<table class="table table-sm">
<tbody id="lootRow">
Expand Down Expand Up @@ -143,6 +183,7 @@
<p>If you have made any modifications to the loot table contents above, be sure to save it (click the Edit button) before testing.</p>
<p>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. <i>This is not indicative of the code being buggy or poor game balance.</i> 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.</p>
<p><b>Guaranteed rewards will not be given when testing, as it is user specific.</b></p>
<div class="form-group">
{!! Form::label('quantity', 'Number of Rolls') !!}
{!! Form::text('quantity', 1, ['class' => 'form-control', 'id' => 'rollQuantity']) !!}
Expand All @@ -158,6 +199,18 @@
@parent
<script>
$(document).ready(function() {
// GUARANTEED DROPS //////////////////////////////////////////
var $guaranteedDrops = $('#guaranteedDrops');
var $guaranteedRow = $('#guaranteedRowData').find('.col-sm-12');
$('#addGuaranteed').on('click', function(e) {
e.preventDefault();
var $clone = $guaranteedRow.clone();
$guaranteedDrops.append($clone);
$clone.find('.selectize').selectize();
});
//////////////////////////////////////////////////////////////
var $lootTable = $('#lootTableBody');
var $lootRow = $('#lootRow').find('.loot-row');
var $itemSelect = $('#lootRowData').find('.item-select');
Expand Down

0 comments on commit f1bb2c7

Please sign in to comment.