Skip to content

refactor: change Limit, Reward, and Loot into polymorphic traits - #1509

Open
perappu wants to merge 5 commits into
lk-arpg:unstablefrom
perappu:pr/polymorphic-limits
Open

refactor: change Limit, Reward, and Loot into polymorphic traits#1509
perappu wants to merge 5 commits into
lk-arpg:unstablefrom
perappu:pr/polymorphic-limits

Conversation

@perappu

@perappu perappu commented Jul 3, 2026

Copy link
Copy Markdown
Member

I'm back at it again with another big ol' refactor! This time, I have changed the Limit, Reward, and Loot models to be actually polymorphic.

"Hey Toko, what does that even mean?"

They're not very prevalent in LK, so I'd like to take some time to explain the concept of polymorphism and how it applies to us.

In the Limit model, we have an if statement that compares the value of limit_type to certain strings. Based on those certain strings, it will return a different model, such as an Item vs a Currency. These relationships with "changing" model types are called a polymorphic relationship.

The problem is that, all this time, we have essentially been reinventing the wheel -- and to our detriment.

The if statement with limit_type is evaluated at runtime. What this means is that Laravel can not pull the reward ahead of time. More or less, every time the limit() relationship is called, it has to re-query the database. In some cases, this can lead to a massive amount of duplicate queries, when either processing limits or displaying them. It's also just generally inefficient, and requires a lot of strange workarounds.

Fortunately, Laravel provides some excellent ways for us to create "one-to-many" polymorphic relationship the proper way. By using relationships such as morphTo() instead of belongsTo(), we can define these relationships in a way that can be eager loaded and overall handled far more simply.

As part of this, I have also added "Rewardable" and "Limitable" traits. A trait is basically just a shorthand saying, "hey, attach these methods onto this model". These allow us to load the limits and rewards attached to any given object with an actual model relationship, and should be added to anything that uses limits/rewards.


Here is a summary of all the changes that this PR makes. As always, I tried to add good inline comments explaining everything, but I figured this couldn't hurt too. I have bolded/italicized any changes that may require changes to existing extensions/custom code.

Morph Map

  • A dynamically constructed morph map has been added to the boot() method in AppServiceProvider.
  • This directly maps aliases (i.e., 'item', 'currency') with a model string (i.e., \App\Models\Item\Item and \App\Models\Currency\Currency in a polymorphic model relationship.
    • This way, we do not need to modify the rewardable_type, etc in existing tables to make them polymorphic.
  • Additional logic (mostly to do with limits and loot tables) has been added to getAssetModelString() to assist with converting aliases to their model strings.
    • Please be mindful to preserve the new code when merging changes in AssetHelpers.
    • Based on your extensions or custom code, you may need to add your own logic to getAssetModelString() as well. More details below.

Limits

  • The limit() relationship on the Limit is now polymorphic. It finds the appropriate model based on the morph map.
    • Limit types outside of core will need to be added to getAssetModelString.
    • Essentially, the if statement logic that matches the limit_type to the model needs to be moved to the switch statement in getAssetModelString.
    • If you have additional logic that happens after the belongsTo(), it will need to be added as a constraint.
  • A new value for dynamic has been added to getAssetModelString() to account for the above.
  • The new trait Limitable has been added. The Limitable trait must be added to any model that uses limits.
    • You can view the Prompt model as an example of how to implement this.
  • The old object relationship has been preserved for backwards compatibility. limitable() and object() can be used interchangeably.
  • Various versions of the getLimits() and hasLimits() methods have been changed to leverage the new relationship.
  • The limit() relationship is always eager loaded (because you're basically going to need to retrieve it every time anyway).

To make it super clear what changes are required for custom limit types, here is an example with the user_level and character_level limit types from Claymores & Companions:
image

Rewards

  • The reward() relationship on the Reward is now polymorphic. It finds the appropriate model based on the morph map.
    • You almost certainly already have all the required logic for this in getAssetModelString() and probably don't need to do anything.
    • However, if you run into errors with the reward relationship, it may be worth verifying.
  • The new trait Rewardable has been added. The Rewardable trait must be added to any model that uses rewards.
    • You can view the Prompt model as an example of how to implement this.
  • The old object relationship has been preserved for backwards compatibility. rewardable() and object() can be used interchangeably.
  • Various versions of the getRewards() and hasRewards() methods have been changed to leverage the new relationship.
  • The reward() relationship is always eager loaded (see above).

Prompts

  • Prompts have had the Limitable and Rewardable traits added to them.
  • rewards() has been removed from the Prompt model, as it is now redundant.
  • Eager loading has been added in various places to leverage the new traits.

Loot Tables

  • The reward relation on the Loot model is now polymorphic.
  • Extension/custom loot types must be listed in the new file config/loot_types.php. These are used in building the morph map. They will be added similarly to custom limit types.
  • A new value for itemcategory and itemcategoryrarity has been added to getAssetModelString() to account for the above.
  • I didn't refactor the dropdowns in the create_edit_loot_table blade to use the config. I considered it, but it felt a little out of scope. It could be another PR at a later date...?

...and I think that covers everything! Thanks for taking the time to read this.

@itinerare itinerare added enhancement New feature or request needs review Pull requests that are pending community review labels Jul 3, 2026

@liwoyadan liwoyadan left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

polymorphs AND traits...life can be so so beautiful

@ScuffedNewt

Copy link
Copy Markdown
Contributor

Very nice! Requesting Limitable be added to shops since it also uses limits out of the box iirc

@perappu

perappu commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Very nice! Requesting Limitable be added to shops since it also uses limits out of the box iirc

good catch LOL I knew there was something with limits that I was forgetting.

@Draconizations

Copy link
Copy Markdown

I don't have the confidence to review this myself, but this is very good work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request needs review Pull requests that are pending community review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants