From 508a419ad9782b522275e2e94405f127b236561c Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:53:55 +0200 Subject: [PATCH 01/17] Add hologram API and commands --- build.gradle | 6 + .../java/gg/nextforge/NextCorePlugin.java | 1 + .../gg/nextforge/plugin/NextForgePlugin.java | 3 + .../textblockitemdisplay/BlockHologram.java | 20 +++ .../textblockitemdisplay/Hologram.java | 42 +++++ .../textblockitemdisplay/HologramCommand.java | 150 ++++++++++++++++++ .../textblockitemdisplay/HologramManager.java | 75 +++++++++ .../textblockitemdisplay/ItemHologram.java | 20 +++ .../textblockitemdisplay/TextHologram.java | 35 ++++ .../impl/AbstractHologram.java | 77 +++++++++ .../impl/SimpleBlockHologram.java | 27 ++++ .../impl/SimpleItemHologram.java | 27 ++++ .../impl/SimpleTextHologram.java | 39 +++++ .../HologramManagerTest.java | 43 +++++ 14 files changed, 565 insertions(+) create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/BlockHologram.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/Hologram.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/ItemHologram.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/impl/AbstractHologram.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleBlockHologram.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleItemHologram.java create mode 100644 src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java create mode 100644 src/test/java/gg/nextforge/textblockitemdisplay/HologramManagerTest.java diff --git a/build.gradle b/build.gradle index c5df20b..f363bf8 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,8 @@ dependencies { implementation "org.mozilla:rhino:1.7.14" implementation "com.google.code.gson:gson:2.10.1" implementation "org.bstats:bstats-bukkit:3.0.2" + testImplementation "org.junit.jupiter:junit-jupiter:5.9.3" + testImplementation "org.mockito:mockito-core:5.5.0" } subprojects { @@ -72,6 +74,10 @@ tasks { options.encoding = "UTF-8" } + test { + useJUnitPlatform() + } + // Haupt-Versionserweiterung für plugin.yml processResources { filesMatching("plugin.yml") { diff --git a/src/main/java/gg/nextforge/NextCorePlugin.java b/src/main/java/gg/nextforge/NextCorePlugin.java index 7821a84..fe44075 100644 --- a/src/main/java/gg/nextforge/NextCorePlugin.java +++ b/src/main/java/gg/nextforge/NextCorePlugin.java @@ -48,6 +48,7 @@ public void enable(boolean isReload) { new NextCoreCommand(this); new NPCCommand(this, getNpcManager()); + new gg.nextforge.textblockitemdisplay.HologramCommand(this, getHologramManager()); ConsoleHeader.send(this); diff --git a/src/main/java/gg/nextforge/plugin/NextForgePlugin.java b/src/main/java/gg/nextforge/plugin/NextForgePlugin.java index 5efc684..2a93022 100644 --- a/src/main/java/gg/nextforge/plugin/NextForgePlugin.java +++ b/src/main/java/gg/nextforge/plugin/NextForgePlugin.java @@ -7,6 +7,7 @@ import gg.nextforge.protocol.ProtocolManager; import gg.nextforge.scheduler.CoreScheduler; import gg.nextforge.text.TextManager; +import gg.nextforge.textblockitemdisplay.HologramManager; import lombok.Getter; import org.bstats.bukkit.Metrics; import org.bukkit.plugin.Plugin; @@ -25,6 +26,7 @@ public abstract class NextForgePlugin extends JavaPlugin { CommandManager commandManager; TextManager textManager; NPCManager npcManager; + HologramManager hologramManager; ProtocolManager protocolManager; Metrics metrics; @@ -60,6 +62,7 @@ public void onEnable() { this.commandManager = new CommandManager(this); this.textManager = new TextManager(this); this.npcManager = new NPCManager(this); + this.hologramManager = new HologramManager(); this.protocolManager = new ProtocolManager(this); boolean isReload = getServer().getPluginManager().isPluginEnabled("NextForge"); diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/BlockHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/BlockHologram.java new file mode 100644 index 0000000..49bbf32 --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/BlockHologram.java @@ -0,0 +1,20 @@ +package gg.nextforge.textblockitemdisplay; + +import org.bukkit.Material; + +/** + * Represents a hologram displaying a block. + */ +public interface BlockHologram extends Hologram { + /** + * @return block type displayed by this hologram. + */ + Material getBlockType(); + + /** + * Sets block type. + * + * @param material material to display + */ + void setBlockType(Material material); +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/Hologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/Hologram.java new file mode 100644 index 0000000..b6c32a0 --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/Hologram.java @@ -0,0 +1,42 @@ +package gg.nextforge.textblockitemdisplay; + +import org.bukkit.Location; + +/** + * Base interface for all hologram types. + */ +public interface Hologram { + /** + * @return name of the hologram. + */ + String getName(); + + /** + * Get the current location of the hologram. + * + * @return hologram location + */ + Location getLocation(); + + /** + * Sets the location of this hologram. + * + * @param location new location + */ + void setLocation(Location location); + + /** + * Spawns the hologram. + */ + void spawn(); + + /** + * Removes the hologram from the world. + */ + void despawn(); + + /** + * @return whether the hologram is currently spawned + */ + boolean isSpawned(); +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java b/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java new file mode 100644 index 0000000..fe4a0d7 --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java @@ -0,0 +1,150 @@ +package gg.nextforge.textblockitemdisplay; + +import gg.nextforge.NextCorePlugin; +import gg.nextforge.command.CommandContext; +import gg.nextforge.command.CommandManager; +import gg.nextforge.text.TextManager; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +/** + * Command handling for hologram operations. + */ +public class HologramCommand { + private final NextCorePlugin plugin; + private final HologramManager manager; + private final TextManager text; + + public HologramCommand(NextCorePlugin plugin, HologramManager manager) { + this.plugin = plugin; + this.manager = manager; + this.text = plugin.getTextManager(); + register(); + } + + private void register() { + CommandManager cm = plugin.getCommandManager(); + cm.command("hologram") + .permission("nextforge.command.hologram") + .description("Manage holograms") + .executor(this::handleHelp) + .subcommand("help", this::handleHelp) + .subcommand("list", this::handleList) + .subcommand("nearby", this::handleNearby) + .subcommand("create", this::handleCreate) + .subcommand("remove", this::handleRemove) + .subcommand("copy", this::handleCopy) + .subcommand("info", this::handleInfo) + .register(); + } + + private void handleHelp(CommandContext ctx) { + text.send(ctx.sender(), "/hologram list - list holograms"); + text.send(ctx.sender(), "/hologram create (type) (name)"); + } + + private void handleList(CommandContext ctx) { + for (Hologram h : manager.getHolograms()) { + text.send(ctx.sender(), " - " + h.getName()); + } + } + + private void handleNearby(CommandContext ctx) { + if (!(ctx.sender() instanceof Player player)) { + text.send(ctx.sender(), "Only players"); + return; + } + double range = 10; + if (ctx.args().length > 0) { + range = Double.parseDouble(ctx.args()[0]); + } + Location loc = player.getLocation(); + for (Hologram h : manager.getHolograms()) { + if (h.getLocation().getWorld().equals(loc.getWorld()) && + h.getLocation().distance(loc) <= range) { + text.send(player, " - " + h.getName()); + } + } + } + + private void handleCreate(CommandContext ctx) { + if (!(ctx.sender() instanceof Player player)) { + text.send(ctx.sender(), "Only players"); + return; + } + if (ctx.args().length < 2) { + text.send(ctx.sender(), "Usage: /hologram create (type) (name)"); + return; + } + String type = ctx.args()[0].toLowerCase(); + String name = ctx.args()[1]; + Location loc = player.getLocation(); + switch (type) { + case "text" -> manager.createTextHologram(name, loc); + case "item" -> manager.createItemHologram(name, loc, player.getInventory().getItemInMainHand()); + case "block" -> manager.createBlockHologram(name, loc, Material.STONE); + default -> { + text.send(ctx.sender(), "Unknown type"); + return; + } + } + text.send(ctx.sender(), "Created hologram " + name); + } + + private void handleRemove(CommandContext ctx) { + if (ctx.args().length < 1) { + text.send(ctx.sender(), "Usage: /hologram remove (name)"); + return; + } + manager.remove(ctx.args()[0]); + text.send(ctx.sender(), "Removed hologram"); + } + + private void handleCopy(CommandContext ctx) { + if (ctx.args().length < 2) { + text.send(ctx.sender(), "Usage: /hologram copy (src) (dest)"); + return; + } + Hologram src = manager.get(ctx.args()[0]); + if (src == null) { + text.send(ctx.sender(), "Not found"); + return; + } + Location loc = src.getLocation(); + if (src instanceof TextHologram th) { + TextHologram nh = manager.createTextHologram(ctx.args()[1], loc); + th.getLines().forEach(nh::addLine); + } else if (src instanceof ItemHologram ih) { + manager.createItemHologram(ctx.args()[1], loc, ih.getItem()); + } else if (src instanceof BlockHologram bh) { + manager.createBlockHologram(ctx.args()[1], loc, bh.getBlockType()); + } + text.send(ctx.sender(), "Copied hologram"); + } + + private void handleInfo(CommandContext ctx) { + if (ctx.args().length < 1) { + text.send(ctx.sender(), "Usage: /hologram info (name)"); + return; + } + Hologram h = manager.get(ctx.args()[0]); + if (h == null) { + text.send(ctx.sender(), "Not found"); + return; + } + text.send(ctx.sender(), "Name: " + h.getName()); + text.send(ctx.sender(), "Location: " + h.getLocation().toVector()); + if (h instanceof TextHologram th) { + text.send(ctx.sender(), "Lines: " + th.getLines().size()); + } + if (h instanceof ItemHologram ih) { + ItemStack item = ih.getItem(); + text.send(ctx.sender(), "Item: " + item.getType()); + } + if (h instanceof BlockHologram bh) { + text.send(ctx.sender(), "Block: " + bh.getBlockType()); + } + } +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java new file mode 100644 index 0000000..51a141b --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java @@ -0,0 +1,75 @@ +package gg.nextforge.textblockitemdisplay; + +import gg.nextforge.textblockitemdisplay.impl.SimpleBlockHologram; +import gg.nextforge.textblockitemdisplay.impl.SimpleItemHologram; +import gg.nextforge.textblockitemdisplay.impl.SimpleTextHologram; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manager responsible for creating and storing holograms. + */ +public class HologramManager { + private final Map holograms = new ConcurrentHashMap<>(); + + /** + * Creates a text hologram. + * + * @param name hologram name + * @param location spawn location + * @return created hologram + */ + public TextHologram createTextHologram(String name, Location location) { + TextHologram holo = new SimpleTextHologram(name, location); + holograms.put(name.toLowerCase(), holo); + return holo; + } + + /** + * Creates an item hologram. + */ + public ItemHologram createItemHologram(String name, Location location, ItemStack item) { + ItemHologram holo = new SimpleItemHologram(name, location, item); + holograms.put(name.toLowerCase(), holo); + return holo; + } + + /** + * Creates a block hologram. + */ + public BlockHologram createBlockHologram(String name, Location location, Material material) { + BlockHologram holo = new SimpleBlockHologram(name, location, material); + holograms.put(name.toLowerCase(), holo); + return holo; + } + + /** + * Remove a hologram by name. + */ + public void remove(String name) { + Hologram h = holograms.remove(name.toLowerCase()); + if (h != null) { + h.despawn(); + } + } + + /** + * Get a hologram by name. + */ + public Hologram get(String name) { + return holograms.get(name.toLowerCase()); + } + + /** + * @return list of all holograms. + */ + public List getHolograms() { + return new ArrayList<>(holograms.values()); + } +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/ItemHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/ItemHologram.java new file mode 100644 index 0000000..4fa8b8f --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/ItemHologram.java @@ -0,0 +1,20 @@ +package gg.nextforge.textblockitemdisplay; + +import org.bukkit.inventory.ItemStack; + +/** + * Represents a hologram displaying an item. + */ +public interface ItemHologram extends Hologram { + /** + * @return item displayed by the hologram. + */ + ItemStack getItem(); + + /** + * Sets the item to display. + * + * @param item new item + */ + void setItem(ItemStack item); +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java new file mode 100644 index 0000000..5adf511 --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java @@ -0,0 +1,35 @@ +package gg.nextforge.textblockitemdisplay; + +import java.util.List; + +/** + * Represents a hologram consisting of text lines. + */ +public interface TextHologram extends Hologram { + /** + * @return mutable list of lines of text. + */ + List getLines(); + + /** + * Sets a specific line. + * + * @param index line index + * @param text new text + */ + void setLine(int index, String text); + + /** + * Adds a line to the end. + * + * @param text line to add + */ + void addLine(String text); + + /** + * Removes a line by index. + * + * @param index index of line + */ + void removeLine(int index); +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/AbstractHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/AbstractHologram.java new file mode 100644 index 0000000..3b69198 --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/AbstractHologram.java @@ -0,0 +1,77 @@ +package gg.nextforge.textblockitemdisplay.impl; + +import gg.nextforge.textblockitemdisplay.Hologram; +import org.bukkit.Location; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Base implementation for holograms providing thread-safe updates. + */ +public abstract class AbstractHologram implements Hologram { + protected final String name; + protected Location location; + private boolean spawned; + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + protected AbstractHologram(String name, Location location) { + this.name = name; + this.location = location.clone(); + } + + @Override + public String getName() { + return name; + } + + @Override + public Location getLocation() { + lock.readLock().lock(); + try { + return location.clone(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void setLocation(Location location) { + lock.writeLock().lock(); + try { + this.location = location.clone(); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void spawn() { + lock.writeLock().lock(); + try { + this.spawned = true; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void despawn() { + lock.writeLock().lock(); + try { + this.spawned = false; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean isSpawned() { + lock.readLock().lock(); + try { + return spawned; + } finally { + lock.readLock().unlock(); + } + } +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleBlockHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleBlockHologram.java new file mode 100644 index 0000000..1ad06cb --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleBlockHologram.java @@ -0,0 +1,27 @@ +package gg.nextforge.textblockitemdisplay.impl; + +import gg.nextforge.textblockitemdisplay.BlockHologram; +import org.bukkit.Location; +import org.bukkit.Material; + +/** + * Basic implementation of a block hologram. + */ +public class SimpleBlockHologram extends AbstractHologram implements BlockHologram { + private Material material; + + public SimpleBlockHologram(String name, Location location, Material material) { + super(name, location); + this.material = material; + } + + @Override + public Material getBlockType() { + return material; + } + + @Override + public void setBlockType(Material material) { + this.material = material; + } +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleItemHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleItemHologram.java new file mode 100644 index 0000000..82b4778 --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleItemHologram.java @@ -0,0 +1,27 @@ +package gg.nextforge.textblockitemdisplay.impl; + +import gg.nextforge.textblockitemdisplay.ItemHologram; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; + +/** + * Basic implementation of an item hologram. + */ +public class SimpleItemHologram extends AbstractHologram implements ItemHologram { + private ItemStack item; + + public SimpleItemHologram(String name, Location location, ItemStack item) { + super(name, location); + this.item = item.clone(); + } + + @Override + public ItemStack getItem() { + return item.clone(); + } + + @Override + public void setItem(ItemStack item) { + this.item = item.clone(); + } +} diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java new file mode 100644 index 0000000..c293288 --- /dev/null +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java @@ -0,0 +1,39 @@ +package gg.nextforge.textblockitemdisplay.impl; + +import gg.nextforge.textblockitemdisplay.TextHologram; +import org.bukkit.Location; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Basic implementation of a text hologram. + */ +public class SimpleTextHologram extends AbstractHologram implements TextHologram { + private final List lines = Collections.synchronizedList(new ArrayList<>()); + + public SimpleTextHologram(String name, Location location) { + super(name, location); + } + + @Override + public List getLines() { + return lines; + } + + @Override + public void setLine(int index, String text) { + lines.set(index, text); + } + + @Override + public void addLine(String text) { + lines.add(text); + } + + @Override + public void removeLine(int index) { + lines.remove(index); + } +} diff --git a/src/test/java/gg/nextforge/textblockitemdisplay/HologramManagerTest.java b/src/test/java/gg/nextforge/textblockitemdisplay/HologramManagerTest.java new file mode 100644 index 0000000..6e0e531 --- /dev/null +++ b/src/test/java/gg/nextforge/textblockitemdisplay/HologramManagerTest.java @@ -0,0 +1,43 @@ +package gg.nextforge.textblockitemdisplay; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.inventory.ItemStack; +import org.bukkit.Material; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link HologramManager}. + */ +public class HologramManagerTest { + + private HologramManager manager; + private World world; + + @BeforeEach + void setUp() { + manager = new HologramManager(); + world = mock(World.class); + when(world.getName()).thenReturn("world"); + } + + @Test + void createTextHologram() { + Location loc = new Location(world, 0, 0, 0); + TextHologram holo = manager.createTextHologram("test", loc); + assertNotNull(holo); + assertEquals(holo, manager.get("test")); + } + + @Test + void copyItemHologram() { + Location loc = new Location(world, 1, 2, 3); + ItemHologram ih = manager.createItemHologram("i", loc, new ItemStack(Material.STONE)); + manager.createItemHologram("copy", ih.getLocation(), ih.getItem()); + assertEquals(2, manager.getHolograms().size()); + } +} From fad56abd201da1b0219e0fb657616db23b49b6d3 Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:58:25 +0200 Subject: [PATCH 02/17] Update src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../nextforge/textblockitemdisplay/impl/SimpleTextHologram.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java index c293288..cf4f01a 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java @@ -19,7 +19,7 @@ public SimpleTextHologram(String name, Location location) { @Override public List getLines() { - return lines; + return Collections.unmodifiableList(new ArrayList<>(lines)); } @Override From afdf62489301a1e0246222626acf1ce8ae73eb82 Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:58:31 +0200 Subject: [PATCH 03/17] Update src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../java/gg/nextforge/textblockitemdisplay/TextHologram.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java index 5adf511..6b7750c 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/TextHologram.java @@ -7,7 +7,7 @@ */ public interface TextHologram extends Hologram { /** - * @return mutable list of lines of text. + * @return immutable list of lines of text. */ List getLines(); From 100018962ccfc7b5da8b852544525b2e5208caa5 Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:58:40 +0200 Subject: [PATCH 04/17] Update src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../textblockitemdisplay/impl/SimpleTextHologram.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java index cf4f01a..e3210cf 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java @@ -24,6 +24,9 @@ public List getLines() { @Override public void setLine(int index, String text) { + if (index < 0 || index >= lines.size()) { + throw new IllegalArgumentException("Index out of bounds: " + index + ". Valid range is [0, " + (lines.size() - 1) + "]."); + } lines.set(index, text); } From 0205def9792decefa1bccf5ce8cbb26c105808a3 Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:58:48 +0200 Subject: [PATCH 05/17] Update src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../textblockitemdisplay/impl/SimpleTextHologram.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java index e3210cf..9dc6f8c 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java @@ -37,6 +37,9 @@ public void addLine(String text) { @Override public void removeLine(int index) { + if (index < 0 || index >= lines.size()) { + throw new IllegalArgumentException("Index out of bounds: " + index + ". Valid range: 0 to " + (lines.size() - 1)); + } lines.remove(index); } } From 3c6a4fbbfce38c57ad0d577d929c08de11bf0b11 Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:58:56 +0200 Subject: [PATCH 06/17] Update src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../gg/nextforge/textblockitemdisplay/HologramManager.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java index 51a141b..173d522 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java @@ -33,6 +33,11 @@ public TextHologram createTextHologram(String name, Location location) { /** * Creates an item hologram. + * + * @param name hologram name + * @param location spawn location + * @param item item to display + * @return created hologram */ public ItemHologram createItemHologram(String name, Location location, ItemStack item) { ItemHologram holo = new SimpleItemHologram(name, location, item); From 3216560389f97fdf3d3d1838e1437faa23d7bebc Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:59:03 +0200 Subject: [PATCH 07/17] Update src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../gg/nextforge/textblockitemdisplay/HologramManager.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java index 173d522..064b329 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java @@ -47,6 +47,11 @@ public ItemHologram createItemHologram(String name, Location location, ItemStack /** * Creates a block hologram. + * + * @param name hologram name + * @param location spawn location + * @param material block material + * @return created hologram */ public BlockHologram createBlockHologram(String name, Location location, Material material) { BlockHologram holo = new SimpleBlockHologram(name, location, material); From 9882d2cac711c445b5c8807736e247bc6c779799 Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:59:11 +0200 Subject: [PATCH 08/17] Update src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../gg/nextforge/textblockitemdisplay/HologramManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java index 064b329..3ccbc6d 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java @@ -60,7 +60,10 @@ public BlockHologram createBlockHologram(String name, Location location, Materia } /** - * Remove a hologram by name. + * Removes a hologram by name. + * + * @param name the name of the hologram to remove + * If no hologram exists with the given name, no action is taken. */ public void remove(String name) { Hologram h = holograms.remove(name.toLowerCase()); From 856a8576bbfd9892257b54dd61b1ac62aaa4b3fd Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:59:17 +0200 Subject: [PATCH 09/17] Update src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../gg/nextforge/textblockitemdisplay/HologramManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java index 3ccbc6d..96c34b7 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/HologramManager.java @@ -73,7 +73,10 @@ public void remove(String name) { } /** - * Get a hologram by name. + * Retrieves a hologram by its name. + * + * @param name the name of the hologram to retrieve + * @return the hologram associated with the given name, or {@code null} if no such hologram exists */ public Hologram get(String name) { return holograms.get(name.toLowerCase()); From 7591965ee592d8e85cd9c15c6ce86cc8a4b3558a Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:59:23 +0200 Subject: [PATCH 10/17] Update src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../gg/nextforge/textblockitemdisplay/HologramCommand.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java b/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java index fe4a0d7..3011373 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java @@ -58,7 +58,12 @@ private void handleNearby(CommandContext ctx) { } double range = 10; if (ctx.args().length > 0) { - range = Double.parseDouble(ctx.args()[0]); + try { + range = Double.parseDouble(ctx.args()[0]); + } catch (NumberFormatException e) { + text.send(ctx.sender(), "Invalid range. Please provide a valid number."); + return; + } } Location loc = player.getLocation(); for (Hologram h : manager.getHolograms()) { From 737874e9d74f3e6d5e07ee2c8238fa356f8c59aa Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:01:12 +0200 Subject: [PATCH 11/17] Update src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../textblockitemdisplay/impl/SimpleTextHologram.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java index 9dc6f8c..da17d2b 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java @@ -19,7 +19,9 @@ public SimpleTextHologram(String name, Location location) { @Override public List getLines() { - return Collections.unmodifiableList(new ArrayList<>(lines)); + synchronized (lines) { + return Collections.unmodifiableList(new ArrayList<>(lines)); + } } @Override From e98077f8b97a064eae6051731b47d3c8debed169 Mon Sep 17 00:00:00 2001 From: Keine Secrets <80967338+whynotmax@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:03:37 +0200 Subject: [PATCH 12/17] Update src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../nextforge/textblockitemdisplay/impl/SimpleTextHologram.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java index da17d2b..5d5fce6 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java @@ -40,7 +40,7 @@ public void addLine(String text) { @Override public void removeLine(int index) { if (index < 0 || index >= lines.size()) { - throw new IllegalArgumentException("Index out of bounds: " + index + ". Valid range: 0 to " + (lines.size() - 1)); + throw new IllegalArgumentException("Index out of bounds: " + index + ". Valid range is [0, " + (lines.size() - 1) + "]."); } lines.remove(index); } From 4ac0345ebf2e9afe22d3885cd82d34405a8ff17b Mon Sep 17 00:00:00 2001 From: KeineSecrets Date: Fri, 11 Jul 2025 14:10:49 +0200 Subject: [PATCH 13/17] Add hologram command configuration and refactor command registration --- src/main/java/gg/nextforge/NextCorePlugin.java | 5 ++--- .../builtin}/HologramCommand.java | 9 +++++---- .../java/gg/nextforge/command/builtin/NPCCommand.java | 4 ++-- .../textblockitemdisplay/impl/AbstractHologram.java | 1 + .../textblockitemdisplay/impl/SimpleTextHologram.java | 2 +- src/main/resources/config.yml | 9 ++++++++- 6 files changed, 19 insertions(+), 11 deletions(-) rename src/main/java/gg/nextforge/{textblockitemdisplay => command/builtin}/HologramCommand.java (94%) diff --git a/src/main/java/gg/nextforge/NextCorePlugin.java b/src/main/java/gg/nextforge/NextCorePlugin.java index fe44075..35175ed 100644 --- a/src/main/java/gg/nextforge/NextCorePlugin.java +++ b/src/main/java/gg/nextforge/NextCorePlugin.java @@ -1,16 +1,15 @@ package gg.nextforge; +import gg.nextforge.command.builtin.HologramCommand; import gg.nextforge.command.builtin.NPCCommand; import gg.nextforge.command.builtin.NextCoreCommand; import gg.nextforge.config.ConfigFile; -import gg.nextforge.config.ConfigManager; import gg.nextforge.console.ConsoleHeader; import gg.nextforge.plugin.NextForgePlugin; import gg.nextforge.scheduler.CoreScheduler; import gg.nextforge.scheduler.ScheduledTask; import gg.nextforge.updater.CoreAutoUpdater; import lombok.Getter; -import org.checkerframework.checker.units.qual.N; import java.io.IOException; import java.util.UUID; @@ -48,7 +47,7 @@ public void enable(boolean isReload) { new NextCoreCommand(this); new NPCCommand(this, getNpcManager()); - new gg.nextforge.textblockitemdisplay.HologramCommand(this, getHologramManager()); + new HologramCommand(this, getHologramManager()); ConsoleHeader.send(this); diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java b/src/main/java/gg/nextforge/command/builtin/HologramCommand.java similarity index 94% rename from src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java rename to src/main/java/gg/nextforge/command/builtin/HologramCommand.java index 3011373..d8af66b 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/HologramCommand.java +++ b/src/main/java/gg/nextforge/command/builtin/HologramCommand.java @@ -1,9 +1,10 @@ -package gg.nextforge.textblockitemdisplay; +package gg.nextforge.command.builtin; import gg.nextforge.NextCorePlugin; import gg.nextforge.command.CommandContext; import gg.nextforge.command.CommandManager; import gg.nextforge.text.TextManager; +import gg.nextforge.textblockitemdisplay.*; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -21,13 +22,13 @@ public HologramCommand(NextCorePlugin plugin, HologramManager manager) { this.plugin = plugin; this.manager = manager; this.text = plugin.getTextManager(); - register(); + if (plugin.getConfigFile().getBoolean("commands.hologram.enabled", true)) registerCommands(); } - private void register() { + private void registerCommands() { CommandManager cm = plugin.getCommandManager(); cm.command("hologram") - .permission("nextforge.command.hologram") + .permission(plugin.getConfigFile().getString("commands.hologram.permission", "nextforge.command.hologram")) .description("Manage holograms") .executor(this::handleHelp) .subcommand("help", this::handleHelp) diff --git a/src/main/java/gg/nextforge/command/builtin/NPCCommand.java b/src/main/java/gg/nextforge/command/builtin/NPCCommand.java index 7b87201..56f5af8 100644 --- a/src/main/java/gg/nextforge/command/builtin/NPCCommand.java +++ b/src/main/java/gg/nextforge/command/builtin/NPCCommand.java @@ -31,12 +31,12 @@ public NPCCommand(NextCorePlugin plugin, NPCManager npcManager) { this.plugin = plugin; this.npcManager = npcManager; this.textManager = plugin.getTextManager(); - registerCommands(); + if (plugin.getConfigFile().getBoolean("commands.npc.enabled", true)) registerCommands(); } private void registerCommands() { plugin.getCommandManager().command("npc") - .permission("nextforge.command.npc") + .permission(plugin.getConfigFile().getString("commands.npc.permission", "nextforge.command.npc")) .description("Manage your npc's with ease.") .aliases("npcs", "npcmanager") .executor(this::handleHelp) diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/AbstractHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/AbstractHologram.java index 3b69198..781f30c 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/impl/AbstractHologram.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/AbstractHologram.java @@ -11,6 +11,7 @@ public abstract class AbstractHologram implements Hologram { protected final String name; protected Location location; + protected boolean isTransient; private boolean spawned; private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); diff --git a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java index 5d5fce6..fe27b66 100644 --- a/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java +++ b/src/main/java/gg/nextforge/textblockitemdisplay/impl/SimpleTextHologram.java @@ -20,7 +20,7 @@ public SimpleTextHologram(String name, Location location) { @Override public List getLines() { synchronized (lines) { - return Collections.unmodifiableList(new ArrayList<>(lines)); + return List.copyOf(lines); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1742ffd..5f76d3f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,4 +2,11 @@ updater: auto_update: true # Enable automatic updates check_interval: 7200000 # Check for updates every 2 hours (in milliseconds) update_branch: 'master' # Branch to check for updates | Available branches: master, dev - disable_update_message: false # Disable the update message in the console \ No newline at end of file + disable_update_message: false # Disable the update message in the console +commands: + npc: + enabled: true # Enable the /npc command + permission: 'nextcore.command.npc' # Permission required to use the /npc command + hologram: + enabled: true # Enable the /hologram command + permission: 'nextcore.command.hologram' # Permission required to use the /hologram command \ No newline at end of file From 9b48599ff4c54dae9c395102a012f13339813bbc Mon Sep 17 00:00:00 2001 From: KeineSecrets Date: Sat, 12 Jul 2025 10:51:03 +0200 Subject: [PATCH 14/17] Update version to 1.1-SNAPSHOT and configure publishing repository --- build.gradle | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index f363bf8..edaad4f 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group = 'gg.nextforge' -version = '1.0' +version = '1.1-SNAPSHOT' repositories { mavenCentral() @@ -69,6 +69,12 @@ java { withSourcesJar() } +javadoc { + options.encoding = 'UTF-8' + options.addStringOption('Xdoclint:none', '-quiet') + failOnError = false +} + tasks { compileJava { options.encoding = "UTF-8" @@ -99,13 +105,21 @@ shadowJar { publishing { repositories { - mavenLocal() + maven { + name = 'NextForge' + url = uri("http://87.106.178.7:4231/releases") + allowInsecureProtocol = true + credentials { + username = "push_access" + password = project.findProperty("core_token") ?: System.getenv("CORE_TOKEN") ?: "" + } + } } publications { create("mavenJava", MavenPublication) { from components.java groupId = project.group - artifactId = 'nextforge' + artifactId = 'nextcore' version = project.version pom { From ec5d8cc2ee9538dcec4a22204fe1c8c793687dcc Mon Sep 17 00:00:00 2001 From: KeineSecrets Date: Sat, 12 Jul 2025 11:06:09 +0200 Subject: [PATCH 15/17] Add NPC interaction features and event handling for actions --- .../java/gg/nextforge/NextCorePlugin.java | 7 ++ .../java/gg/nextforge/npc/NPCListener.java | 68 +++++++++++++++ .../java/gg/nextforge/npc/NPCManager.java | 87 +++++++++++++++---- src/main/java/gg/nextforge/npc/model/NPC.java | 36 +++++++- 4 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 src/main/java/gg/nextforge/npc/NPCListener.java diff --git a/src/main/java/gg/nextforge/NextCorePlugin.java b/src/main/java/gg/nextforge/NextCorePlugin.java index 35175ed..bf828fc 100644 --- a/src/main/java/gg/nextforge/NextCorePlugin.java +++ b/src/main/java/gg/nextforge/NextCorePlugin.java @@ -5,11 +5,14 @@ import gg.nextforge.command.builtin.NextCoreCommand; import gg.nextforge.config.ConfigFile; import gg.nextforge.console.ConsoleHeader; +import gg.nextforge.event.EventBus; +import gg.nextforge.npc.NPCListener; import gg.nextforge.plugin.NextForgePlugin; import gg.nextforge.scheduler.CoreScheduler; import gg.nextforge.scheduler.ScheduledTask; import gg.nextforge.updater.CoreAutoUpdater; import lombok.Getter; +import org.bukkit.Bukkit; import java.io.IOException; import java.util.UUID; @@ -49,6 +52,10 @@ public void enable(boolean isReload) { new NPCCommand(this, getNpcManager()); new HologramCommand(this, getHologramManager()); + if (getConfigFile().getBoolean("commands.npc.enabled", true)) { + Bukkit.getPluginManager().registerEvents(new NPCListener(getNpcManager()), this); + } + ConsoleHeader.send(this); getSLF4JLogger().info("[NextForge] {} v{} is running as the core plugin.", getName(), getPluginVersion()); diff --git a/src/main/java/gg/nextforge/npc/NPCListener.java b/src/main/java/gg/nextforge/npc/NPCListener.java new file mode 100644 index 0000000..99414a9 --- /dev/null +++ b/src/main/java/gg/nextforge/npc/NPCListener.java @@ -0,0 +1,68 @@ +package gg.nextforge.npc; + +import gg.nextforge.npc.model.NPC; +import gg.nextforge.scheduler.CoreScheduler; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; + +public class NPCListener implements Listener { + private final NPCManager manager; + + public NPCListener(NPCManager manager) { + this.manager = manager; + handleTurnToPlayer(); + } + + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent e) { + NPC npc = manager.getByEntity(e.getRightClicked()); + if (npc == null) return; + + // any_click + right_click + runActions(npc, e.getPlayer(), NPC.ClickType.ANY_CLICK); + runActions(npc, e.getPlayer(), NPC.ClickType.RIGHT_CLICK); + } + + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player)) return; + NPC npc = manager.getByEntity(e.getEntity()); + if (npc == null) return; + + // any_click + left_click + runActions(npc, (Player)e.getDamager(), NPC.ClickType.ANY_CLICK); + runActions(npc, (Player)e.getDamager(), NPC.ClickType.LEFT_CLICK); + e.setCancelled(true); // Optional: NPC darf keinen Schaden bekommen + } + + private void runActions(NPC npc, Player player, NPC.ClickType click) { + for (NPC.CommandAction ca : npc.listActions(click)) { + switch (ca.getActionType()) { + case PLAYER_COMMAND: + player.performCommand(ca.getCommand()); + break; + case CONSOLE_COMMAND: + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), ca.getCommand()); + break; + } + } + } + + // Dreh-Task (kann auch als Repeating Task in NPCManager laufen) + public void handleTurnToPlayer() { + CoreScheduler.runTimer(() -> { + for (NPC npc : manager.getNpcs().values()) { + if (!npc.isTurnToPlayer()) continue; + Player nearest = manager.getNearestPlayer(npc, npc.getTurnToPlayerDistance()); + if (nearest != null) { + manager.rotateNpcHead(npc, nearest.getLocation()); + } + } + }, 0L, 2L); + } +} + diff --git a/src/main/java/gg/nextforge/npc/NPCManager.java b/src/main/java/gg/nextforge/npc/NPCManager.java index 03b9347..39abee9 100644 --- a/src/main/java/gg/nextforge/npc/NPCManager.java +++ b/src/main/java/gg/nextforge/npc/NPCManager.java @@ -13,14 +13,14 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import java.util.function.Function; +import java.util.stream.Collectors; /** * Manager class for handling NPCs (Non-Player Characters) in the NextForge framework. @@ -199,18 +199,30 @@ private NPC loadFromSection(String id, ConfigurationSection sec) { .scale(sec.getDouble("scale", 1.0)) .transientNPC(sec.getBoolean("transient", false)) .interactionCooldown(sec.getInt("cooldown", 0)) - .turnToPlayer(sec.getBoolean("turnToPlayer")) + .turnToPlayer(sec.getBoolean("turnToPlayer", false)) .turnToPlayerDistance(sec.getDouble("turnToPlayerDistance", 3.0)) - .attributes(new HashMap<>()) - .actions(new ArrayList<>()) + .actions(new EnumMap<>(NPC.ClickType.class)) .build(); - ConfigurationSection attr = sec.getConfigurationSection("attributes"); - if (attr != null) { - for (String key : attr.getKeys(false)) { - npc.getAttributes().put(key, attr.getString(key)); + + // Load actions + ConfigurationSection actionsSec = sec.getConfigurationSection("actions"); + if (actionsSec != null) { + for (String clickKey : actionsSec.getKeys(false)) { + try { + NPC.ClickType clickType = NPC.ClickType.valueOf(clickKey); + List entries = actionsSec.getStringList(clickKey); + for (String entry : entries) { + String[] parts = entry.split(":", 2); + NPC.ActionType type = NPC.ActionType.valueOf(parts[0]); + String cmd = parts.length > 1 ? parts[1] : ""; + npc.getActions().computeIfAbsent(clickType, k -> new ArrayList<>()) + .add(new NPC.CommandAction(type, cmd)); + } + } catch (IllegalArgumentException ignored) {} } } - npc.getActions().addAll(sec.getStringList("actions")); + + // Load location ConfigurationSection loc = sec.getConfigurationSection("location"); if (loc != null) { World w = Bukkit.getWorld(loc.getString("world", "world")); @@ -260,10 +272,17 @@ private void saveToSection(NPC npc, ConfigurationSection sec) { sec.set("cooldown", npc.getInteractionCooldown()); sec.set("turnToPlayer", npc.isTurnToPlayer()); sec.set("turnToPlayerDistance", npc.getTurnToPlayerDistance()); - for (String key : npc.getAttributes().keySet()) { - sec.set("attributes." + key, npc.getAttributes().get(key)); + + // Save actions + ConfigurationSection actionsSec = sec.createSection("actions"); + for (Map.Entry> e : npc.getActions().entrySet()) { + List list = e.getValue().stream() + .map(a -> a.getActionType().name() + ":" + a.getCommand()) + .collect(Collectors.toList()); + actionsSec.set(e.getKey().name(), list); } - sec.set("actions", npc.getActions()); + + // Save location if (npc.getLocation() != null) { ConfigurationSection loc = sec.createSection("location"); loc.set("world", npc.getLocation().getWorld().getName()); @@ -274,4 +293,42 @@ private void saveToSection(NPC npc, ConfigurationSection sec) { loc.set("pitch", npc.getLocation().getPitch()); } } + + public NPC getByEntity(Entity entity) { + if (entity == null || !npcs.containsKey(entity.getUniqueId().toString())) { + return null; // Return null if the entity is not an NPC + } + return npcs.get(entity.getUniqueId().toString()); // Get the NPC by its unique ID + } + + public Player getNearestPlayer(NPC npc, double turnToPlayerDistance) { + if (npc.getLocation() == null) return null; // Ensure the NPC has a valid location + double closestDistance = turnToPlayerDistance; + Player closestPlayer = null; + + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getWorld().equals(npc.getLocation().getWorld())) { + double distance = player.getLocation().distance(npc.getLocation()); + if (distance <= closestDistance) { + closestDistance = distance; + closestPlayer = player; + } + } + } + return closestPlayer; // Return the nearest player within the specified distance + } + + public void rotateNpcHead(NPC npc, @NotNull Location location) { + if (npc.getEntity() == null || !(npc.getEntity() instanceof LivingEntity livingEntity)) { + return; // Ensure the NPC entity is valid and is a LivingEntity + } + Location npcLocation = npc.getLocation(); + if (npcLocation == null) return; // Ensure the NPC has a valid location + + double deltaX = location.getX() - npcLocation.getX(); + double deltaZ = location.getZ() - npcLocation.getZ(); + double yaw = Math.toDegrees(Math.atan2(deltaZ, deltaX)) - 90; // Calculate yaw based on the target location + + livingEntity.setRotation((float) yaw, livingEntity.getLocation().getPitch()); // Set the NPC's head rotation + } } \ No newline at end of file diff --git a/src/main/java/gg/nextforge/npc/model/NPC.java b/src/main/java/gg/nextforge/npc/model/NPC.java index 45eb62e..bcc66d8 100644 --- a/src/main/java/gg/nextforge/npc/model/NPC.java +++ b/src/main/java/gg/nextforge/npc/model/NPC.java @@ -1,13 +1,13 @@ package gg.nextforge.npc.model; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import org.bukkit.ChatColor; import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.entity.Entity; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -32,7 +32,35 @@ public class NPC { private boolean turnToPlayer; private double turnToPlayerDistance; private Map attributes; - private List actions; + private Map> actions; private Location location; private Entity entity; // runtime only + + @Getter @Setter @NoArgsConstructor + @AllArgsConstructor + public static class CommandAction { + private ActionType actionType; // PLAYER_COMMAND vs CONSOLE_COMMAND + private String command; // Befehl ohne Slash + } + + public enum ClickType { ANY_CLICK, LEFT_CLICK, RIGHT_CLICK } + public enum ActionType { PLAYER_COMMAND, CONSOLE_COMMAND } + + // Methods für action add/remove/clear/list + public void addAction(ClickType click, CommandAction action) { + actions.computeIfAbsent(click, k -> new ArrayList<>()).add(action); + } + + public boolean removeAction(ClickType click, int index) { + List list = actions.get(click); + return list != null && list.remove(index) != null; + } + + public void clearActions(ClickType click) { + actions.remove(click); + } + + public List listActions(ClickType click) { + return actions.getOrDefault(click, Collections.emptyList()); + } } From 8e238381affb37593a265220c8573e057735e585 Mon Sep 17 00:00:00 2001 From: KeineSecrets Date: Sat, 12 Jul 2025 11:06:42 +0200 Subject: [PATCH 16/17] Update version to 2.0-SNAPSHOT in build configuration --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index edaad4f..2d19335 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group = 'gg.nextforge' -version = '1.1-SNAPSHOT' +version = '2.0-SNAPSHOT' repositories { mavenCentral() From ee62d50615696bbb577ef95001e43a8a7509d47d Mon Sep 17 00:00:00 2001 From: KeineSecrets Date: Sat, 12 Jul 2025 11:14:30 +0200 Subject: [PATCH 17/17] Refactor NPC action handling to use a map for actions and improve command structure --- .../nextforge/command/builtin/NPCCommand.java | 199 ++++++++++++++---- 1 file changed, 153 insertions(+), 46 deletions(-) diff --git a/src/main/java/gg/nextforge/command/builtin/NPCCommand.java b/src/main/java/gg/nextforge/command/builtin/NPCCommand.java index 56f5af8..549fd94 100644 --- a/src/main/java/gg/nextforge/command/builtin/NPCCommand.java +++ b/src/main/java/gg/nextforge/command/builtin/NPCCommand.java @@ -15,10 +15,7 @@ import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.ItemStack; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class NPCCommand { @@ -133,7 +130,7 @@ private void handleCreate(CommandContext ctx) { .turnToPlayer(false) .turnToPlayerDistance(3.0) .attributes(new java.util.HashMap<>()) - .actions(new java.util.ArrayList<>()) + .actions(new java.util.HashMap<>()) .location(loc) .build(); npcManager.register(npc); @@ -170,7 +167,7 @@ private void handleCopy(CommandContext ctx) { .turnToPlayer(orig.isTurnToPlayer()) .turnToPlayerDistance(orig.getTurnToPlayerDistance()) .attributes(new java.util.HashMap<>(orig.getAttributes())) - .actions(new java.util.ArrayList<>(orig.getActions())) + .actions(new java.util.HashMap<>(orig.getActions())) .location(orig.getLocation()) .build(); npcManager.register(copy); @@ -263,10 +260,12 @@ private void handleInfo(CommandContext ctx) { if (npc.getActions().isEmpty()) { textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.info.actions.empty", " No actions defined.")); } else { - for (String action : npc.getActions()) { - textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.info.actions.line", " %action% (%trigger%)").replace("%action%", action) - .replace("%trigger%", action.split(" ")[0]) // Assuming the trigger is the first word in the action string - ); + for (NPC.ClickType action : npc.getActions().keySet()) { + textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.info.actions.line", " %action% (%trigger%)").replace("%action%", npc.getActions().get(action).stream() + // Aus jedem CommandAction nur den eigentlichen Befehl ziehen + .map(NPC.CommandAction::getCommand) + .collect(Collectors.joining(", ")) + .replace("%trigger%", action.name()))); } } textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.info.attributes.header", " Attributes:")); @@ -843,55 +842,163 @@ private void handleTeleport(CommandContext ctx) { } private void handleAction(CommandContext ctx) { - if (ctx.args().length < 3) { - textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.action.usage", "%prefix% Usage: /npc action (npc) add|remove|clear|list [params]")); + String[] args = ctx.args(); + // /npc action add|remove|clear|list [...] + if (args.length < 2) { + textManager.send(ctx.sender(), + plugin.getMessagesFile() + .getString("commands.npc.action.usage", + "%prefix% Usage: /npc action add|remove|clear|list [params]")); return; } - String id = ctx.args()[0]; - String sub = ctx.args()[1]; - NPC npc = npcManager.getNpcs().get(id); - if (npc == null) { textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.action.not-found", "%prefix% NPC '%name%' not found.")); return; } - switch (sub.toLowerCase()) { - case "list": + + String id = args[0]; + String sub = args[1].toLowerCase(); + NPC npc = npcManager.getNpcs().get(id); + + if (npc == null) { + textManager.send(ctx.sender(), + plugin.getMessagesFile() + .getString("commands.npc.action.not-found", + "%prefix% NPC '%name%' not found.") + .replace("%name%", id)); + return; + } + + switch (sub) { + case "list" -> { if (npc.getActions().isEmpty()) { - textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.action.list.empty", "%prefix% NPC '%name%' has no actions.").replace("%name%", id)); + textManager.send(ctx.sender(), + plugin.getMessagesFile() + .getString("commands.npc.action.list.empty", + "%prefix% NPC '%name%' has no actions.") + .replace("%name%", id)); } else { - for (String header : plugin.getMessagesFile().getStringList("commands.npc.action.list.header")) { - header = header.replace("%name%", id); - textManager.send(ctx.sender(), header); + // Header + for (String line : plugin.getMessagesFile().getStringList("commands.npc.action.list.header")) { + textManager.send(ctx.sender(), line.replace("%name%", id)); } - for (String action : npc.getActions()) { - textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.action.list.line", " %action%").replace("%action%", action)); + // Jede Click-Type einzeln listen + for (NPC.ClickType click : npc.getActions().keySet()) { + String joined = npc.getActions() + .get(click) + .stream() + .map(ca -> ca.getActionType().name() + ":" + ca.getCommand()) + .collect(Collectors.joining(", ")); + String template = plugin.getMessagesFile() + .getString("commands.npc.action.list.line", + " %trigger% – %action%"); + String filled = template + .replace("%trigger%", click.name()) + .replace("%action%", joined); + textManager.send(ctx.sender(), filled); } - for (String footer : plugin.getMessagesFile().getStringList("commands.npc.action.list.footer")) { - footer = footer.replace("%name%", id); - textManager.send(ctx.sender(), footer); + // Footer + for (String line : plugin.getMessagesFile().getStringList("commands.npc.action.list.footer")) { + textManager.send(ctx.sender(), line.replace("%name%", id)); } } - break; - case "clear": - npcManager.modify(id, n -> { n.getActions().clear(); return n; }); - npcManager.save(); - textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.action.clear.success", "%prefix% All actions cleared for NPC '%name%'.").replace("%name%", id)); - break; - case "add": - String action = ctx.args()[2]; - npcManager.modify(id, n -> { n.getActions().add(action); return n; }); + } + + case "clear" -> { + // Optional: /npc action clear [ClickType] + if (args.length == 3) { + try { + NPC.ClickType click = NPC.ClickType.valueOf(args[2].toUpperCase()); + npcManager.modify(id, n -> { n.getActions().remove(click); return n; }); + } catch (IllegalArgumentException ex) { + textManager.send(ctx.sender(), + "Unknown click type: " + args[2] + ""); + return; + } + } else { + // Ohne ClickType → alle löschen + npcManager.modify(id, n -> { n.getActions().clear(); return n; }); + } npcManager.save(); - textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.action.add.success", "%prefix% Action '%action%' added to NPC '%name%'.").replace("%action%", action).replace("%name%", id)); - break; - case "remove": - String rem = ctx.args()[2]; - npcManager.modify(id, n -> { n.getActions().remove(rem); return n; }); + textManager.send(ctx.sender(), + plugin.getMessagesFile() + .getString("commands.npc.action.clear.success", + "%prefix% Actions cleared for NPC '%name%'.") + .replace("%name%", id)); + } + + case "add" -> { + // /npc action add + if (args.length < 5) { + textManager.send(ctx.sender(), + "Usage: /npc action " + id + " add "); + return; + } + NPC.ClickType click; + NPC.ActionType type; + try { + click = NPC.ClickType.valueOf(args[2].toUpperCase()); + type = NPC.ActionType.valueOf(args[3].toUpperCase()); + } catch (IllegalArgumentException ex) { + textManager.send(ctx.sender(), + "Invalid click- or action-type!"); + return; + } + String command = String.join(" ", Arrays.copyOfRange(args, 4, args.length)); + npcManager.modify(id, n -> { + n.getActions() + .computeIfAbsent(click, k -> new ArrayList<>()) + .add(new NPC.CommandAction(type, command)); + return n; + }); npcManager.save(); - textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.action.remove.success", "%prefix% Action '%action%' removed from NPC '%name%'.").replace("%action%", rem).replace("%name%", id)); - break; - default: - textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.action.usage", "%prefix% Usage: /npc action (npc) add|remove|clear|list [params]")); - break; + textManager.send(ctx.sender(), + plugin.getMessagesFile() + .getString("commands.npc.action.add.success", + "%prefix% Action '%action%' added to NPC '%name%'.") + .replace("%action%", type.name() + ":" + command) + .replace("%name%", id)); + } + + case "remove" -> { + // /npc action remove + if (args.length != 4) { + textManager.send(ctx.sender(), + "Usage: /npc action " + id + " remove "); + return; + } + NPC.ClickType click; + int index; + try { + click = NPC.ClickType.valueOf(args[2].toUpperCase()); + index = Integer.parseInt(args[3]); + } catch (IllegalArgumentException ex) { + textManager.send(ctx.sender(), + "Invalid click-type or index!"); + return; + } + final boolean removed = npcManager.getNpcs() + .get(id) + .removeAction(click, index); + if (removed) { + npcManager.save(); + textManager.send(ctx.sender(), + plugin.getMessagesFile() + .getString("commands.npc.action.remove.success", + "%prefix% Action removed from NPC '%name%'.") + .replace("%name%", id)); + } else { + textManager.send(ctx.sender(), + "No action at index " + index + " for " + click.name() + "!"); + } + } + + default -> { + textManager.send(ctx.sender(), + plugin.getMessagesFile() + .getString("commands.npc.action.usage", + "%prefix% Usage: /npc action add|remove|clear|list [params]")); + } } } + private void handleInteractionCooldown(CommandContext ctx) { if (ctx.args().length < 2) { textManager.send(ctx.sender(), plugin.getMessagesFile().getString("commands.npc.interaction_cooldown.usage", "%prefix% Usage: /npc interaction_cooldown (npc) [disabled | ticks]"));