Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -4,9 +4,14 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.Translatable;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomData;
import org.bukkit.Material;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
Expand Down Expand Up @@ -48,16 +53,30 @@ public static String getItemName(@Nullable final ItemStack item) {
return item == null ? null : ChatUtils.stringify(Component.translatable(item));
}

/**
* Checks whether the given item can be interpreted as an empty slot.
*
* @param item The item to check.
* @return Returns true if the item can be interpreted as an empty slot.
*/
public static boolean isEmptyItem(final ItemStack item) {
/// Checks whether the given item can be interpreted as an empty slot.
///
/// @apiNote This is not merely a null-safe version of [org.bukkit.inventory.ItemStack#isEmpty()]: this does not
/// check the item's amount. The purpose of this function is to determine whether the item is safe to
/// operate on (a stack of buttons with -2 amount can still have its display name set).
@Contract("null -> true")
public static boolean isEmptyItem(
final ItemStack item
) {
return item == null || item.getType() == Material.AIR;
}

/// Checks whether the given item can be interpreted as an empty slot.
///
/// @apiNote This is not merely a null-safe version of [net.minecraft.world.item.ItemStack#isEmpty()]: this does
/// not check the item's amount. The purpose of this function is to determine whether the item is safe to
/// operate on (a stack of buttons with -2 amount can still have its display name set).
@Contract("null -> true")
public static boolean isEmptyItem(
final net.minecraft.world.item.ItemStack item
) {
return item == null || item == net.minecraft.world.item.ItemStack.EMPTY || item.getItem() == Items.AIR;
}

/**
* Checks if an ItemStack is valid. An ItemStack is considered valid if when added to an inventory, it shows as an
* item with an amount within appropriate bounds. Therefore {@code new ItemStack(Material.AIR)} will not be
Expand Down Expand Up @@ -151,25 +170,21 @@ public static boolean areItemsSimilar(@Nullable final ItemStack former,
return MetaUtils.areMetasEqual(former.getItemMeta(), latter.getItemMeta());
}

/**
* Returns the NMS version of a given item, preferring the item's craft handle but will fall back upon creating an
* NMS copy.
*
* @param item The item to get the NMS version of.
* @return The NMS version, either handle or copy.
*/
@Contract("!null -> !null")
@Nullable
public static net.minecraft.world.item.ItemStack getNMSItemStack(@Nullable final ItemStack item) {
/// Unwraps a given Bukkit item to retrieve the internal NMS item.
///
/// @return Returns the internal NMS item, or null. Will return null if the given item is "empty" (as determined by
/// [#isEmptyItem]).
public static @Nullable net.minecraft.world.item.ItemStack getNMSItemStack(
final ItemStack item
) {
if (item == null) {
return null;
}
if (item instanceof CraftItemStack craftItem) {
if (craftItem.handle != null) {
return craftItem.handle;
}
final net.minecraft.world.item.ItemStack nms = CraftItemStack.unwrap(item);
if (isEmptyItem(nms)) {
return null;
}
return CraftItemStack.asNMSCopy(item);
return nms;
}

/**
Expand Down Expand Up @@ -384,6 +399,40 @@ && getItemMeta(item) instanceof Damageable damageable) {
return null;
}

/// Retrieves the `minecraft:custom_data` component from an item. Do **NOT** modify it.
///
/// @param item Cannot be null or "empty" (as determined by Bukkit).
///
/// @apiNote This should **ONLY** be used for inspection. If you want to set data, use [#editCustomData] instead.
public static @Nullable CompoundTag inspectCustomData(
final @NotNull ItemStack item
) {
final net.minecraft.world.item.ItemStack nms = Objects.requireNonNull(getNMSItemStack(item));
if (nms.get(DataComponents.CUSTOM_DATA) instanceof final CustomData component) {
//noinspection deprecation
return component.getUnsafe();
}
return null;
}

/// Edits the `minecraft:custom_data` component of an item. If the provided NBT is empty after being edited, the
/// component is removed from the item.
///
/// @param item Cannot be null or "empty" (as determined by Bukkit).
///
/// @apiNote It is good practice to namespace your data onto a child component instead of setting it directly onto
/// the provided nbt.
public static void editCustomData(
final @NotNull ItemStack item,
final @NotNull Consumer<@NotNull CompoundTag> editor
) {
CustomData.update(
DataComponents.CUSTOM_DATA,
Objects.requireNonNull(getNMSItemStack(item)),
Objects.requireNonNull(editor)
);
}

/**
* Handles an item's metadata.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.untamedears.itemexchange.events.BrowseOrPurchaseEvent;
import com.untamedears.itemexchange.events.SuccessfulPurchaseEvent;
import com.untamedears.itemexchange.items.Token;
import com.untamedears.itemexchange.rules.BulkExchangeRule;
import com.untamedears.itemexchange.rules.ExchangeRule;
import com.untamedears.itemexchange.rules.ShopRule;
Expand All @@ -12,6 +13,11 @@
import java.util.Hashtable;
import java.util.Map;
import java.util.stream.Stream;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
Expand All @@ -22,6 +28,7 @@
import org.bukkit.block.data.type.WallSign;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
Expand All @@ -33,6 +40,7 @@
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.CraftingInventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import vg.civcraft.mc.civmodcore.inventory.InventoryUtils;
import vg.civcraft.mc.civmodcore.inventory.RecipeManager;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;
Expand Down Expand Up @@ -290,4 +298,36 @@ public void onInventoryPickupItem(InventoryPickupItemEvent event) {
}
}

@EventHandler(ignoreCancelled = true)
public void sendTokenOnRightClick(
final @NotNull PlayerInteractEvent event
) {
switch (event.getAction()) {
case RIGHT_CLICK_AIR:
case RIGHT_CLICK_BLOCK:
break;
default:
return;
}
final Token itemToken = Token.fromItem(event.getItem());
if (itemToken == null) {
return;
}
event.setUseInteractedBlock(Event.Result.DENY);
event.setUseItemInHand(Event.Result.DENY);
event.getPlayer().sendMessage(Component.textOfChildren(
Component.text(
"That item holds the following token: ",
NamedTextColor.GRAY
),
Component.text(
itemToken.token(),
Style.style()
.color(NamedTextColor.WHITE)
.clickEvent(ClickEvent.copyToClipboard(itemToken.token()))
.hoverEvent(HoverEvent.showText(Component.text("Click to copy token to clipboard")))
.build()
)
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.untamedears.itemexchange.rules.modifiers.LoreModifier;
import com.untamedears.itemexchange.rules.modifiers.PotionModifier;
import com.untamedears.itemexchange.rules.modifiers.RepairModifier;
import com.untamedears.itemexchange.rules.modifiers.TokenModifier;
import java.util.List;
import vg.civcraft.mc.civmodcore.ACivMod;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
Expand Down Expand Up @@ -44,6 +45,7 @@ public void onEnable() {
commands = new CommandRegistrar(this);
commands.init();
modifiers = new ModifierRegistrar();
modifiers.registerModifier(TokenModifier.TEMPLATE); // 50
modifiers.registerModifier(DisplayNameModifier.TEMPLATE); // 100
modifiers.registerModifier(EnchantModifier.TEMPLATE); // 200
modifiers.registerModifier(EnchantStorageModifier.TEMPLATE); // 201
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public void registerCommands() {
registerCommand(new InfoCommand());
registerCommand(new ReloadCommand(getPlugin()));
registerCommand(new SetCommand());
registerCommand(new TokenCommand());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.untamedears.itemexchange.items.Token;
import com.untamedears.itemexchange.rules.ExchangeRule;
import com.untamedears.itemexchange.rules.modifiers.TokenModifier;
import com.untamedears.itemexchange.utility.ModifierHandler;
import com.untamedears.itemexchange.utility.RuleHandler;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils;

@CommandAlias(SetCommand.ALIAS)
Expand Down Expand Up @@ -72,4 +78,42 @@ public void switchIO(Player player) {
}
}

// ============================================================
// Tokens
// ============================================================

@Subcommand("token")
@Description("Sets [or removes] the token modifier.")
@Syntax("[token]")
public void commandSetToken(
final @NotNull Player sender,
final @Optional @Single String value
) {
try (final var handler = new ModifierHandler<>(sender, TokenModifier.TEMPLATE)) {
if (StringUtils.isEmpty(value)) {
handler.setModifier(null);
handler.relay(ChatColor.GREEN + "Successfully removed token modifier.");
return;
}
final Token token = Token.create(value);
if (token == null) {
throw new InvalidCommandArgument("%s is not a valid token!".formatted(value));
}
final TokenModifier modifier = handler.ensureModifier();
modifier.token = token;
handler.relay(ChatColor.GREEN + "Successfully set token modifier.");
}
}

@Subcommand("anytoken")
@Description("Adds (or resets) a token modifier to accept any token.")
public void commandSetAnyToken(
final @NotNull Player sender
) {
try (final var handler = new ModifierHandler<>(sender, TokenModifier.TEMPLATE)) {
final TokenModifier modifier = handler.ensureModifier();
modifier.token = null;
handler.relay(ChatColor.GREEN + "Successfully allowed any token.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.untamedears.itemexchange.commands;

import co.aikar.commands.BaseCommand;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.untamedears.itemexchange.items.Token;
import com.untamedears.itemexchange.utility.Utilities;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;

@CommandAlias("ietoken|iet")
public final class TokenCommand extends BaseCommand {
@Subcommand("create|c")
@Syntax("<token>")
public void createToken(
final @NotNull Player sender,
final @Single String value
) {
final Token token = Token.create(value);
if (token == null) {
throw new InvalidCommandArgument("%s is not a valid token!".formatted(value));
}
Utilities.giveItemsOrDrop(
sender.getInventory(),
token.asItem()
);
sender.sendMessage(Component.text(
"Token created!",
NamedTextColor.GREEN
));
}
}
Loading
Loading