Skip to content

Commit

Permalink
feat(items): make items to use rarity system for rarities (#1114)
Browse files Browse the repository at this point in the history
* feat: make items have rarities and also fix conditional loot table error(s)

* feat: make loot conditional queries based on rarity sort rather than id

* chore: restore configs to defaults

* refactor: fix PHP styling

* feat: add alert and conversion command

* refactor: fix PHP styling

---------

Co-authored-by: ScuffedNewt <[email protected]>
  • Loading branch information
ScuffedNewt and ScuffedNewt authored Nov 10, 2024
1 parent 354c1aa commit 76fef05
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 47 deletions.
94 changes: 94 additions & 0 deletions app/Console/Commands/ConvertItemRarities.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace App\Console\Commands;

use App\Models\Item\Item;
use App\Models\Rarity;
use Illuminate\Console\Command;

class ConvertItemRarities extends Command {
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'convert-item-rarities';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Converts previously arbitrary item rarities (numeric format) to utilise existing rarity model';

/**
* Execute the console command.
*/
public function handle() {
//
if (!config('lorekeeper.extensions.item_entry_expansion.extra_fields')) {
$this->info('Item Entry Expansion is not enabled. Exiting.');

return;
}
$rarityItems = Item::whereNotNull('data->rarity')->get();
// pluck the rarity values from the items
$rarityValues = $rarityItems->pluck('data.rarity')->unique();
foreach ($rarityValues as $rarityValue) {
$this->info("\nCreating rarity for value: ".$rarityValue.'...');
$nearestRarity = Rarity::where('sort', $rarityValue)->first();
if (!$nearestRarity) {
$nearestRarity = Rarity::where('sort', $rarityValue - 1)->first();
}

if ($nearestRarity) {
$this->info('Closest rarity (based on sort) found: '.$nearestRarity->name);
$this->info('If this rarity is not correct, please enter "n" and choose the correct rarity.');
$ask = $this->ask('Do you want to use this rarity for the value '.$rarityValue.'? (y/n)', 'y');
if ($ask === 'y') {
$rarityItems->where('data.rarity', $rarityValue)->each(function ($item) use ($nearestRarity) {
$item->data = array_merge($item->data, ['rarity_id' => $nearestRarity->id]);
$item->save();
});
$this->info('Items with rarity value '.$rarityValue.' have been updated to use rarity '.$nearestRarity->name);
} else {
$rarityName = $this->ask('Enter the name of the rarity you want to use for the value '.$rarityValue);
$rarity = Rarity::where('name', 'LIKE', '%'.$rarityName.'%')->first();
if ($rarity) {
$check = $this->ask('Do you want to use the rarity '.$rarity->name.' for the value '.$rarityValue.'? (y/n)', 'y');
if ($check === 'y') {
$rarityItems->where('data.rarity', $rarityValue)->each(function ($item) use ($rarity) {
$item->data = array_merge($item->data, ['rarity_id' => $rarity->id]);
$item->save();
});
$this->info('Items with rarity value '.$rarityValue.' have been updated to use rarity '.$rarity->name);
} else {
$this->info('No changes made for rarity value '.$rarityValue.'.');
}
} else {
$this->info('No matching rarity found for name '.$rarityName.'.');
}
}
} else {
$this->info('No matching rarity found for value '.$rarityValue.'.');
$rarityName = $this->ask('Enter the name of the rarity you want to use for the value '.$rarityValue.':');
$rarity = Rarity::whereRaw('LOWER(name) LIKE ?', ['%'.strtolower($rarityName).'%'])->first();
if ($rarity) {
$check = $this->ask('Do you want to use the rarity '.$rarity->name.' for the value '.$rarityValue.'? (y/n)', 'y');
if ($check === 'y') {
$rarityItems->where('data.rarity', $rarityValue)->each(function ($item) use ($rarity) {
$item->data = array_merge($item->data, ['rarity_id' => $rarity->id]);
$item->save();
});
$this->info('Items with rarity value '.$rarityValue.' have been updated to use rarity '.$rarity->name);
} else {
$this->info('No changes made for rarity value '.$rarityValue.'.');
}
} else {
$this->info('No matching rarity found for name '.$rarityName.'.');
$this->info('Feel free to re-run the command to try again.');
}
}
}
}
}
5 changes: 4 additions & 1 deletion app/Http/Controllers/Admin/Data/ItemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Models\Item\Item;
use App\Models\Item\ItemCategory;
use App\Models\Prompt\Prompt;
use App\Models\Rarity;
use App\Models\Shop\Shop;
use App\Models\Shop\ShopStock;
use App\Models\User\User;
Expand Down Expand Up @@ -220,6 +221,7 @@ public function getItemIndex(Request $request) {
public function getCreateItem() {
return view('admin.items.create_edit_item', [
'item' => new Item,
'rarities' => Rarity::orderBy('sort', 'DESC')->pluck('name', 'id'),
'categories' => ['none' => 'No category'] + ItemCategory::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'prompts' => Prompt::where('is_active', 1)->orderBy('id')->pluck('name', 'id'),
'userCurrencies' => Currency::where('is_user_owned', 1)->orderBy('sort_user', 'DESC')->pluck('name', 'id'),
Expand All @@ -242,6 +244,7 @@ public function getEditItem($id) {

return view('admin.items.create_edit_item', [
'item' => $item,
'rarities' => Rarity::orderBy('sort', 'DESC')->pluck('name', 'id'),
'categories' => ['none' => 'No category'] + ItemCategory::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'shops' => Shop::whereIn('id', ShopStock::where('item_id', $item->id)->pluck('shop_id')->unique()->toArray())->orderBy('sort', 'DESC')->get(),
'prompts' => Prompt::where('is_active', 1)->orderBy('id')->pluck('name', 'id'),
Expand All @@ -261,7 +264,7 @@ public function getEditItem($id) {
public function postCreateEditItem(Request $request, ItemService $service, $id = null) {
$id ? $request->validate(Item::$updateRules) : $request->validate(Item::$createRules);
$data = $request->only([
'name', 'allow_transfer', 'item_category_id', 'description', 'image', 'remove_image', 'rarity',
'name', 'allow_transfer', 'item_category_id', 'description', 'image', 'remove_image', 'rarity_id',
'reference_url', 'artist_id', 'artist_url', 'uses', 'shops', 'prompts', 'release', 'currency_id', 'currency_quantity',
'is_released', 'is_deletable',
]);
Expand Down
11 changes: 3 additions & 8 deletions app/Http/Controllers/Admin/Data/LootTableController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Models\Item\Item;
use App\Models\Item\ItemCategory;
use App\Models\Loot\LootTable;
use App\Models\Rarity;
use App\Services\LootService;
use Illuminate\Http\Request;

Expand Down Expand Up @@ -43,16 +44,13 @@ public function getIndex(Request $request) {
* @return \Illuminate\Contracts\Support\Renderable
*/
public function getCreateLootTable() {
$rarities = Item::whereNotNull('data')->get()->pluck('rarity')->unique()->toArray();
sort($rarities);

return view('admin.loot_tables.create_edit_loot_table', [
'table' => new LootTable,
'items' => Item::orderBy('name')->pluck('name', 'id'),
'categories' => ItemCategory::orderBy('sort', 'DESC')->pluck('name', 'id'),
'currencies' => Currency::orderBy('name')->pluck('name', 'id'),
'tables' => LootTable::orderBy('name')->pluck('name', 'id'),
'rarities' => array_filter($rarities),
'rarities' => Rarity::orderBy('sort')->pluck('name', 'id')->toArray(),
]);
}

Expand All @@ -69,16 +67,13 @@ public function getEditLootTable($id) {
abort(404);
}

$rarities = Item::whereNotNull('data')->get()->pluck('rarity')->unique()->toArray();
sort($rarities);

return view('admin.loot_tables.create_edit_loot_table', [
'table' => $table,
'items' => Item::orderBy('name')->pluck('name', 'id'),
'categories' => ItemCategory::orderBy('sort', 'DESC')->pluck('name', 'id'),
'currencies' => Currency::orderBy('name')->pluck('name', 'id'),
'tables' => LootTable::orderBy('name')->pluck('name', 'id'),
'rarities' => array_filter($rarities),
'rarities' => Rarity::orderBy('sort')->pluck('name', 'id')->toArray(),
]);
}

Expand Down
2 changes: 1 addition & 1 deletion app/Http/Middleware/ParsePostRequestFields.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ParsePostRequestFields {
*/
public function handle(Request $request, Closure $next) {
if ($request->isMethod('post')) {
$excludedFields = ['_token', 'password', 'email', 'description', 'text'];
$excludedFields = ['_token', 'password', 'email', 'description', 'text', 'criteria'];
$strippedFields = ['name', 'title'];

$parsedFields = [];
Expand Down
32 changes: 20 additions & 12 deletions app/Models/Item/Item.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Models\Model;
use App\Models\Prompt\Prompt;
use App\Models\Rarity;
use App\Models\Shop\Shop;
use App\Models\User\User;

Expand Down Expand Up @@ -32,15 +33,15 @@ class Item extends Model {
* @var array
*/
public static $createRules = [
'item_category_id' => 'nullable',
'name' => 'required|unique:items|between:3,100',
'description' => 'nullable',
'image' => 'mimes:png',
'rarity' => 'nullable',
'reference_url' => 'nullable|between:3,200',
'uses' => 'nullable|between:3,250',
'release' => 'nullable|between:3,100',
'currency_quantity' => 'nullable|integer|min:1',
'item_category_id' => 'nullable',
'name' => 'required|unique:items|between:3,100',
'description' => 'nullable',
'image' => 'mimes:png',
'rarity_id' => 'nullable',
'reference_url' => 'nullable|between:3,200',
'uses' => 'nullable|between:3,250',
'release' => 'nullable|between:3,100',
'currency_quantity' => 'nullable|integer|min:1',
];

/**
Expand Down Expand Up @@ -86,6 +87,13 @@ public function artist() {
return $this->belongsTo(User::class, 'artist_id');
}

/**
* Gets the item's rarity.
*/
public function rarity() {
return $this->belongsTo(Rarity::class, $this->attributes['rarity_id'] ?? null, 'id');
}

/**********************************************************************************************
SCOPES
Expand Down Expand Up @@ -295,12 +303,12 @@ public function getDataAttribute() {
*
* @return string
*/
public function getRarityAttribute() {
if (!isset($this->data) || !isset($this->data['rarity'])) {
public function getRarityIdAttribute() {
if (!isset($this->data) || !isset($this->data['rarity_id'])) {
return null;
}

return $this->data['rarity'];
return $this->data['rarity_id'];
}

/**
Expand Down
35 changes: 27 additions & 8 deletions app/Models/Loot/LootTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Models\Item\Item;
use App\Models\Model;
use App\Models\Rarity;

class LootTable extends Model {
/**
Expand Down Expand Up @@ -167,11 +168,20 @@ public function rollCategory($id, $quantity = 1, $criteria = null, $rarity = nul
$rewards = createAssetsArray();

if (isset($criteria) && $criteria && isset($rarity) && $rarity) {
if (config('lorekeeper.extensions.item_entry_expansion.loot_tables.alternate_filtering')) {
$loot = Item::where('item_category_id', $id)->released()->whereNotNull('data')->where('data->rarity', $criteria, $rarity)->get();
} else {
$loot = Item::where('item_category_id', $id)->released()->whereNotNull('data')->whereRaw('JSON_EXTRACT(`data`, \'$.rarity\')'.$criteria.$rarity)->get();
$rarity = Rarity::find($rarity);
if (!$rarity) {
throw new \Exception('Invalid rarity!');
}

$loot = Item::where('item_category_id', $id)->released()->whereNotNull('data')->get()->filter(function ($item) use ($criteria, $rarity) {
$itemRarity = Rarity::find($item->data['rarity_id'] ?? null);
if (!$itemRarity) {
return false;
}

// check the sort order of the rarity
return eval('return '.$itemRarity->sort.$criteria.$rarity->sort.';');
})->values();
} else {
$loot = Item::where('item_category_id', $id)->released()->get();
}
Expand Down Expand Up @@ -206,11 +216,20 @@ public function rollCategory($id, $quantity = 1, $criteria = null, $rarity = nul
public function rollRarityItem($quantity, $criteria, $rarity) {
$rewards = createAssetsArray();

if (config('lorekeeper.extensions.item_entry_expansion.loot_tables.alternate_filtering')) {
$loot = Item::released()->whereNotNull('data')->where('data->rarity', $criteria, $rarity)->get();
} else {
$loot = Item::released()->whereNotNull('data')->whereRaw('JSON_EXTRACT(`data`, \'$.rarity\')'.$criteria.$rarity)->get();
$rarity = Rarity::find($rarity);
if (!$rarity) {
throw new \Exception('Invalid rarity!');
}

$loot = Item::released()->whereNotNull('data')->get()->filter(function ($item) use ($criteria, $rarity) {
$itemRarity = Rarity::find($item->data['rarity_id'] ?? null);
if (!$itemRarity) {
return false;
}

// check the sort order of the rarity
return eval('return '.$itemRarity->sort.$criteria.$rarity->sort.';');
})->values();
if (!$loot->count()) {
throw new \Exception('There are no items to select from!');
}
Expand Down
20 changes: 10 additions & 10 deletions app/Services/ItemService.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,11 @@ public function createItem($data, $user) {

$item->update([
'data' => json_encode([
'rarity' => isset($data['rarity']) && $data['rarity'] ? $data['rarity'] : null,
'uses' => isset($data['uses']) && $data['uses'] ? $data['uses'] : null,
'release' => isset($data['release']) && $data['release'] ? $data['release'] : null,
'prompts' => isset($data['prompts']) && $data['prompts'] ? $data['prompts'] : null,
'resell' => isset($data['currency_quantity']) ? [$data['currency_id'] => $data['currency_quantity']] : null,
'uses' => isset($data['uses']) && $data['uses'] ? $data['uses'] : null,
'release' => isset($data['release']) && $data['release'] ? $data['release'] : null,
'prompts' => isset($data['prompts']) && $data['prompts'] ? $data['prompts'] : null,
'resell' => isset($data['currency_quantity']) ? [$data['currency_id'] => $data['currency_quantity']] : null,
'rarity_id' => isset($data['rarity_id']) && $data['rarity_id'] ? $data['rarity_id'] : null,
]), // rarity, availability info (original source, purchase locations, drop locations)
]);

Expand Down Expand Up @@ -279,11 +279,11 @@ public function updateItem($item, $data, $user) {

$item->update([
'data' => json_encode([
'rarity' => isset($data['rarity']) && $data['rarity'] ? $data['rarity'] : null,
'uses' => isset($data['uses']) && $data['uses'] ? $data['uses'] : null,
'release' => isset($data['release']) && $data['release'] ? $data['release'] : null,
'prompts' => isset($data['prompts']) && $data['prompts'] ? $data['prompts'] : null,
'resell' => isset($data['currency_quantity']) ? [$data['currency_id'] => $data['currency_quantity']] : null,
'uses' => isset($data['uses']) && $data['uses'] ? $data['uses'] : null,
'release' => isset($data['release']) && $data['release'] ? $data['release'] : null,
'prompts' => isset($data['prompts']) && $data['prompts'] ? $data['prompts'] : null,
'resell' => isset($data['currency_quantity']) ? [$data['currency_id'] => $data['currency_quantity']] : null,
'rarity_id' => isset($data['rarity_id']) && $data['rarity_id'] ? $data['rarity_id'] : null,
]), // rarity, availability info (original source, purchase locations, drop locations)
]);

Expand Down
2 changes: 1 addition & 1 deletion app/Services/LootService.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public function updateLootTable($table, $data) {
if (!$type) {
throw new \Exception('Loot type is required.');
}
if ($type != 'ItemRarity' && !$data['rewardable_id'][$key]) {
if (($type != 'ItemRarity' && $type != 'ItemCategoryRarity') && !$data['rewardable_id'][$key]) {
throw new \Exception('Reward is required.');
}
if (!$data['quantity'][$key] || $data['quantity'][$key] < 1) {
Expand Down
1 change: 0 additions & 1 deletion config/lorekeeper/extensions.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
'loot_tables' => [
// Adds the ability to use either rarity criteria for items or item categories with rarity criteria in loot tables. Note that disabling this does not apply retroactively.
'enable' => 0,
'alternate_filtering' => 0, // By default this uses more broadly compatible methods to filter by rarity. If you are on Dreamhost/know your DB software can handle searching in JSON, it's recommended to set this to 1 instead.
],
],

Expand Down
4 changes: 2 additions & 2 deletions resources/views/admin/items/create_edit_item.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
</div>
@if (config('lorekeeper.extensions.item_entry_expansion.extra_fields'))
<div class="col-md form-group">
{!! Form::label('Item Rarity (Optional)') !!} {!! add_help('This should be a number.') !!}
{!! Form::number('rarity', $item && $item->rarity ? $item->rarity : '', ['class' => 'form-control']) !!}
{!! Form::label('Item Rarity (Optional)') !!}
{!! Form::select('rarity_id', $rarities, $item && $item->rarityId ? $item->rarityId : '', ['class' => 'form-control', 'placeholder' => 'Select a Rarity']) !!}
</div>
@endif
</div>
Expand Down
5 changes: 5 additions & 0 deletions resources/views/admin/rarities/rarities.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
</tbody>

</table>
@if (config('lorekeeper.extensions.item_entry_expansion.extra_fields') && config('lorekeeper.extensions.item_entry_expansion.loot_tables.enable'))
<div class="alert alert-info mb-2">
Note that changing the order of rarities may have unintended consequences on criteria loot tables.
</div>
@endif
<div class="mb-4">
{!! Form::open(['url' => 'admin/data/rarities/sort']) !!}
{!! Form::hidden('sort', '', ['id' => 'sortableOrder']) !!}
Expand Down
2 changes: 1 addition & 1 deletion resources/views/world/_item_entry.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
@if (config('lorekeeper.extensions.item_entry_expansion.extra_fields'))
@if (isset($item->rarity) && $item->rarity)
<div class="col-md">
<p><strong>Rarity:</strong> {!! $item->rarity !!}</p>
<p><strong>Rarity:</strong> {!! $item->rarity->displayName !!}</p>
</div>
@endif
@if (isset($item->itemArtist) && $item->itemArtist)
Expand Down
Loading

0 comments on commit 76fef05

Please sign in to comment.