Item updater#871
Conversation
+ refactors ItemMetaConverterHack
This means functions can predicate potentially expensive followup code (like updating the client about something) on whether there's something to update.
This was considered too speculative. Unfortunately, there's really no other way to achieve the same type-limiting goal (ChatGPT tells me this concept is also called "Programming to an Interface", "Information Hiding", and "Minimized Coupling") without delving into NMS, which *does* have the interface (weird how PaperMC doesn't). This does means that migrators can cast the NMS DataComponentHolder back to an NMS ItemStack... but then again migrators could have reflectively accessed the internal item within ItemDataComponentHolder, so 🤷♂️.
I was basically fretting over this because I knew, I just knew, that someone would write an ItemStack migration that's chock full of ItemUtils calls instead of a ItemMeta migration with MetaUtils calls. That said, even though I do honestly believe that a migration context should be more limited, it's still nonetheless an abstraction on top of ItemUpdater, which unashamedly updates ItemStacks. That said, I've annotated the ItemStack migration interface as experimental and denied it a convenient registration shortcut as a form of "friction as a deterrent".
This will help with doing befores/afters with item updates
This uses the event-driven nature of ItemUpdater and the default implementations, effectively using Bukkit's event system as a pseudo itemUpdater registry. Which is a bit silly but preferable to the alternative.
Per request, this has been removed as unnecessary, that the HashMap overhead is fine.
Turns out the Bukkit method is pretty convenient.
The "items" package was *right there*, why wasn't the "CustomItem" class put in there? 😭
Genuinely cannot wait for JEP Draft 8303099 to be merged 🫠
Plus added a deliberate NMS item migration option
This removes the impl package
This will allow custom items and compact items to have independent version values.
# Conflicts: # plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/ArmourModifier.java # plugins/heliodor-paper/src/main/java/net/civmc/heliodor/meteoriciron/FactoryUpgrade.java # plugins/heliodor-paper/src/main/java/net/civmc/heliodor/meteoriciron/MeteoricIron.java
okay but actually this time
| import vg.civcraft.mc.civmodcore.inventory.items.updater.migrations.ItemMigration; | ||
| import vg.civcraft.mc.civmodcore.inventory.items.updater.migrations.ItemMigrations; | ||
|
|
||
| public abstract class CustomItemsUpdater implements ItemUpdater { |
There was a problem hiding this comment.
How is this class initialised?
There was a problem hiding this comment.
Its really weird. The class is meant to be extended, by a migrator for an item, which then calls
itself, or something similar to that.There was a problem hiding this comment.
Yup. Give #754 a read through. It started off as a really simple predicate-with-side-effects: it takes an item and returns a boolean of whether the item was modified or not. You'd do all your checks within that predicate, returning early wherever possible.
Then I added event handler classes which handle specific events and updating their respective items. For example, UpdatePlayerItemsOnJoin listens for player joins and then invokes the predicate for each item in the player's inventory and ender chest. UpdateInventoryItemsOnOpen listens for players opening chests (and other contains), updating its items. UpdateContainerItemsOnLoad listens for chunk loads and updates all the items in its container blocks.
package uk.protonull.civ.example;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import vg.civcraft.mc.civmodcore.ACivMod;
import vg.civcraft.mc.civmodcore.inventory.items.updater.listeners.UpdatePlayerItemsOnJoin;
public final class ExamplePlugin extends ACivMod {
@Override
public void onEnable() {
super.onEnable();
registerListener(new ItemUpdateListener());
}
private static final class ItemUpdateListener implements UpdatePlayerItemsOnJoin /** , UpdateInventoryItemsOnOpen */ {
@Override
public void updateItem(final @NotNull ItemStack item) {
// if (item.needsUpdating()) {
// item.updateSomehow();
// }
}
}
}If you wanted your item-updater to run whenever a chest was opened, all you needed to do was uncomment that implements part. All the work was done for you.
However, Okx wanted more of a DataFixer-esque system. Take a look at net.minecraft.util.datafix.DataFixers for what that looks like. As mentioned here: instead of entirely replacing what was already built, I instead created the CustomItemUpdater as a layer of abstraction on-top of it. This meant there was a simpler system if you wanted it, and a complexer system if you needed it.
However, I do admit that the type shenanigans were a mistake. It seemed clever at the time but it's definitely a struggle to read, even for me now and I wrote it. Basically, since UpdatePlayerItemsOnJoin extends ItemUpdater but does not implement it, you can effectively combine/union them with an implementation of ItemUpdater via casting, eg:
package uk.protonull.civ.example;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import vg.civcraft.mc.civmodcore.ACivMod;
import vg.civcraft.mc.civmodcore.inventory.items.updater.listeners.UpdatePlayerItemsOnJoin;
public final class ExamplePlugin extends ACivMod {
@Override
public void onEnable() {
super.onEnable();
registerListener((UpdatePlayerItemsOnJoin) ExamplePlugin::updateItem);
}
private static void updateItem(final @NotNull ItemStack item) {
// if (item.needsUpdating()) {
// item.updateSomehow();
// }
}
}| ) { | ||
| // This is a deliberate 0th migration that ensures that any item | ||
| // being migrated has a custom-item key and item version. | ||
| this.migrations.put(0, (ItemMigration.OfItem) (item) -> { |
There was a problem hiding this comment.
is a first migration necessary?
There was a problem hiding this comment.
My understanding that items like fossils are registered custom items, so they would need a migration like this to be put into the system essentially. The problem is I have no idea how they are targeted by a migration if they lack a custom item key. IIRC the migration code just looks for items with a custom item key?
There was a problem hiding this comment.
There are two purposes to the item updater system:
-
Provide a means to update legacy custom items (those that are "custom" by virtue of having names and/or lore) to be registered custom items.
-
Provide a means to update registered custom items through migrations.
The 0th migration just ensures that EVERY registered custom item has both a custom-item key and migration version, as stated explicitly in the comments. It establishes a baseline of what data can be accessed from the item without concern for NPEs. It also prevents stacking issues where one person's item has an explicit 0th migration-version set, and another whose 0th migration-version is implied by its absence (related: null != emptyList()).
The 0th migration is not strictly necessary but it does offer a benefit, "speculative" as that may be. That said, I maintain that the vast majority of this migration system would be utterly unnecessary if Okx simply agreed to permit ephemeral display data (ie, setting names and lore to items at network-time): this whole migration system exists to fix typos in lore, or to remove outdated parts of lore, etc. It's silly.
|
To clean up the api, and make the usage clearer, I made a demo implementation Huskydog9988#1 |
Reopening #754 in preparation of custom item rework