diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java index 52fb321..7524491 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java @@ -202,9 +202,9 @@ public class ZNpcsPlus extends JavaPlugin { NpcEntryImpl entry = npcRegistry.create("debug_npc_" + i, world, type, new NpcLocation(i * 3, 200, 0, 0, 0)); entry.setProcessed(true); NpcImpl npc = entry.getNpc(); - npc.getHologram().addLineComponent(Component.text("Hello, World!", TextColor.color(255, 0, 0))); - npc.getHologram().addLineComponent(Component.text("Hello, World!", TextColor.color(0, 255, 0))); - npc.getHologram().addLineComponent(Component.text("Hello, World!", TextColor.color(0, 0, 255))); + npc.getHologram().addTextLineComponent(Component.text("Hello, World!", TextColor.color(255, 0, 0))); + npc.getHologram().addTextLineComponent(Component.text("Hello, World!", TextColor.color(0, 255, 0))); + npc.getHologram().addTextLineComponent(Component.text("Hello, World!", TextColor.color(0, 0, 255))); i++; } } @@ -214,6 +214,7 @@ public class ZNpcsPlus extends JavaPlugin { public void onDisable() { NpcApiProvider.unregister(); for (Runnable runnable : shutdownTasks) runnable.run(); + shutdownTasks.clear(); PacketEvents.getAPI().terminate(); } @@ -283,11 +284,14 @@ public class ZNpcsPlus extends JavaPlugin { .addSubcommand("reload", new LoadAllCommand(npcRegistry)) .addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry))) .addSubcommand("holo", new MultiCommand(loadHelpMessage("holo")) - .addSubcommand("add", new HoloAddCommand(npcRegistry, textSerializer)) + .addSubcommand("add", new HoloAddCommand(npcRegistry)) + .addSubcommand("additem", new HoloAddItemCommand(npcRegistry)) .addSubcommand("delete", new HoloDeleteCommand(npcRegistry)) .addSubcommand("info", new HoloInfoCommand(npcRegistry)) - .addSubcommand("insert", new HoloInsertCommand(npcRegistry, textSerializer)) - .addSubcommand("set", new HoloSetCommand(npcRegistry, textSerializer)) + .addSubcommand("insert", new HoloInsertCommand(npcRegistry)) + .addSubcommand("insertitem", new HoloInsertItemCommand(npcRegistry)) + .addSubcommand("set", new HoloSetCommand(npcRegistry)) + .addSubcommand("setitem", new HoloSetItemCommand(npcRegistry)) .addSubcommand("offset", new HoloOffsetCommand(npcRegistry)) .addSubcommand("refreshdelay", new HoloRefreshDelayCommand(npcRegistry))) .addSubcommand("action", new MultiCommand(loadHelpMessage("action")) diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloAddCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloAddCommand.java index cf4ed32..f739d18 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloAddCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloAddCommand.java @@ -4,22 +4,20 @@ import lol.pyr.director.adventure.command.CommandContext; import lol.pyr.director.adventure.command.CommandHandler; import lol.pyr.director.common.command.CommandExecutionException; import lol.pyr.znpcsplus.hologram.HologramImpl; +import lol.pyr.znpcsplus.hologram.HologramItem; import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import java.util.Collections; import java.util.List; public class HoloAddCommand implements CommandHandler { private final NpcRegistryImpl registry; - private final LegacyComponentSerializer textSerializer; - public HoloAddCommand(NpcRegistryImpl registry, LegacyComponentSerializer textSerializer) { + public HoloAddCommand(NpcRegistryImpl registry) { this.registry = registry; - this.textSerializer = textSerializer; } @Override @@ -27,7 +25,13 @@ public class HoloAddCommand implements CommandHandler { context.setUsage(context.getLabel() + " holo add "); HologramImpl hologram = context.parse(NpcEntryImpl.class).getNpc().getHologram(); context.ensureArgsNotEmpty(); - hologram.addLineComponent(textSerializer.deserialize(context.dumpAllArgs())); + String in = context.dumpAllArgs(); + if (in.toLowerCase().startsWith("item:")) { + if (!HologramItem.ensureValidItemInput(in.substring(5))) { + context.halt(Component.text("The item input is invalid!", NamedTextColor.RED)); + } + } + hologram.addLine(in); context.send(Component.text("NPC line added!", NamedTextColor.GREEN)); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloAddItemCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloAddItemCommand.java new file mode 100644 index 0000000..cb23a56 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloAddItemCommand.java @@ -0,0 +1,39 @@ +package lol.pyr.znpcsplus.commands.hologram; + +import lol.pyr.director.adventure.command.CommandContext; +import lol.pyr.director.adventure.command.CommandHandler; +import lol.pyr.director.common.command.CommandExecutionException; +import lol.pyr.znpcsplus.hologram.HologramImpl; +import lol.pyr.znpcsplus.npc.NpcEntryImpl; +import lol.pyr.znpcsplus.npc.NpcRegistryImpl; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; + +public class HoloAddItemCommand implements CommandHandler { + private final NpcRegistryImpl registry; + + public HoloAddItemCommand(NpcRegistryImpl registry) { + this.registry = registry; + } + + @Override + public void run(CommandContext context) throws CommandExecutionException { + context.setUsage(context.getLabel() + " holo additem "); + Player player = context.ensureSenderIsPlayer(); + org.bukkit.inventory.ItemStack itemStack = player.getInventory().getItemInHand(); + if (itemStack == null) context.halt(Component.text("You must be holding an item!", NamedTextColor.RED)); + HologramImpl hologram = context.parse(NpcEntryImpl.class).getNpc().getHologram(); + hologram.addItemLineStack(itemStack); + context.send(Component.text("NPC item line added!", NamedTextColor.GREEN)); + } + + @Override + public List suggest(CommandContext context) throws CommandExecutionException { + if (context.argSize() == 1) return context.suggestCollection(registry.getModifiableIds()); + return Collections.emptyList(); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInfoCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInfoCommand.java index f9e2c5e..20c5a6c 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInfoCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInfoCommand.java @@ -4,7 +4,6 @@ import lol.pyr.director.adventure.command.CommandContext; import lol.pyr.director.adventure.command.CommandHandler; import lol.pyr.director.common.command.CommandExecutionException; import lol.pyr.znpcsplus.hologram.HologramImpl; -import lol.pyr.znpcsplus.hologram.HologramLine; import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import net.kyori.adventure.text.Component; @@ -26,7 +25,11 @@ public class HoloInfoCommand implements CommandHandler { NpcEntryImpl entry = context.parse(NpcEntryImpl.class); HologramImpl hologram = entry.getNpc().getHologram(); Component component = Component.text("NPC Hologram Info of ID " + entry.getId() + ":", NamedTextColor.GREEN).appendNewline(); - for (HologramLine line : hologram.getLines()) component = component.append(line.getText()).appendNewline(); + for (int i = 0; i < hologram.getLines().size(); i++) { + component = component.append(Component.text(i + ") ", NamedTextColor.GREEN)) + .append(Component.text(hologram.getLine(i), NamedTextColor.WHITE)) + .appendNewline(); + } context.send(component); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInsertCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInsertCommand.java index 7429410..8bb8471 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInsertCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInsertCommand.java @@ -4,11 +4,11 @@ import lol.pyr.director.adventure.command.CommandContext; import lol.pyr.director.adventure.command.CommandHandler; import lol.pyr.director.common.command.CommandExecutionException; import lol.pyr.znpcsplus.hologram.HologramImpl; +import lol.pyr.znpcsplus.hologram.HologramItem; import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import java.util.Collections; import java.util.List; @@ -16,11 +16,9 @@ import java.util.stream.Stream; public class HoloInsertCommand implements CommandHandler { private final NpcRegistryImpl npcRegistry; - private final LegacyComponentSerializer componentSerializer; - public HoloInsertCommand(NpcRegistryImpl npcRegistry, LegacyComponentSerializer componentSerializer) { + public HoloInsertCommand(NpcRegistryImpl npcRegistry) { this.npcRegistry = npcRegistry; - this.componentSerializer = componentSerializer; } @Override @@ -30,7 +28,13 @@ public class HoloInsertCommand implements CommandHandler { int line = context.parse(Integer.class); if (line < 0 || line >= hologram.getLines().size()) context.halt(Component.text("Invalid line number!", NamedTextColor.RED)); context.ensureArgsNotEmpty(); - hologram.insertLineComponent(line, componentSerializer.deserialize(context.dumpAllArgs())); + String in = context.dumpAllArgs(); + if (in.toLowerCase().startsWith("item:")) { + if (!HologramItem.ensureValidItemInput(in.substring(5))) { + context.halt(Component.text("The item input is invalid!", NamedTextColor.RED)); + } + } + hologram.insertLine(line, in); context.send(Component.text("NPC line inserted!", NamedTextColor.GREEN)); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInsertItemCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInsertItemCommand.java new file mode 100644 index 0000000..68273e4 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloInsertItemCommand.java @@ -0,0 +1,45 @@ +package lol.pyr.znpcsplus.commands.hologram; + +import lol.pyr.director.adventure.command.CommandContext; +import lol.pyr.director.adventure.command.CommandHandler; +import lol.pyr.director.common.command.CommandExecutionException; +import lol.pyr.znpcsplus.hologram.HologramImpl; +import lol.pyr.znpcsplus.npc.NpcEntryImpl; +import lol.pyr.znpcsplus.npc.NpcRegistryImpl; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +public class HoloInsertItemCommand implements CommandHandler { + private final NpcRegistryImpl npcRegistry; + + public HoloInsertItemCommand(NpcRegistryImpl npcRegistry) { + this.npcRegistry = npcRegistry; + } + + @Override + public void run(CommandContext context) throws CommandExecutionException { + context.setUsage(context.getLabel() + " holo insertitem "); + HologramImpl hologram = context.parse(NpcEntryImpl.class).getNpc().getHologram(); + int line = context.parse(Integer.class); + if (line < 0 || line >= hologram.getLines().size()) context.halt(Component.text("Invalid line number!", NamedTextColor.RED)); + Player player = context.ensureSenderIsPlayer(); + org.bukkit.inventory.ItemStack itemStack = player.getInventory().getItemInHand(); + if (itemStack == null) context.halt(Component.text("You must be holding an item!", NamedTextColor.RED)); + hologram.insertItemLineStack(line, itemStack); + context.send(Component.text("NPC item line inserted!", NamedTextColor.GREEN)); + } + + @Override + public List suggest(CommandContext context) throws CommandExecutionException { + if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds()); + if (context.argSize() == 2) return context.suggestStream(Stream.iterate(0, n -> n + 1) + .limit(context.suggestionParse(0, NpcEntryImpl.class).getNpc().getHologram().getLines().size()) + .map(String::valueOf)); + return Collections.emptyList(); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloSetCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloSetCommand.java index df3678a..7b106b0 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloSetCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloSetCommand.java @@ -8,7 +8,6 @@ import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import java.util.Collections; import java.util.List; @@ -16,11 +15,9 @@ import java.util.stream.Stream; public class HoloSetCommand implements CommandHandler { private final NpcRegistryImpl npcRegistry; - private final LegacyComponentSerializer componentSerializer; - public HoloSetCommand(NpcRegistryImpl npcRegistry, LegacyComponentSerializer componentSerializer) { + public HoloSetCommand(NpcRegistryImpl npcRegistry) { this.npcRegistry = npcRegistry; - this.componentSerializer = componentSerializer; } @Override @@ -31,7 +28,7 @@ public class HoloSetCommand implements CommandHandler { if (line < 0 || line >= hologram.getLines().size()) context.halt(Component.text("Invalid line number!", NamedTextColor.RED)); context.ensureArgsNotEmpty(); hologram.removeLine(line); - hologram.insertLineComponent(line, componentSerializer.deserialize(context.dumpAllArgs())); + hologram.insertLine(line, context.dumpAllArgs()); context.send(Component.text("NPC line set!", NamedTextColor.GREEN)); } @@ -42,8 +39,7 @@ public class HoloSetCommand implements CommandHandler { HologramImpl hologram = context.suggestionParse(0, NpcEntryImpl.class).getNpc().getHologram(); if (context.argSize() == 2) return context.suggestStream(Stream.iterate(0, n -> n + 1) .limit(hologram.getLines().size()).map(String::valueOf)); - if (context.argSize() == 3) return context.suggestLiteral(componentSerializer.serialize( - hologram.getLineComponent(context.suggestionParse(1, Integer.class)))); + if (context.argSize() == 3) return context.suggestLiteral(hologram.getLine(context.suggestionParse(1, Integer.class))); } return Collections.emptyList(); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloSetItemCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloSetItemCommand.java new file mode 100644 index 0000000..1e623ab --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/hologram/HoloSetItemCommand.java @@ -0,0 +1,48 @@ +package lol.pyr.znpcsplus.commands.hologram; + +import lol.pyr.director.adventure.command.CommandContext; +import lol.pyr.director.adventure.command.CommandHandler; +import lol.pyr.director.common.command.CommandExecutionException; +import lol.pyr.znpcsplus.hologram.HologramImpl; +import lol.pyr.znpcsplus.npc.NpcEntryImpl; +import lol.pyr.znpcsplus.npc.NpcRegistryImpl; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +public class HoloSetItemCommand implements CommandHandler { + private final NpcRegistryImpl npcRegistry; + + public HoloSetItemCommand(NpcRegistryImpl npcRegistry) { + this.npcRegistry = npcRegistry; + } + + @Override + public void run(CommandContext context) throws CommandExecutionException { + context.setUsage(context.getLabel() + " holo setitem "); + HologramImpl hologram = context.parse(NpcEntryImpl.class).getNpc().getHologram(); + int line = context.parse(Integer.class); + if (line < 0 || line >= hologram.getLines().size()) context.halt(Component.text("Invalid line number!", NamedTextColor.RED)); + Player player = context.ensureSenderIsPlayer(); + org.bukkit.inventory.ItemStack itemStack = player.getInventory().getItemInHand(); + if (itemStack == null) context.halt(Component.text("You must be holding an item!", NamedTextColor.RED)); + hologram.removeLine(line); + hologram.insertItemLineStack(line, itemStack); + context.send(Component.text("NPC item line set!", NamedTextColor.GREEN)); + } + + @Override + public List suggest(CommandContext context) throws CommandExecutionException { + if (context.argSize() == 1) return context.suggestCollection(npcRegistry.getModifiableIds()); + if (context.argSize() >= 2) { + HologramImpl hologram = context.suggestionParse(0, NpcEntryImpl.class).getNpc().getHologram(); + if (context.argSize() == 2) return context.suggestStream(Stream.iterate(0, n -> n + 1) + .limit(hologram.getLines().size()).map(String::valueOf)); + } + return Collections.emptyList(); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/CitizensImporter.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/CitizensImporter.java index 39b3deb..2a17d34 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/CitizensImporter.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/citizens/CitizensImporter.java @@ -82,7 +82,7 @@ public class CitizensImporter implements DataImporter { world = Bukkit.getWorlds().get(0).getName(); } NpcImpl npc = new NpcImpl(uuid, propertyRegistry, configManager, packetFactory, textSerializer, world, typeRegistry.getByName("armor_stand"), new NpcLocation(0, 0, 0, 0, 0)); - npc.getHologram().addLineComponent(textSerializer.deserialize(name)); + npc.getHologram().addTextLineComponent(textSerializer.deserialize(name)); ConfigurationSection traits = npcSection.getConfigurationSection("traits"); if (traits != null) { for (String traitName : traits.getKeys(false)) { diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java index d652d64..6c17b35 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/conversion/znpcs/ZNpcImporter.java @@ -106,7 +106,7 @@ public class ZNpcImporter implements DataImporter { hologram.setOffset(model.getHologramHeight()); for (String raw : model.getHologramLines()) { Component line = textSerializer.deserialize(raw); - hologram.addLineComponent(line); + hologram.addTextLineComponent(line); } for (ZNpcsAction action : model.getClickActions()) { diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java index ebfc03c..02abda9 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java @@ -224,6 +224,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { register(new GlowProperty(packetFactory)); register(new SimpleBitsetProperty("fire", 0, 0x01)); register(new SimpleBitsetProperty("invisible", 0, 0x20)); + register(new HoloItemProperty()); linkProperties("glow", "fire", "invisible"); register(new SimpleBooleanProperty("silent", 4, false, legacyBooleans)); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HoloItemProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HoloItemProperty.java new file mode 100644 index 0000000..cb590c5 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HoloItemProperty.java @@ -0,0 +1,24 @@ +package lol.pyr.znpcsplus.entity.properties; + +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import com.github.retrooper.packetevents.protocol.item.ItemStack; +import lol.pyr.znpcsplus.entity.EntityPropertyImpl; +import lol.pyr.znpcsplus.entity.PacketEntity; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class HoloItemProperty extends EntityPropertyImpl { + + public HoloItemProperty() { + super("holo_item", null, ItemStack.class); + setPlayerModifiable(false); + } + + @Override + public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { + properties.put(8, newEntityData(8, EntityDataTypes.ITEMSTACK, entity.getProperty(this))); + properties.put(5, newEntityData(5, EntityDataTypes.BOOLEAN, true)); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java index f85301e..89ed907 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramImpl.java @@ -1,11 +1,13 @@ package lol.pyr.znpcsplus.hologram; +import com.github.retrooper.packetevents.protocol.item.ItemStack; +import io.github.retrooper.packetevents.util.SpigotConversionUtil; import lol.pyr.znpcsplus.api.hologram.Hologram; import lol.pyr.znpcsplus.config.ConfigManager; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.packets.PacketFactory; -import lol.pyr.znpcsplus.util.Viewable; import lol.pyr.znpcsplus.util.NpcLocation; +import lol.pyr.znpcsplus.util.Viewable; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.entity.Player; @@ -24,7 +26,7 @@ public class HologramImpl extends Viewable implements Hologram { private long refreshDelay = -1; private long lastRefresh = System.currentTimeMillis(); private NpcLocation location; - private final List lines = new ArrayList<>(); + private final List> lines = new ArrayList<>(); public HologramImpl(EntityPropertyRegistryImpl propertyRegistry, ConfigManager configManager, PacketFactory packetFactory, LegacyComponentSerializer textSerializer, NpcLocation location) { this.propertyRegistry = propertyRegistry; @@ -34,32 +36,59 @@ public class HologramImpl extends Viewable implements Hologram { this.location = location; } - public void addLineComponent(Component line) { - HologramLine newLine = new HologramLine(propertyRegistry, packetFactory, null, line); + public void addTextLineComponent(Component line) { + HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line); + lines.add(newLine); + relocateLines(newLine); + for (Player viewer : getViewers()) newLine.show(viewer.getPlayer()); + } + + public void addTextLine(String line) { + addTextLineComponent(textSerializer.deserialize(line)); + } + + public void addItemLineStack(org.bukkit.inventory.ItemStack item) { + addItemLinePEStack(SpigotConversionUtil.fromBukkitItemStack(item)); + } + + public void addItemLine(String serializedItem) { + addItemLinePEStack(HologramItem.deserialize(serializedItem)); + } + + public void addItemLinePEStack(ItemStack item) { + HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item); lines.add(newLine); relocateLines(newLine); for (Player viewer : getViewers()) newLine.show(viewer.getPlayer()); } public void addLine(String line) { - addLineComponent(textSerializer.deserialize(line)); + if (line.toLowerCase().startsWith("item:")) { + addItemLine(line.substring(5)); + } else { + addTextLine(line); + } } - public Component getLineComponent(int index) { - return lines.get(index).getText(); + public Component getLineTextComponent(int index) { + return ((HologramText) lines.get(index)).getValue(); } public String getLine(int index) { - return textSerializer.serialize(getLineComponent(index)); + if (lines.get(index) instanceof HologramItem) { + return ((HologramItem) lines.get(index)).serialize(); + } else { + return textSerializer.serialize(getLineTextComponent(index)); + } } public void removeLine(int index) { - HologramLine line = lines.remove(index); + HologramLine line = lines.remove(index); for (Player viewer : getViewers()) line.hide(viewer); relocateLines(); } - public List getLines() { + public List> getLines() { return Collections.unmodifiableList(lines); } @@ -68,25 +97,48 @@ public class HologramImpl extends Viewable implements Hologram { lines.clear(); } - public void insertLineComponent(int index, Component line) { - HologramLine newLine = new HologramLine(propertyRegistry, packetFactory, null, line); + public void insertTextLineComponent(int index, Component line) { + HologramText newLine = new HologramText(propertyRegistry, packetFactory, null, line); lines.add(index, newLine); relocateLines(newLine); for (Player viewer : getViewers()) newLine.show(viewer.getPlayer()); } + public void insertTextLine(int index, String line) { + insertTextLineComponent(index, textSerializer.deserialize(line)); + } + + public void insertItemLineStack(int index, org.bukkit.inventory.ItemStack item) { + insertItemLinePEStack(index, SpigotConversionUtil.fromBukkitItemStack(item)); + } + + public void insertItemLinePEStack(int index, ItemStack item) { + HologramItem newLine = new HologramItem(propertyRegistry, packetFactory, null, item); + lines.add(index, newLine); + relocateLines(newLine); + for (Player viewer : getViewers()) newLine.show(viewer.getPlayer()); + } + + public void insertItemLine(int index, String item) { + insertItemLinePEStack(index, HologramItem.deserialize(item)); + } + public void insertLine(int index, String line) { - insertLineComponent(index, textSerializer.deserialize(line)); + if (line.toLowerCase().startsWith("item:")) { + insertItemLine(index, line.substring(5)); + } else { + insertTextLine(index, line); + } } @Override protected void UNSAFE_show(Player player) { - for (HologramLine line : lines) line.show(player); + for (HologramLine line : lines) line.show(player); } @Override protected void UNSAFE_hide(Player player) { - for (HologramLine line : lines) line.hide(player); + for (HologramLine line : lines) line.hide(player); } public long getRefreshDelay() { @@ -103,7 +155,7 @@ public class HologramImpl extends Viewable implements Hologram { public void refresh() { lastRefresh = System.currentTimeMillis(); - for (HologramLine line : lines) for (Player viewer : getViewers()) line.refreshMeta(viewer); + for (HologramLine line : lines) for (Player viewer : getViewers()) line.refreshMeta(viewer); } public void setLocation(NpcLocation location) { @@ -115,10 +167,10 @@ public class HologramImpl extends Viewable implements Hologram { relocateLines(null); } - private void relocateLines(HologramLine newLine) { + private void relocateLines(HologramLine newLine) { final double lineSpacing = configManager.getConfig().lineSpacing(); double height = location.getY() + (lines.size() - 1) * lineSpacing + getOffset(); - for (HologramLine line : lines) { + for (HologramLine line : lines) { line.setLocation(location.withY(height), line == newLine ? Collections.emptySet() : getViewers()); height -= lineSpacing; } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramItem.java b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramItem.java new file mode 100644 index 0000000..c25e434 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramItem.java @@ -0,0 +1,111 @@ +package lol.pyr.znpcsplus.hologram; + +import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import com.github.retrooper.packetevents.protocol.item.ItemStack; +import com.github.retrooper.packetevents.protocol.item.type.ItemType; +import com.github.retrooper.packetevents.protocol.item.type.ItemTypes; +import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; +import com.github.retrooper.packetevents.protocol.nbt.NBTInt; +import com.github.retrooper.packetevents.protocol.nbt.NBTNumber; +import com.github.retrooper.packetevents.protocol.nbt.codec.NBTCodec; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import lol.pyr.znpcsplus.api.entity.EntityProperty; +import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; +import lol.pyr.znpcsplus.packets.PacketFactory; +import lol.pyr.znpcsplus.util.NpcLocation; +import org.bukkit.entity.Player; + +import java.util.Collection; + +public class HologramItem extends HologramLine { + public HologramItem(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, ItemStack item) { + super(item, packetFactory, EntityTypes.ITEM, location); + addProperty(propertyRegistry.getByName("holo_item")); + } + + @SuppressWarnings("unchecked") + @Override + public T getProperty(EntityProperty key) { + if (key.getName().equalsIgnoreCase("holo_item")) return (T) getValue(); + return super.getProperty(key); + } + + @Override + public void setLocation(NpcLocation location, Collection viewers) { + super.setLocation(location.withY(location.getY() + 2.05), viewers); + } + + public static boolean ensureValidItemInput(String in) { + if (in == null || in.isEmpty()) { + return false; + } + + int indexOfNbt = in.indexOf("{"); + if (indexOfNbt != -1) { + String typeName = in.substring(0, indexOfNbt); + ItemType type = ItemTypes.getByName("minecraft:" + typeName.toLowerCase()); + if (type == null) { + return false; + } + String nbtString = in.substring(indexOfNbt); + return ensureValidNbt(nbtString); + } else { + ItemType type = ItemTypes.getByName("minecraft:" + in.toLowerCase()); + return type != null; + } + } + + private static boolean ensureValidNbt(String nbtString) { + JsonElement nbtJson; + try { + nbtJson = JsonParser.parseString(nbtString); + } catch (JsonSyntaxException e) { + return false; + } + try { + NBTCodec.jsonToNBT(nbtJson); + } catch (Exception ignored) { + return false; + } + return true; + } + + public static ItemStack deserialize(String serializedItem) { + int indexOfNbt = serializedItem.indexOf("{"); + String typeName = serializedItem; + int amount = 1; + NBTCompound nbt = new NBTCompound(); + if (indexOfNbt != -1) { + typeName = serializedItem.substring(0, indexOfNbt); + String nbtString = serializedItem.substring(indexOfNbt); + JsonElement nbtJson = null; + try { + nbtJson = JsonParser.parseString(nbtString); + } catch (Exception ignored) { + } + if (nbtJson != null) { + nbt = (NBTCompound) NBTCodec.jsonToNBT(nbtJson); + NBTNumber nbtAmount = nbt.getNumberTagOrNull("Count"); + if (nbtAmount != null) { + nbt.removeTag("Count"); + amount = nbtAmount.getAsInt(); + if (amount <= 0) amount = 1; + if (amount > 127) amount = 127; + } + } + } + ItemType type = ItemTypes.getByName("minecraft:" + typeName.toLowerCase()); + if (type == null) type = ItemTypes.STONE; + return ItemStack.builder().type(type).amount(amount).nbt(nbt).build(); + } + + public String serialize() { + NBTCompound nbt = getValue().getNBT(); + if (nbt == null) nbt = new NBTCompound(); + if (getValue().getAmount() > 1) nbt.setTag("Count", new NBTInt(getValue().getAmount())); + if (nbt.isEmpty()) return "item:" + getValue().getType().getName().toString().replace("minecraft:", ""); + return "item:" + getValue().getType().getName().toString().replace("minecraft:", "") + NBTCodec.nbtToJson(nbt, true); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java index df76abd..9bb695f 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramLine.java @@ -1,76 +1,73 @@ package lol.pyr.znpcsplus.hologram; -import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.api.entity.PropertyHolder; -import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.util.NpcLocation; -import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import java.util.Collection; import java.util.HashSet; import java.util.Set; -public class HologramLine implements PropertyHolder { - private Component text; - private final PacketEntity armorStand; +public class HologramLine implements PropertyHolder { + private M value; + private final PacketEntity entity; private final Set> properties; - public HologramLine(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) { - this.text = text; + public HologramLine(M value, PacketFactory packetFactory, EntityType type, NpcLocation location) { + this.value = value; + this.entity = new PacketEntity(packetFactory, this, type, location); this.properties = new HashSet<>(); - this.properties.add(propertyRegistry.getByName("name")); - this.properties.add(propertyRegistry.getByName("invisible")); - armorStand = new PacketEntity(packetFactory, this, EntityTypes.ARMOR_STAND, location); } - public Component getText() { - return text; + public M getValue() { + return value; } - public void setText(Component text) { - this.text = text; + public void setValue(M value) { + this.value = value; } public void refreshMeta(Player player) { - armorStand.refreshMeta(player); + entity.refreshMeta(player); } protected void show(Player player) { - armorStand.spawn(player); + entity.spawn(player); } protected void hide(Player player) { - armorStand.despawn(player); + entity.despawn(player); } public void setLocation(NpcLocation location, Collection viewers) { - armorStand.setLocation(location, viewers); + entity.setLocation(location, viewers); } public int getEntityId() { - return armorStand.getEntityId(); + return entity.getEntityId(); + } + + public void addProperty(EntityProperty property) { + properties.add(property); } - @SuppressWarnings("unchecked") @Override public T getProperty(EntityProperty key) { - if (key.getName().equalsIgnoreCase("invisible")) return (T) Boolean.TRUE; - if (key.getName().equalsIgnoreCase("name")) return (T) text; return key.getDefaultValue(); } @Override public boolean hasProperty(EntityProperty key) { - return key.getName().equalsIgnoreCase("name") || key.getName().equalsIgnoreCase("invisible"); + return properties.contains(key); } @Override public void setProperty(EntityProperty key, T value) { - throw new UnsupportedOperationException("Can't set properties on a hologram"); + throw new UnsupportedOperationException("Can't set properties on a hologram line"); } @Override diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramText.java b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramText.java new file mode 100644 index 0000000..2c66aef --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/hologram/HologramText.java @@ -0,0 +1,30 @@ +package lol.pyr.znpcsplus.hologram; + +import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import lol.pyr.znpcsplus.api.entity.EntityProperty; +import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; +import lol.pyr.znpcsplus.packets.PacketFactory; +import lol.pyr.znpcsplus.util.NpcLocation; +import net.kyori.adventure.text.Component; + +public class HologramText extends HologramLine { + + public HologramText(EntityPropertyRegistryImpl propertyRegistry, PacketFactory packetFactory, NpcLocation location, Component text) { + super(text, packetFactory, EntityTypes.ARMOR_STAND, location); + addProperty(propertyRegistry.getByName("name")); + addProperty(propertyRegistry.getByName("invisible")); + } + + @SuppressWarnings("unchecked") + @Override + public T getProperty(EntityProperty key) { + if (key.getName().equalsIgnoreCase("invisible")) return (T) Boolean.TRUE; + if (key.getName().equalsIgnoreCase("name")) return (T) getValue(); + return super.getProperty(key); + } + + @Override + public boolean hasProperty(EntityProperty key) { + return key.getName().equalsIgnoreCase("name") || key.getName().equalsIgnoreCase("invisible"); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/storage/yaml/YamlStorage.java b/plugin/src/main/java/lol/pyr/znpcsplus/storage/yaml/YamlStorage.java index ef1407b..9f0ef66 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/storage/yaml/YamlStorage.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/storage/yaml/YamlStorage.java @@ -6,7 +6,6 @@ import lol.pyr.znpcsplus.entity.EntityPropertyImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.PropertySerializer; import lol.pyr.znpcsplus.hologram.HologramImpl; -import lol.pyr.znpcsplus.hologram.HologramLine; import lol.pyr.znpcsplus.interaction.ActionRegistry; import lol.pyr.znpcsplus.npc.NpcEntryImpl; import lol.pyr.znpcsplus.npc.NpcImpl; @@ -14,7 +13,6 @@ import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl; import lol.pyr.znpcsplus.packets.PacketFactory; import lol.pyr.znpcsplus.storage.NpcStorage; import lol.pyr.znpcsplus.util.NpcLocation; -import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; @@ -73,7 +71,7 @@ public class YamlStorage implements NpcStorage { HologramImpl hologram = npc.getHologram(); hologram.setOffset(config.getDouble("hologram.offset", 0.0)); hologram.setRefreshDelay(config.getLong("hologram.refresh-delay", -1)); - for (String line : config.getStringList("hologram.lines")) hologram.addLineComponent(MiniMessage.miniMessage().deserialize(line)); + for (String line : config.getStringList("hologram.lines")) hologram.addLine(line); for (String s : config.getStringList("actions")) npc.addAction(actionRegistry.deserialize(s)); NpcEntryImpl entry = new NpcEntryImpl(config.getString("id"), npc); @@ -112,8 +110,8 @@ public class YamlStorage implements NpcStorage { if (hologram.getOffset() != 0.0) config.set("hologram.offset", hologram.getOffset()); if (hologram.getRefreshDelay() != -1) config.set("hologram.refresh-delay", hologram.getRefreshDelay()); List lines = new ArrayList<>(npc.getHologram().getLines().size()); - for (HologramLine line : hologram.getLines()) { - lines.add(MiniMessage.miniMessage().serialize(line.getText())); + for (int i = 0; i < hologram.getLines().size(); i++) { + lines.add(hologram.getLine(i)); } config.set("hologram.lines", lines); config.set("actions", npc.getActions().stream()