Improved soulbound.can-drop option to support moving items around in chests, dispensers...

This commit is contained in:
Jules 2024-06-21 17:43:35 -07:00
parent cef7d0163a
commit c71f673bc4
7 changed files with 177 additions and 30 deletions

View File

@ -192,22 +192,16 @@ public class MMOItemTemplate implements ItemReference, PreloadedObject {
return options.contains(option); return options.contains(option);
} }
public MMOItemBuilder newBuilder(@Nullable Player player) {
if (player != null) {
return newBuilder(PlayerData.get(player).getRPG());
}
return newBuilder((RPGPlayer) null);
}
public MMOItemBuilder newBuilder() { public MMOItemBuilder newBuilder() {
return newBuilder((RPGPlayer) null); return newBuilder((RPGPlayer) null);
} }
public MMOItemBuilder newBuilder(@Nullable PlayerData player) { public MMOItemBuilder newBuilder(@Nullable Player player) {
if (player != null) { return newBuilder(player == null ? null : PlayerData.get(player).getRPG());
return newBuilder(player.getRPG());
} }
return newBuilder((RPGPlayer) null);
public MMOItemBuilder newBuilder(@Nullable PlayerData player) {
return newBuilder(player == null ? null : player.getRPG());
} }
/** /**
@ -233,12 +227,11 @@ public class MMOItemTemplate implements ItemReference, PreloadedObject {
* @param forDisplay Should it take modifiers into account * @param forDisplay Should it take modifiers into account
* @return Item builder with random level and tier? * @return Item builder with random level and tier?
*/ */
@NotNull
public MMOItemBuilder newBuilder(@Nullable RPGPlayer player, boolean forDisplay) { public MMOItemBuilder newBuilder(@Nullable RPGPlayer player, boolean forDisplay) {
// No player ~ default settings // No player ~ default settings
if (player == null) { if (player == null) return newBuilder(0, null);
return newBuilder(0, null);
}
// Read from player // Read from player
int itemLevel = hasOption(TemplateOption.LEVEL_ITEM) ? MMOItems.plugin.getTemplates().rollLevel(player.getLevel()) : 0; int itemLevel = hasOption(TemplateOption.LEVEL_ITEM) ? MMOItems.plugin.getTemplates().rollLevel(player.getLevel()) : 0;
@ -251,6 +244,7 @@ public class MMOItemTemplate implements ItemReference, PreloadedObject {
* @param itemTier The desired item tier, can be null * @param itemTier The desired item tier, can be null
* @return Item builder with specific item level and tier * @return Item builder with specific item level and tier
*/ */
@NotNull
public MMOItemBuilder newBuilder(int itemLevel, @Nullable ItemTier itemTier) { public MMOItemBuilder newBuilder(int itemLevel, @Nullable ItemTier itemTier) {
return new MMOItemBuilder(this, itemLevel, itemTier); return new MMOItemBuilder(this, itemLevel, itemTier);
} }

View File

@ -36,6 +36,11 @@ public class MMOUtils {
return particle.getDataType() == Particle.DustOptions.class; return particle.getDataType() == Particle.DustOptions.class;
} }
/**
* Optimized Soulbound check based on the fact that the
* compressed item Soulbound data contains only one UUID,
* the target player's UUID, sparing one Json parse pass.
*/
public static boolean isSoulboundTo(@NotNull NBTItem item, @NotNull Player player) { public static boolean isSoulboundTo(@NotNull NBTItem item, @NotNull Player player) {
final @Nullable String foundNbt = item.getString("MMOITEMS_SOULBOUND"); final @Nullable String foundNbt = item.getString("MMOITEMS_SOULBOUND");
return foundNbt != null && foundNbt.contains(player.getUniqueId().toString()); return foundNbt != null && foundNbt.contains(player.getUniqueId().toString());

View File

@ -6,6 +6,7 @@ import net.Indyuce.mmoitems.comp.PhatLootsHook;
import net.Indyuce.mmoitems.gui.listener.GuiListener; import net.Indyuce.mmoitems.gui.listener.GuiListener;
import net.Indyuce.mmoitems.listener.*; import net.Indyuce.mmoitems.listener.*;
import net.Indyuce.mmoitems.listener.option.DroppedItems; import net.Indyuce.mmoitems.listener.option.DroppedItems;
import net.Indyuce.mmoitems.listener.option.SoulboundNoDrop;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
public class MMOItemsBukkit { public class MMOItemsBukkit {
@ -33,6 +34,9 @@ public class MMOItemsBukkit {
if (plugin.getLanguage().disableRemovedItems) if (plugin.getLanguage().disableRemovedItems)
Bukkit.getPluginManager().registerEvents(new DisabledItemsListener(plugin), plugin); Bukkit.getPluginManager().registerEvents(new DisabledItemsListener(plugin), plugin);
if (!plugin.getConfig().getBoolean("soulbound.can-drop"))
Bukkit.getPluginManager().registerEvents(new SoulboundNoDrop(), plugin);
// Profile support // Profile support
if (MythicLib.plugin.hasProfiles()) if (MythicLib.plugin.hasProfiles())
Bukkit.getPluginManager().registerEvents(new ProfileSupportListener(), plugin); Bukkit.getPluginManager().registerEvents(new ProfileSupportListener(), plugin);

View File

@ -21,7 +21,6 @@ import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.inventory.PrepareItemCraftEvent; import org.bukkit.event.inventory.PrepareItemCraftEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.CraftingInventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -137,13 +136,6 @@ public class ItemListener implements Listener {
if (newItem != null) event.setCurrentItem(newItem); if (newItem != null) event.setCurrentItem(newItem);
} }
@EventHandler(ignoreCancelled = true)
private void dropItem(PlayerDropItemEvent event) {
NBTItem nbt = NBTItem.get(event.getItemDrop().getItemStack());
if (!MMOItems.plugin.getConfig().getBoolean("soulbound.can-drop") && nbt.hasTag("MMOITEMS_SOULBOUND"))
event.setCancelled(true);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void playerJoin(PlayerJoinEvent event) { public void playerJoin(PlayerJoinEvent event) {
if (!MythicLib.plugin.hasProfiles()) updateInventory(event.getPlayer()); if (!MythicLib.plugin.hasProfiles()) updateInventory(event.getPlayer());

View File

@ -81,11 +81,6 @@ public class PlayerListener implements Listener {
final ItemStack item = iterator.next(); final ItemStack item = iterator.next();
final NBTItem nbt = NBTItem.get(item); final NBTItem nbt = NBTItem.get(item);
/*
* Not a perfect check but it's very sufficient and so we avoid
* using a JsonParser followed by map checkups in the SoulboundData
* constructor
*/
if (nbt.getBoolean("MMOITEMS_DISABLE_DEATH_DROP") || (MMOItems.plugin.getLanguage().keepSoulboundOnDeath && MMOUtils.isSoulboundTo(nbt, player))) { if (nbt.getBoolean("MMOITEMS_DISABLE_DEATH_DROP") || (MMOItems.plugin.getLanguage().keepSoulboundOnDeath && MMOUtils.isSoulboundTo(nbt, player))) {
iterator.remove(); iterator.remove();
soulboundInfo.registerItem(item); soulboundInfo.registerItem(item);

View File

@ -0,0 +1,154 @@
package net.Indyuce.mmoitems.listener.option;
import io.lumine.mythic.lib.api.item.NBTItem;
import net.Indyuce.mmoitems.util.MMOUtils;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
/**
* Useful Resources:
* - <a href="https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/event/inventory/InventoryAction.html">...</a>
* - <a href="https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/event/inventory/InventoryType.html">...</a>
*
* @author Jules
*/
public class SoulboundNoDrop implements Listener {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void cannotDrop(PlayerDropItemEvent event) {
if (isBound(event.getItemDrop().getItemStack(), event.getPlayer())) event.setCancelled(true);
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void cannotDragAround(InventoryDragEvent event) {
if (event.getView().getType() == InventoryType.CRAFTING) return;
// This easily allows to check if the item was dragged in or out of the player's inventory
final int topInventorySize = event.getView().getTopInventory().getContents().length;
for (Map.Entry<Integer, ItemStack> entry : event.getNewItems().entrySet())
if (entry.getKey() < topInventorySize && isBound(entry.getValue(), event.getWhoClicked())) {
event.setCancelled(true);
return;
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void cannotMoveAround(InventoryClickEvent event) {
// Can only move around in
if (event.getView().getType() == InventoryType.CRAFTING) return;
try {
// Depends on click and inventory type.
final boolean result = isSafe(event);
if (!result) event.setCancelled(true);
} catch (RuntimeException exception) {
// Safe check...
if (isBound(event.getCurrentItem(), event.getWhoClicked()) || isBound(event.getCursor(), event.getWhoClicked()))
event.setCancelled(true);
}
}
private boolean isSafe(@NotNull InventoryClickEvent event) {
switch (event.getAction()) {
/*
* Pickups
*/
case NOTHING: // 'Nothing happens' is safe enough
case PICKUP_ALL: // Can pickup any item
case PICKUP_SOME:
case PICKUP_HALF:
case PICKUP_ONE:
case COLLECT_TO_CURSOR: // Considered a pickup
case CLONE_STACK: // (Creative) Clones currentItem into cursor. Considered a pickup
case HOTBAR_MOVE_AND_READD: // Some is given to the player, but not the target inventory, hence safe!
return true;
/*
* Drop cursor. Check cursor
*/
case DROP_ONE_CURSOR: // Check cursor (dropped)
case DROP_ALL_CURSOR:
return !isBound(event.getCursor(), event.getWhoClicked());
/*
* Drop current item. Check current item
*/
case DROP_ALL_SLOT: // Check current item (dropped)
case DROP_ONE_SLOT:
return !isBound(event.getCurrentItem(), event.getWhoClicked());
/*
* Places. Check cursor only if place is in remove inventory
*/
case SWAP_WITH_CURSOR:
case PLACE_ALL:
case PLACE_SOME:
case PLACE_ONE: {
// Can place any item in player's inventory
if (event.getClickedInventory().getType() == InventoryType.PLAYER) return true;
// Only accepted if the item is not soulbound
return !isBound(event.getCursor(), event.getWhoClicked());
}
/*
* Swap with hotbar. Check hotbar item only if
* swap is done with remote inventory
*/
case HOTBAR_SWAP: {
// Can place any item in player's inventory
if (event.getClickedInventory().getType() == InventoryType.PLAYER) return true;
// Check hotbar
final ItemStack hotbarItem = event.getWhoClicked().getInventory().getItem(event.getHotbarButton());
return !isBound(hotbarItem, event.getWhoClicked());
}
/*
* Shift click item move. Check current item only if
* being placed in remove inventory
*/
case MOVE_TO_OTHER_INVENTORY: {
// Can move anything to player's inventory
if (event.getClickedInventory().getType() != InventoryType.PLAYER) return true;
// Check current item
return !isBound(event.getCurrentItem(), event.getWhoClicked());
}
/*
* For anything else, check both current item and cursor for safeguard.
* Maybe caused by 1.20.6+ inventory actions and other plugins.
*/
case UNKNOWN:
default:
throw new RuntimeException("Not implemented");
}
}
private boolean isBound(@Nullable ItemStack item, @NotNull HumanEntity player) {
return item != null && item.hasItemMeta() && MMOUtils.isSoulboundTo(NBTItem.get(item), (Player) player);
}
}

View File

@ -125,12 +125,15 @@ soulbound:
base: 1 base: 1
per-lvl: 1 per-lvl: 1
# Whether or not soulbound items should be # Whether soulbound items should be
# kept when a player dies. # kept when a player dies.
keep-on-death: true keep-on-death: true
# Whether or not soulbound item can be # [Experimental feature]
# dropped by the player # When toggled off, players cannot drop or take
# Soulbound items away from their inventory.
# Requires `keep-on-death` enabled.
# Changes apply on server restart.
can-drop: true can-drop: true
# Enable, disable, and customize the weapon effects here. # Enable, disable, and customize the weapon effects here.