Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7c59ad6
Add ItemUpdater interface
Protonull Feb 4, 2025
ded68a4
Rename since the inventory might not be of a container
Protonull Feb 8, 2025
ffdf075
Have updateItem() return whether the item was updated
Protonull Feb 8, 2025
bead778
Add item migrations system
Protonull Feb 11, 2025
9d12618
A few javadoc additions
Protonull Feb 11, 2025
ab8db1c
Remove DataComponentHolder
Protonull Feb 11, 2025
09ef62a
Add ItemStack migration type
Protonull Feb 11, 2025
aace5d6
fix plugin name
okx-code Feb 11, 2025
d586a4f
Use same plugin name as CustomItem class
Protonull Feb 11, 2025
7e6dbbd
Add alternative CustomItemsUpdater class
Protonull Feb 11, 2025
28197d7
Add debug command
Protonull Feb 12, 2025
dd5e296
Add item updater command
Protonull Feb 12, 2025
c3cbcd0
Remove CustomItemUpdater
Protonull Feb 21, 2025
4f8abf5
Remove EventUtils convenience shortcut
Protonull Feb 21, 2025
f9f8f0f
Merge branch 'main' into item-updater
Protonull May 4, 2025
8c73175
Relocate CustomItem class to appropriate package
Protonull May 12, 2025
69c0e8a
Clean up CustomItem class
Protonull May 12, 2025
19f3e8c
Move migrations to custom items package
Protonull May 13, 2025
a5c5e66
Move item update events and command to relevant packages
Protonull May 13, 2025
fe5e240
Oops, quick 0th migration bugfix
Protonull May 13, 2025
d101628
Abstractify the ItemMigrations class more
Protonull May 15, 2025
2a590ef
merge main into item-updater
Huskydog9988 Oct 14, 2025
635fa31
fix debugging command
Huskydog9988 Oct 14, 2025
7f22e27
fix custom item import
Huskydog9988 Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.bukkit.inventory.ItemStack;
import vg.civcraft.mc.civmodcore.inventory.CustomItem;
import vg.civcraft.mc.civmodcore.inventory.items.custom.CustomItem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.event.EventHandler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import org.bukkit.entity.HumanEntity;
import vg.civcraft.mc.civmodcore.chat.dialog.DialogManager;
import vg.civcraft.mc.civmodcore.commands.ChunkMetaCommand;
import vg.civcraft.mc.civmodcore.commands.CommandHelpers;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
import vg.civcraft.mc.civmodcore.inventory.items.updater.command.ItemUpdaterCommand;
import vg.civcraft.mc.civmodcore.commands.StatCommand;
import vg.civcraft.mc.civmodcore.dao.DatabaseCredentials;
import vg.civcraft.mc.civmodcore.dao.ManagedDatasource;
Expand Down Expand Up @@ -75,6 +77,8 @@ public void onEnable() {
// Register commands
this.commands = new CommandManager(this);
this.commands.init();
CommandHelpers.enableCommandHelp(this.commands);
this.commands.registerCommand(new ItemUpdaterCommand());
this.commands.registerCommand(new ConfigCommand());
this.commands.registerCommand(new StatCommand());
this.commands.registerCommand(new ChunkMetaCommand());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import vg.civcraft.mc.civmodcore.inventory.CustomItem;
import vg.civcraft.mc.civmodcore.inventory.items.custom.CustomItem;
import vg.civcraft.mc.civmodcore.inventory.items.ItemMap;
import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils;
import vg.civcraft.mc.civmodcore.world.model.EllipseArea;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import vg.civcraft.mc.civmodcore.inventory.CustomItem;
import vg.civcraft.mc.civmodcore.inventory.items.custom.CustomItem;

/**
* Allows the storage and comparison of item stacks while ignoring their maximum possible stack sizes. This offers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static String getItemName(@Nullable final ItemStack item) {
* @param item The item to check.
* @return Returns true if the item can be interpreted as an empty slot.
*/
@Contract("null -> true")
public static boolean isEmptyItem(final ItemStack item) {
return item == null || item.getType() == Material.AIR;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package vg.civcraft.mc.civmodcore.inventory.items.custom;

import java.util.Objects;
import java.util.function.Supplier;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import vg.civcraft.mc.civmodcore.CivModCorePlugin;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;

/**
* Since Minecraft doesn't [yet] offer a means of registering custom item materials, this is the intended means of
* defining custom items in the meantime. Keep in mind that each custom item must correlate 1:1 with its key, ie, that
* custom-item keys should be treated like item materials. Do NOT use custom-item keys as custom-item categories, such
* as compacted items. You must always be able to receive the same item from the same key.
*/
public final class CustomItem {
public static NamespacedKey CUSTOM_ITEM_KEY = new NamespacedKey(JavaPlugin.getPlugin(CivModCorePlugin.class), "custom_item");

private static final Map<String, Supplier<ItemStack>> customItems = new HashMap<>();

public static void registerCustomItem(
final @NotNull String key,
final @NotNull Supplier<@NotNull ItemStack> factory
) {
Objects.requireNonNull(key);
Objects.requireNonNull(factory);
customItems.putIfAbsent(key, factory);
}

public static void registerCustomItem(
final @NotNull String key,
final @NotNull ItemStack template
) {
registerCustomItem(key, template::clone);
}

public static @Nullable ItemStack getCustomItem(
final @NotNull String key
) {
if (customItems.get(Objects.requireNonNull(key)) instanceof final Supplier<ItemStack> factory) {
final ItemStack item = factory.get();
setCustomItemKey(item, key);
return item;
}
return null;
}

/**
* Just remember that has-then-get is an anti-pattern: use {@link #isCustomItem(org.bukkit.inventory.ItemStack, String)}
* or {@link #getCustomItemKey(org.bukkit.inventory.ItemStack)} instead.
*/
public static boolean isCustomItem(
final ItemStack item
) {
return !ItemUtils.isEmptyItem(item) && item.getPersistentDataContainer().has(CUSTOM_ITEM_KEY);
}

public static boolean isCustomItem(
final ItemStack item,
final @NotNull String key
) {
return key.equals(getCustomItemKey(item));
}

public static @Nullable String getCustomItemKey(
final ItemStack item
) {
if (!ItemUtils.isEmptyItem(item)) {
return item.getPersistentDataContainer().get(CUSTOM_ITEM_KEY, PersistentDataType.STRING);
}
return null;
}

@ApiStatus.Internal
public static void setCustomItemKey(
final @NotNull ItemStack item,
final @NotNull String key
) {
item.editPersistentDataContainer((pdc) -> pdc.set(CUSTOM_ITEM_KEY, PersistentDataType.STRING, key));
}

public static @NotNull Set<@NotNull String> getRegisteredKeys() {
return Collections.unmodifiableSet(customItems.keySet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package vg.civcraft.mc.civmodcore.inventory.items.custom;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import vg.civcraft.mc.civmodcore.CivModCorePlugin;
import vg.civcraft.mc.civmodcore.inventory.items.updater.ItemUpdater;
import vg.civcraft.mc.civmodcore.inventory.items.updater.listeners.DefaultItemUpdaterListeners;
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 {
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected final Map<String, ItemMigrations> targets = new HashMap<>();

/**
* Retrieves a migration list for the given custom key, creating one if it didn't already exist.
*/
public @NotNull ItemMigrations getMigrationsFor(
final @NotNull String customKey
) {
return getMigrationsFor(Objects.requireNonNull(customKey), true);
}

/**
* Retrieves a migration list for the given custom key.
*
* @param createIfAbsent Whether to create the list if one doesn't already exist.
*/
@Contract("_, true -> !null")
public @Nullable ItemMigrations getMigrationsFor(
final @NotNull String customKey,
final boolean createIfAbsent
) {
Objects.requireNonNull(customKey);
if (createIfAbsent) {
return this.targets.computeIfAbsent(customKey, CustomItemMigrations::new);
}
return this.targets.get(customKey);
}

public void removeMigrationsFor(
final @NotNull String customKey
) {
final ItemMigrations migrations = this.targets.remove(Objects.requireNonNull(customKey));
if (migrations != null) {
migrations.clearMigrations();
}
}

public void clearMigrations() {
final List<ItemMigrations> migrations = List.copyOf(this.targets.values());
this.targets.clear();
for (final ItemMigrations migration : migrations) {
migration.clearMigrations();
}
}

/**
* <p>This is how this class determines whether a given item is a custom item. You can just override this with a simple
* call to {@link vg.civcraft.mc.civmodcore.inventory.items.custom.CustomItem#getCustomItemKey(org.bukkit.inventory.ItemStack)},
* but you may want to add additional logic to support legacy custom items.</p>
*
* <pre>{@code
* // Example
* @Override
* public @Nullable String getCustomKeyFrom(@NotNull ItemStack item) {
* if (CustomItem.getCustomItem(item) instanceof final String customKey) {
* return customKey;
* }
* if (item.getType() == Material.ENDER_EYE
* && ExampleUtils.hasPlainDisplayName(item, "Player Essence")
* && ExampleUtils.hasPlainLoreLine(item, "Activity reward used to fuel pearls")
* ) {
* return "player_essence";
* }
* if (item.getType() == Material.BONE_BLOCK
* && ExampleUtils.hasPlainDisplayName(item, "City Bastion")
* && ExampleUtils.hasPlainLoreLine(item, "City bastions block reinforcements and elytra")
* ) {
* return "city_bastion";
* }
* // etc
* return null;
* }
* }</pre>
*/
protected abstract @Nullable String getCustomKeyFrom(
@NotNull ItemStack item
);

@Override
public boolean updateItem(
final @NotNull ItemStack item
) {
final String customKey = getCustomKeyFrom(item);
if (customKey == null) {
return false;
}
final ItemMigrations migrations = this.targets.get(customKey);
if (migrations == null) {
return false;
}
return migrations.attemptMigration(item);
}

// ============================================================
// Defaults
// ============================================================

@Contract("_, _ -> param2")
public static <T extends CustomItemsUpdater> @NotNull T init(
final @NotNull JavaPlugin plugin,
final @NotNull T updater
) {
Bukkit.getPluginManager().registerEvents(DefaultItemUpdaterListeners.wrap(updater), plugin);
return updater;
}
}

final class CustomItemMigrations extends ItemMigrations {
private static final NamespacedKey VERSION_KEY = new NamespacedKey(JavaPlugin.getPlugin(CivModCorePlugin.class), "item_version");

public CustomItemMigrations(
final @NotNull String customKey
) {
// 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) -> {
CustomItem.setCustomItemKey(item, customKey);
setMigrationVersion(item, 0);
});
}

@Override
public @Nullable Integer getMigrationVersion(
final @NotNull ItemStack item
) {
// This is a readonly PDC
return item.getPersistentDataContainer().get(VERSION_KEY, PersistentDataType.INTEGER);
}

@Override
public void setMigrationVersion(
final @NotNull ItemStack item,
final int version
) {
item.editPersistentDataContainer((pdc) -> pdc.set(VERSION_KEY, PersistentDataType.INTEGER, version));
}
}
Loading
Loading