diff --git a/build.gradle.kts b/build.gradle.kts index 5c45515a..50c6e120 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ plugins { `maven-publish` id("xyz.jpenilla.run-paper") version "3.0.2" id("io.github.goooler.shadow") version "8.1.8" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" apply false + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" apply false } repositories { @@ -38,20 +38,19 @@ repositories { dependencies { implementation(project(":core")) - implementation(project(":v1_21_R6")) - implementation(project(":v1_21_R5")) - implementation(project(":v1_21_R4")) - implementation(project(":v1_21_R3")) - implementation(project(":v1_21_R2")) - implementation(project(":v1_21_R1")) - implementation(project(":v1_21_R0")) - implementation(project(":v1_20_R4")) - implementation(project(":v1_20_R3", "reobf")) - implementation(project(":v1_20_R2", "reobf")) - implementation(project(":v1_20_R1", "reobf")) + implementation(project(":v26_2_R1", configuration = "default")) + implementation(project(":v26_1_R1", configuration = "default")) + implementation(project(":v1_21_R6", configuration = "default")) + implementation(project(":v1_21_R5", configuration = "default")) + implementation(project(":v1_21_R4", configuration = "default")) + implementation(project(":v1_21_R3", configuration = "default")) + implementation(project(":v1_21_R2", configuration = "default")) + implementation(project(":v1_21_R1", configuration = "default")) + implementation(project(":v1_21_R0", configuration = "default")) + implementation(project(":v1_20_R4", configuration = "default")) } -var pluginVersion = "1.7.9" +var pluginVersion = "1.7.10-pre2" allprojects { group = "dev.foxikle" @@ -59,7 +58,7 @@ allprojects { description = "CustomNPCs" } -java.sourceCompatibility = JavaVersion.VERSION_21 +java.sourceCompatibility = JavaVersion.VERSION_25 val javadocJar = tasks.register("javadocJar") { archiveClassifier.set("javadoc") @@ -95,6 +94,7 @@ publishing { } tasks { + assemble { dependsOn(shadowJar) } @@ -103,6 +103,8 @@ tasks { dependsOn(shadowJar) } + + compileJava { options.encoding = Charsets.UTF_8.name() options.release = 21 diff --git a/core/build.gradle.kts b/core/build.gradle.kts index f92be989..f90c741f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -37,11 +37,17 @@ dependencies { compileOnly("com.github.mqzn:Lotus:1.6.0") compileOnly("org.bstats:bstats-bukkit:3.1.0") compileOnly("me.clip:placeholderapi:2.12.1") - compileOnly("io.papermc.paper:paper-api:1.20.2-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:1.20.6-R0.1-SNAPSHOT") + compileOnly("net.kyori:adventure-api:5.1.1") compileOnly("org.mineskin:java-client:3.2.5") compileOnly("org.mineskin:java-client-jsoup:3.2.5") - compileOnly("dev.velix:imperat-bukkit:1.9.7") - compileOnly("dev.velix:imperat-core:1.9.7") + + testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +tasks.test { + useJUnitPlatform() } val generateClassloader = tasks.register("generateClassloader") { @@ -122,7 +128,7 @@ tasks { } compileJava { - options.release = 17 + options.release = 21 } jar { diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/Action.java b/core/src/main/java/dev/foxikle/customnpcs/actions/Action.java index 986b200e..9e8c968c 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/Action.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/Action.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/ActionType.java b/core/src/main/java/dev/foxikle/customnpcs/actions/ActionType.java index 293f350d..749547b2 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/ActionType.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/ActionType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/LegacyAction.java b/core/src/main/java/dev/foxikle/customnpcs/actions/LegacyAction.java index cd739d7d..6ba6c9de 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/LegacyAction.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/LegacyAction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/ActionAdapter.java b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/ActionAdapter.java index d7cdcaa6..296c0d20 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/ActionAdapter.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/ActionAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/Condition.java b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/Condition.java index 11d83af1..0e7b399e 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/Condition.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/Condition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/Conditional.java b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/Conditional.java index 461d3d00..478bcede 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/Conditional.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/Conditional.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/ConditionalTypeAdapter.java b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/ConditionalTypeAdapter.java index 89f3d647..18d61d2b 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/ConditionalTypeAdapter.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/ConditionalTypeAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/LogicalCondition.java b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/LogicalCondition.java index c5dd5575..f5f34520 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/LogicalCondition.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/LogicalCondition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/LogicalConditional.java b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/LogicalConditional.java index 69fb58c4..57e906a2 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/LogicalConditional.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/LogicalConditional.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/NumericCondition.java b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/NumericCondition.java index d982a227..30e3b6b2 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/NumericCondition.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/NumericCondition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/NumericConditional.java b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/NumericConditional.java index 2cda6ceb..91453f40 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/NumericConditional.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/conditions/NumericConditional.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/ActionBar.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/ActionBar.java index 6ef3511b..79e02242 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/ActionBar.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/ActionBar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/DisplayTitle.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/DisplayTitle.java index 324c088d..71cd8ea3 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/DisplayTitle.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/DisplayTitle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/FollowPresetPathAction.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/FollowPresetPathAction.java new file mode 100644 index 00000000..797e7129 --- /dev/null +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/FollowPresetPathAction.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2026. Foxikle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.foxikle.customnpcs.actions.defaultImpl; + +import com.google.gson.reflect.TypeToken; +import dev.foxikle.customnpcs.actions.Action; +import dev.foxikle.customnpcs.actions.conditions.Condition; +import dev.foxikle.customnpcs.internal.CustomNPCs; +import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; +import dev.foxikle.customnpcs.internal.menu.MenuUtils; +import dev.foxikle.customnpcs.internal.runnables.RecordingRunnable; +import dev.foxikle.customnpcs.internal.utils.Msg; +import dev.foxikle.customnpcs.internal.utils.WaitingType; +import io.github.mqzen.menus.base.Content; +import io.github.mqzen.menus.base.Menu; +import io.github.mqzen.menus.misc.Capacity; +import io.github.mqzen.menus.misc.DataRegistry; +import io.github.mqzen.menus.misc.button.Button; +import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; +import io.github.mqzen.menus.misc.itembuilder.ItemBuilder; +import io.github.mqzen.menus.titles.MenuTitle; +import io.github.mqzen.menus.titles.MenuTitles; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class FollowPresetPathAction extends Action { + + private static final Map> recordingPaths = new ConcurrentHashMap<>(); + private static final Map viewPaths = new ConcurrentHashMap<>(); + private static final Map lastRecordTime = new ConcurrentHashMap<>(); + private static final Map activePlaybacks = new ConcurrentHashMap<>(); + @Getter + @Setter + private List path; + + @Getter + @Setter + private boolean loop; + + public FollowPresetPathAction(List path, boolean loop, int delay, Condition.SelectionMode mode, + List conditions, int cooldown) { + super(delay, mode, conditions, cooldown); + this.path = path; + this.loop = loop; + } + + public static Button creationButton(Player player) { + return Button.clickable(ItemBuilder.modern(Material.RAIL) + .setDisplay(Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.favicon")) + .setLore(Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.description")) + .build(), + ButtonClickAction.plain((menuView, event) -> { + Player p = (Player) event.getWhoClicked(); + event.setCancelled(true); + p.playSound(event.getWhoClicked(), Sound.UI_BUTTON_CLICK, 1, 1); + FollowPresetPathAction action = new FollowPresetPathAction(new ArrayList<>(), false, 0, + Condition.SelectionMode.ONE, new ArrayList<>(), 0); + CustomNPCs.getInstance().editingActions.put(p.getUniqueId(), action); + menuView.getAPI().openMenu(p, action.getMenu()); + })); + } + + public static FollowPresetPathAction deserialize(String serialized, Class clazz) { + if (clazz != FollowPresetPathAction.class) + throw new IllegalArgumentException("This deserialize method only supports the FollowPresetPathAction"); + ParseResult result = Action.parseBase(serialized); + String pathData = Action.parseString(serialized, "path"); + List path = CustomNPCs.getGson().fromJson(pathData, new TypeToken>() { + }.getType()); + boolean loop = Action.parseBoolean(pathData, "loop"); + return new FollowPresetPathAction(path, loop, result.delay(), result.mode(), result.conditions(), + result.cooldown()); + } + + public static void startRecording(Player player) { + UUID uuid = player.getUniqueId(); + recordingPaths.put(uuid, new ArrayList<>()); + lastRecordTime.put(uuid, 0L); + viewPaths.put(uuid, Bukkit.getScheduler().runTaskTimer(CustomNPCs.getInstance(), () -> { + for (RecordedPathNode n : recordingPaths.get(uuid)) { + player.spawnParticle(Particle.END_ROD, n.toLocation(player.getWorld()), 1, 0, 0, 0, 0); + } + }, 5, 5)); + CustomNPCs.getInstance().wait(player, WaitingType.RECORDING); + new RecordingRunnable(player, CustomNPCs.getInstance()).runTaskTimer(CustomNPCs.getInstance(), 0, 10); + } + + public static List stopRecording(Player player) { + UUID uuid = player.getUniqueId(); + lastRecordTime.remove(uuid); + viewPaths.remove(uuid).cancel(); + return recordingPaths.remove(uuid); + } + + // Better recordMovement + public static void recordMovement(Player player, Location loc) { + UUID uuid = player.getUniqueId(); + List currentPath = recordingPaths.get(uuid); + if (currentPath == null) return; + + long now = System.currentTimeMillis(); + if (currentPath.isEmpty()) { + lastRecordTime.put(uuid, now); + currentPath.add(new RecordedPathNode(0, loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch())); + return; + } + + long startTime = lastRecordTime.get(uuid); + long elapsed = now - startTime; + + // Sample every 50ms + RecordedPathNode lastNode = currentPath.get(currentPath.size() - 1); + if (elapsed - lastNode.timestamp() < 50) return; + + // Don't record duplicates + if (lastNode.x() == loc.getX() && lastNode.y() == loc.getY() && lastNode.z() == loc.getZ() && + lastNode.yaw() == loc.getYaw() && lastNode.pitch() == loc.getPitch()) { + return; + } + + currentPath.add(new RecordedPathNode(elapsed, loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), + loc.getPitch())); + } + + @Override + public void perform(InternalNpc npc, Menu menu, Player player) { + if (path == null || path.isEmpty()) return; + if (activePlaybacks.containsKey(npc)) { + if (loop) return; + activePlaybacks.get(npc).cancel(); + } + final Location returnTo = npc.getCurrentLocation(); + + npc.teleport(path.getFirst().toLocation(npc.getWorld())); + + final int[] currentIndex = {0}; + BukkitTask task = Bukkit.getScheduler().runTaskTimer(CustomNPCs.getInstance(), () -> { + if (currentIndex[0] >= path.size()) { + if (loop) { + currentIndex[0] = 0; + npc.teleport(path.getFirst().toLocation(npc.getWorld())); + } else { + BukkitTask t = activePlaybacks.remove(npc); + if (t != null) t.cancel(); + Bukkit.getScheduler().runTaskLater(CustomNPCs.getInstance(), () -> npc.teleport(returnTo), 20L); + } + return; + } + Location prev; + if (currentIndex[0] > 0) { + prev = path.get(currentIndex[0] - 1).toLocation(npc.getWorld()); + } else prev = npc.getCurrentLocation(); + RecordedPathNode node = path.get(currentIndex[0]); + Location loc = node.toLocation(npc.getWorld()); + npc.setYRotation(loc.getYaw()); + npc.setXRotation(loc.getPitch()); + Vector vec = loc.subtract(prev).toVector(); + npc.moveTo(vec); + currentIndex[0]++; + }, 0, 1); + + + activePlaybacks.put(npc, task); + } + + @Override + public String serialize() { + Map params = new HashMap<>(); + params.put("path", CustomNPCs.getGson().toJson(path)); + params.put("loop", loop); + return generateSerializedString("FollowPresetPath", params).replace("},]", "}]"); + } + + @Override + public ItemStack getFavicon(Player player) { + return ItemBuilder.modern(Material.RAIL) + .setDisplay(Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.favicon")) + .setLore( + Msg.translate(player.locale(), "customnpcs.favicons.delay", getDelay()), + Msg.format(""), + Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.nodes", path.size()), + Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.looped", loop), + Msg.format(""), + Msg.translate(player.locale(), "customnpcs.favicons.edit"), + Msg.translate(player.locale(), "customnpcs.favicons.remove") + ) + .build(); + } + + @Override + public Menu getMenu() { + return new FollowPathCustomizer(this); + } + + @Override + public Action clone() { + return new FollowPresetPathAction(new ArrayList<>(path), loop, getDelay(), getMode(), + new ArrayList<>(getConditions()), getCooldown()); + } + + private class FollowPathCustomizer implements Menu { + private final FollowPresetPathAction action; + + public FollowPathCustomizer(FollowPresetPathAction action) { + this.action = action; + } + + @Override + public String getName() { + return "follow_path_customizer"; + } + + @Override + public @NotNull MenuTitle getTitle(DataRegistry dataRegistry, Player player) { + return MenuTitles.createModern(Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.title")); + } + + @Override + public @NotNull Capacity getCapacity(DataRegistry dataRegistry, Player player) { + return Capacity.ofRows(5); + } + + @Override + public @NotNull Content getContent(DataRegistry dataRegistry, Player player, Capacity capacity) { + return MenuUtils.actionBase(action, player) + .setButton(20, button(player)) + .setButton(24, candle(player)) + .build(); + } + + private Button candle(Player player) { + return Button.clickable(ItemBuilder.modern(action.loop ? Material.GREEN_CANDLE : Material.RED_CANDLE) + .setDisplay(Msg.translate(player.locale(), action.loop ? "customnpcs.menus.action.follow_path" + + ".loop.true" : "customnpcs.menus.action" + + ".follow_path.loop.false")) + .setLore(Msg.lore(player.locale(), action.loop ? "customnpcs.menus.action.follow_path.loop.true" + + ".description" : "customnpcs.menus.action" + + ".follow_path.loop.false" + + ".description")) + .build(), ButtonClickAction.plain((m, e) -> { + e.setCancelled(true); + player.playSound(player, Sound.UI_BUTTON_CLICK, 1, 1); + action.loop = !action.loop; + m.updateButton(24, button -> button.setItem(candle(player).getItem())); + //todo: autostart + } + )); + } + + private Button button(Player player) { + if (action.path == null || action.path.isEmpty()) { + return Button.clickable(ItemBuilder.modern(Material.PLAYER_HEAD) + .setDisplay(Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.record")) + .setLore(Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.record.lore")) + .build(), ButtonClickAction.plain((menuView, event) -> { + player.closeInventory(); + startRecording(player); + })); + } + + return Button.clickable(ItemBuilder.modern(Material.PLAYER_HEAD) + .setDisplay(Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.rerecord")) + .setLore(Msg.translate(player.locale(), "customnpcs.menus.action.follow_path.rerecord.lore", + path.size())) + .build(), ButtonClickAction.plain((menuView, event) -> { + player.closeInventory(); + startRecording(player); + })); + } + } +} diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/GiveEffect.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/GiveEffect.java index 20eb3205..ad9bdf7f 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/GiveEffect.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/GiveEffect.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/GiveXP.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/GiveXP.java index db1d8130..d799f541 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/GiveXP.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/GiveXP.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/PlaySound.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/PlaySound.java index 4c3b9dd8..ff947331 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/PlaySound.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/PlaySound.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R2.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RecordedPathNode.java similarity index 64% rename from v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R2.java rename to core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RecordedPathNode.java index 23c991e7..d8af60b0 100644 --- a/v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R2.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RecordedPathNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,27 +20,23 @@ * SOFTWARE. */ -package dev.foxikle.customnpcs.versions; +package dev.foxikle.customnpcs.actions.defaultImpl; -import net.minecraft.network.Connection; -import net.minecraft.network.PacketListener; -import net.minecraft.network.protocol.PacketFlow; +import org.bukkit.Location; +import org.bukkit.World; -/** - * A fake connection for the NPCs - */ -public class FakeConnection_v1_20_R2 extends Connection { - /** - *

Creates a fake Connection for NPC - *

- * @param enumprotocoldirection The protocol direction - */ - public FakeConnection_v1_20_R2(PacketFlow enumprotocoldirection) { - super(enumprotocoldirection); +import java.io.Serializable; + +public record RecordedPathNode(long timestamp, double x, double y, double z, float yaw, + float pitch) implements Serializable { + + public Location toLocation(World world) { + return new Location(world, x, y, z, yaw, pitch); } @Override - public void setListener(PacketListener packetListener) { - + public String toString() { + return String.format("Node{t=%d, x=%.2f, y=%.2f, z=%.2f, yaw=%.1f, pitch=%.1f}", timestamp, x, y, z, yaw, + pitch); } } diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RemoveEffect.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RemoveEffect.java index 295d21a5..16e6ad6e 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RemoveEffect.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RemoveEffect.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RemoveXP.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RemoveXP.java index df4074f9..0a1edd11 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RemoveXP.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RemoveXP.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RunCommand.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RunCommand.java index 05317a49..978039f1 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RunCommand.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/RunCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -62,25 +62,9 @@ @Setter public class RunCommand extends Action { - public static Button creationButton(Player player) { - return Button.clickable(ItemBuilder.modern(ANVIL) - .setDisplay(Msg.translate(player.locale(), "customnpcs.favicons.command")) - .setLore(Msg.lore(player.locale(), "customnpcs.favicons.command.description")) - .build(), - ButtonClickAction.plain((menuView, event) -> { - Player p = (Player) event.getWhoClicked(); - event.setCancelled(true); - p.playSound(event.getWhoClicked(), Sound.UI_BUTTON_CLICK, 1, 1); - RunCommand actionImpl = new RunCommand("say hi", false, 0, Condition.SelectionMode.ONE, new ArrayList<>(), 0); - CustomNPCs.getInstance().editingActions.put(p.getUniqueId(), actionImpl); - menuView.getAPI().openMenu(p, actionImpl.getMenu()); - })); - } - private String command; private boolean asConsole; - /** * Creates a new RunCommand with the specified command * @@ -90,12 +74,14 @@ public static Button creationButton(Player player) { * @param mode The mode * @param conditionals The conditionals */ - public RunCommand(String rawCommand, boolean asConsole, int delay, Condition.SelectionMode mode, List conditionals, int cooldown) { + public RunCommand(String rawCommand, boolean asConsole, int delay, Condition.SelectionMode mode, + List conditionals, int cooldown) { super(delay, mode, conditionals, cooldown); this.command = rawCommand; this.asConsole = asConsole; } + /** * Creates a new RunCommand with the specified command * @@ -108,12 +94,29 @@ public RunCommand(String rawCommand, boolean asConsole, int delay, Condition.Sel */ @Deprecated @ApiStatus.ScheduledForRemoval(inVersion = "1.9") - public RunCommand(String rawCommand, boolean asConsole, int delay, Condition.SelectionMode mode, List conditionals) { + public RunCommand(String rawCommand, boolean asConsole, int delay, Condition.SelectionMode mode, + List conditionals) { super(delay, mode, conditionals, 0); this.command = rawCommand; this.asConsole = asConsole; } + public static Button creationButton(Player player) { + return Button.clickable(ItemBuilder.modern(ANVIL) + .setDisplay(Msg.translate(player.locale(), "customnpcs.favicons.command")) + .setLore(Msg.lore(player.locale(), "customnpcs.favicons.command.description")) + .build(), + ButtonClickAction.plain((menuView, event) -> { + Player p = (Player) event.getWhoClicked(); + event.setCancelled(true); + p.playSound(event.getWhoClicked(), Sound.UI_BUTTON_CLICK, 1, 1); + RunCommand actionImpl = new RunCommand("say hi", false, 0, Condition.SelectionMode.ONE, + new ArrayList<>(), 0); + CustomNPCs.getInstance().editingActions.put(p.getUniqueId(), actionImpl); + menuView.getAPI().openMenu(p, actionImpl.getMenu()); + })); + } + public static T deserialize(String serialized, Class clazz) { if (!clazz.equals(RunCommand.class)) { throw new IllegalArgumentException("Cannot deserialize " + clazz.getName() + " to " + RunCommand.class.getName()); @@ -169,7 +172,8 @@ public String serialize() { @Override public Action clone() { - return new RunCommand(command, asConsole, getDelay(), getMode(), new ArrayList<>(getConditions()), getCooldown()); + return new RunCommand(command, asConsole, getDelay(), getMode(), new ArrayList<>(getConditions()), + getCooldown()); } public class RunCommandCustomizer implements Menu { @@ -198,7 +202,9 @@ public String getName() { @Override public @NotNull Content getContent(DataRegistry dataRegistry, Player player, Capacity capacity) { return MenuUtils.actionBase(action, player) - .setButton(player.hasPermission("customnpcs.run_command.enable_console") ? 21 : 22, setCommand(player)) + .setButton(4, papiTip(player)) + .setButton(player.hasPermission("customnpcs.run_command.enable_console") ? 21 : 22, + setCommand(player)) .setButton(23, toggle(player)) .build(); } @@ -207,12 +213,15 @@ private Button toggle(Player player) { if (!player.hasPermission("customnpcs.run_command.enable_console")) return MenuItems.MENU_GLASS; List lore = new ArrayList<>(); if (isAsConsole()) { - lore.addAll(Utils.list(Msg.lore(player.locale(), "customnpcs.menus.action.command.as_console.warning"))); + lore.addAll(Utils.list(Msg.lore(player.locale(), + "customnpcs.menus.action.command.as_console.warning"))); } lore.add(Msg.translate(player.locale(), "customnpcs.items.click_to_change")); return Button.clickable(ItemBuilder.modern(isAsConsole() ? RED_CANDLE : GREEN_CANDLE) .setLore(lore.toArray(new Component[]{})) - .setDisplay(isAsConsole() ? Msg.translate(player.locale(), "customnpcs.menus.action.command.as_console.true") : Msg.translate(player.locale(), "customnpcs.menus.action.command.as_console.false")) + .setDisplay(isAsConsole() ? Msg.translate(player.locale(), "customnpcs.menus.action" + + ".command.as_console.true") : Msg.translate(player.locale(), "customnpcs.menus" + + ".action.command.as_console.false")) .build(), ButtonClickAction.plain((menuView, event) -> { event.setCancelled(true); @@ -243,5 +252,31 @@ private Button setCommand(Player player) { player.playSound(event.getWhoClicked(), Sound.UI_BUTTON_CLICK, 1, 1); })); } + + private Button papiTip(Player player) { + Component[] lore; + if (!CustomNPCs.getInstance().papi) { + lore = Msg.lore(player.locale(), "customnpcs.menus.action.command.papi_tip.no_papi"); + } else if (!CustomNPCs.getInstance().papiPlayerExpansion) { + lore = Msg.lore(player.locale(), "customnpcs.menus.action.command.papi_tip.no_expansion"); + } else { + lore = Msg.lore(player.locale(), "customnpcs.menus.action.command.papi_tip.all_good"); + } + return Button.clickable(ItemBuilder.modern(REDSTONE_TORCH) + .setDisplay(Msg.translate(player.locale(), "customnpcs.menus.action.command.papi_tip.title")) + .setLore(lore) + .build(), ButtonClickAction.plain((menuView, inventoryClickEvent) -> { + inventoryClickEvent.setCancelled(true); + if (!CustomNPCs.getInstance().papi) { + player.sendMessage(Msg.translate(player.locale(), + "customnpcs.menus.action.command.papi_tip.download.plugin")); + return; + } + if (!CustomNPCs.getInstance().papiPlayerExpansion) { + player.sendMessage(Msg.translate(player.locale(), + "customnpcs.menus.action.command.papi_tip.download.expansion")); + } + })); + } } } diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/SendMessage.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/SendMessage.java index 18bb2d4a..01e084a0 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/SendMessage.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/SendMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/SendServer.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/SendServer.java index 6815c51d..4e9f41a0 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/SendServer.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/SendServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/Teleport.java b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/Teleport.java index 1bc87b0c..a9b4be9c 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/Teleport.java +++ b/core/src/main/java/dev/foxikle/customnpcs/actions/defaultImpl/Teleport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/api/NPC.java b/core/src/main/java/dev/foxikle/customnpcs/api/NPC.java index 470bb5d7..9df894cf 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/api/NPC.java +++ b/core/src/main/java/dev/foxikle/customnpcs/api/NPC.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/api/NPCApi.java b/core/src/main/java/dev/foxikle/customnpcs/api/NPCApi.java index 96b68347..8f104be1 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/api/NPCApi.java +++ b/core/src/main/java/dev/foxikle/customnpcs/api/NPCApi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/api/Pose.java b/core/src/main/java/dev/foxikle/customnpcs/api/Pose.java index 972a0a8f..b0ae283d 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/api/Pose.java +++ b/core/src/main/java/dev/foxikle/customnpcs/api/Pose.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025. Foxikle + * Copyright (c) 2025-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcDeleteEvent.java b/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcDeleteEvent.java index f5e48641..fe370e5b 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcDeleteEvent.java +++ b/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcDeleteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcEvent.java b/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcEvent.java index 76059e3d..2cfdf717 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcEvent.java +++ b/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcInjectEvent.java b/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcInjectEvent.java index 0384a49a..6589a57d 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcInjectEvent.java +++ b/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcInjectEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcInteractEvent.java b/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcInteractEvent.java index a33196db..832e3db3 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcInteractEvent.java +++ b/core/src/main/java/dev/foxikle/customnpcs/api/events/NpcInteractEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/data/Equipment.java b/core/src/main/java/dev/foxikle/customnpcs/data/Equipment.java index 8a2eea30..e43cb6ab 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/data/Equipment.java +++ b/core/src/main/java/dev/foxikle/customnpcs/data/Equipment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/data/Settings.java b/core/src/main/java/dev/foxikle/customnpcs/data/Settings.java index e93e6cc5..bd55ad3c 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/data/Settings.java +++ b/core/src/main/java/dev/foxikle/customnpcs/data/Settings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/CustomNPCs.java b/core/src/main/java/dev/foxikle/customnpcs/internal/CustomNPCs.java index bc39681f..d7a69b50 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/CustomNPCs.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/CustomNPCs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,7 @@ import com.google.common.cache.CacheBuilder; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.mojang.brigadier.CommandDispatcher; import dev.foxikle.customnpcs.actions.Action; import dev.foxikle.customnpcs.actions.LegacyAction; import dev.foxikle.customnpcs.actions.conditions.ActionAdapter; @@ -34,10 +35,7 @@ import dev.foxikle.customnpcs.actions.defaultImpl.*; import dev.foxikle.customnpcs.data.Equipment; import dev.foxikle.customnpcs.data.Settings; -import dev.foxikle.customnpcs.internal.commands.NpcCommand; -import dev.foxikle.customnpcs.internal.commands.suggestion.NpcSuggester; -import dev.foxikle.customnpcs.internal.commands.suggestion.SoundSuggester; -import dev.foxikle.customnpcs.internal.commands.suggestion.WorldSuggester; +import dev.foxikle.customnpcs.internal.commands.NpcCommandRegistrar; import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; import dev.foxikle.customnpcs.internal.listeners.Listeners; import dev.foxikle.customnpcs.internal.menu.*; @@ -45,11 +43,11 @@ import dev.foxikle.customnpcs.internal.utils.ActionRegistry; import dev.foxikle.customnpcs.internal.utils.AutoUpdater; import dev.foxikle.customnpcs.internal.utils.WaitingType; -import dev.velix.imperat.BukkitImperat; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.Imperat; import io.github.mqzen.menus.Lotus; import io.github.mqzen.menus.base.pagination.Pagination; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -63,6 +61,7 @@ import org.bukkit.entity.Player; import org.bukkit.entity.TextDisplay; import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -108,7 +107,7 @@ public final class CustomNPCs extends JavaPlugin implements PluginMessageListene TimeUnit.MINUTES).expireAfterAccess(1, TimeUnit.MINUTES).build(); private final String[] COMPATIBLE_VERSIONS = {"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6", "1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", - "1.21.10", "1.21.11"}; + "1.21.10", "1.21.11", "26.1", "26.1.1", "26.1.2", "26.2"}; private final String NPC_CLASS = "dev.foxikle.customnpcs.versions.NPC_%s"; /** * The map of what the plugin is waiting for the players to enter. @@ -142,6 +141,10 @@ public final class CustomNPCs extends JavaPlugin implements PluginMessageListene * If the plugin should try to format messages with PlaceholderAPI */ public boolean papi = false; + /** + * If this server has the Player expansion installed + */ + public boolean papiPlayerExpansion = false; /** * If there is a new update available */ @@ -165,7 +168,7 @@ public final class CustomNPCs extends JavaPlugin implements PluginMessageListene @Getter private Lotus lotus; @Getter - private Imperat imperat; + private CommandDispatcher commandDispatcher; @Getter @Setter @@ -180,11 +183,24 @@ public final class CustomNPCs extends JavaPlugin implements PluginMessageListene *

*/ @Override + @SuppressWarnings("UnstableApiUsage") public void onEnable() { // paper... why?? - System.setProperty("org.bukkit.plugin.java.LibraryLoader.centralURL", "https://repo1.maven.org/maven2"); instance = this; + if (!Objects.equals(System.getProperty("CustomNPCs-Started"), "true")) { + LifecycleEventManager manager = this.getLifecycleManager(); + manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { + final Commands commands = event.registrar(); + commands.register( + NpcCommandRegistrar.buildNode(), + "The command for anything NPC related.", + List.of() + ); + }); + System.setProperty("CustomNPCs-Started", "true"); + } + if (!checkForValidVersion()) { printInvalidVersion(); Bukkit.getPluginManager().disablePlugin(this); @@ -199,7 +215,7 @@ public void onEnable() { } catch (ClassNotFoundException e) { getLogger().log(Level.SEVERE, "Failed to load NPC class for server version " + s + "!", e); } catch (Exception e) { - getLogger().log(Level.SEVERE, "SERVERE ERROR: ", e); + getLogger().log(Level.SEVERE, "SERVER ERROR: ", e); } INTERPOLATION_DURATION = getConfig().getInt("DefaultInterpolationDuration"); @@ -232,6 +248,8 @@ public void onEnable() { ACTION_REGISTRY.register("SendMessage", SendMessage.class, SendMessage::creationButton); ACTION_REGISTRY.register("SendServer", SendServer.class, SendServer::creationButton, true, false, true); ACTION_REGISTRY.register("Teleport", Teleport.class, Teleport::creationButton); + ACTION_REGISTRY.register("FollowPresetPath", FollowPresetPathAction.class, + FollowPresetPathAction::creationButton); try { this.getLogger().info("Loading NPCs!"); @@ -312,16 +330,6 @@ public void onEnable() { ); })); - // setup papi - if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { - this.getLogger().info("Successfully hooked into PlaceholderAPI."); - papi = true; - } else { - papi = false; - this.getLogger().warning("Could not find PlaceholderAPI! PlaceholderAPI isn't required, but CustomNPCs " + - "does support it."); - } - if (!System.getProperties().containsKey("customnpcs-reload-check")) { getLogger().info("Loading listeners..."); listeners = new Listeners(this); @@ -350,23 +358,6 @@ public void onEnable() { lotus.registerMenu(new HologramMenu()); lotus.registerMenu(new DeleteLineMenu()); lotus.registerMenu(new PoseEditorMenu()); - - // prevent reload goofery - if (!System.getProperties().containsKey("CUSTOMNPCS_LOADED")) { - getLogger().info("Loading commands!"); - - imperat = BukkitImperat.builder(this).applyBrigadier(false)//todo: true - .namedSuggestionResolver("sound", new SoundSuggester()) - .namedSuggestionResolver("current_npc", new NpcSuggester()) - .namedSuggestionResolver("broken_npc", new NpcSuggester()) - .namedSuggestionResolver("worlds", new WorldSuggester()) - .build(); - - // only one command, the rest are sub commands - imperat.registerCommand(new NpcCommand()); - } - - System.setProperty("CUSTOMNPCS_LOADED", "true"); } /** @@ -406,9 +397,9 @@ public void onDisable() { // don't unregister these on reload if (!reloading) { - if (imperat != null) { - imperat.shutdownPlatform(); - imperat.unregisterAllCommands(); + if (commandDispatcher != null) { + // Clear the command dispatcher by getting a new instance + commandDispatcher = null; } } } @@ -503,9 +494,6 @@ public InternalNpc createNPC(World world, Location location, Equipment equipment public String translateVersion() { return switch (serverVersion) { - case "1.20", "1.20.1" -> "v1_20_R1"; - case "1.20.2" -> "v1_20_R2"; - case "1.20.3", "1.20.4" -> "v1_20_R3"; case "1.20.5", "1.20.6" -> "v1_20_R4"; case "1.21", "1.21.1" -> "v1_21_R0"; case "1.21.2", "1.21.3" -> "v1_21_R1"; @@ -514,6 +502,8 @@ public String translateVersion() { case "1.21.6", "1.21.7", "1.21.8" -> "v1_21_R4"; case "1.21.9", "1.21.10" -> "v1_21_R5"; case "1.21.11" -> "v1_21_R6"; + case "26.1", "26.1.1", "26.1.2" -> "v26_1_R1"; + case "26.2" -> "v26_2_R1"; default -> ""; }; } @@ -535,7 +525,7 @@ private void printInvalidVersion() { logger.severe("+------------------------------------------------------------------------------+"); logger.severe("| INVALID SERVER VERSION DETECTED |"); logger.severe("| ** PLEASE USE ONE OF THE FOLLOWING SERVER VERSIONS ** |"); - logger.severe("| [1.20.x, 1.21.x] |"); + logger.severe("| [1.20.x, 1.21.x, 26.x] |"); logger.severe("| DETECTED: '" + serverVersion + "' " + "|"); logger.severe("| Please contact @foxikle on Discord for more information. |"); @@ -556,4 +546,17 @@ public Pagination getSkinCatalog(Player player) { return getMenuUtils().getSkinCatalogue(player.locale()); } + public static class BukkitImperat { + public static void handleCommandException(com.mojang.brigadier.exceptions.CommandSyntaxException e, + org.bukkit.command.CommandSender sender) { + if (e.getCursor() >= 0) { + String input = e.getInput(); + String pointer = " ".repeat(Math.max(0, e.getCursor())) + "^"; + sender.sendMessage(input); + sender.sendMessage(pointer); + } + sender.sendMessage(e.getMessage()); + } + } + } diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/FileManager.java b/core/src/main/java/dev/foxikle/customnpcs/internal/FileManager.java index f6e3d46a..47b6d930 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/FileManager.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/FileManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/InjectionManager.java b/core/src/main/java/dev/foxikle/customnpcs/internal/InjectionManager.java index 118a7b17..cd6a46ca 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/InjectionManager.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/InjectionManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/LookAtAnchor.java b/core/src/main/java/dev/foxikle/customnpcs/internal/LookAtAnchor.java index 612f2526..34e6a4d6 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/LookAtAnchor.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/LookAtAnchor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CloneCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CloneCommand.java deleted file mode 100644 index 3d3e93cc..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CloneCommand.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.*; -import dev.velix.imperat.command.AttachmentMode; -import org.bukkit.entity.Player; - -import java.util.UUID; - -@SubCommand(value = "clone", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.commands.clone") -@Description("Clones the the npcs.") -public class CloneCommand { - @Usage - public void usage( - BukkitSource source, - @Named("npc") @SuggestionProvider("current_npc") @Greedy String npc - ) { - if (source.isConsole()) { - source.reply(Msg.format("You can't do this :P")); - return; - } - - UUID uuid = CommandUtils.parseNpc(source, npc); - if (uuid == null) { - return; - } - if (!CommandUtils.checkNpc(source, uuid)) return; - - final Player p = source.asPlayer(); - final CustomNPCs plugin = CustomNPCs.getInstance(); - final InternalNpc finalNpc = plugin.getNPCByID(uuid); - - assert finalNpc != null; - InternalNpc newNpc = finalNpc.clone(); - newNpc.setSpawnLoc(p.getLocation()); - newNpc.getSettings().setDirection(p.getLocation().getYaw()); - newNpc.createNPC(); - p.sendMessage(Msg.translate(p.locale(), "customnpcs.commands.clone.success")); - } -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CommandUtils.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CommandUtils.java index 8a69ad45..a52239f0 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CommandUtils.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CommandUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,13 +26,13 @@ import dev.foxikle.customnpcs.internal.CustomNPCs; import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; import lombok.experimental.UtilityClass; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -42,9 +42,6 @@ import java.util.UUID; import java.util.stream.Collectors; -/** - * The class to handle the core command - */ @UtilityClass public class CommandUtils { @@ -96,14 +93,13 @@ public static Component getListComponent(Locale p) { return Msg.translate(p, "customnpcs.commands.manage.no_npcs"); } - Component message = Msg.translate(p, "customnpcs.commands.manage.header").appendNewline(); for (InternalNpc npc : plugin.getNPCs()) { if (npc.getSettings().isResilient()) { Component name = Msg.format(" ") - .append(plugin.getMiniMessage().deserialize(npc.getSettings().getName()).appendSpace().hoverEvent(HoverEvent.showText(Msg.translate(p, "customnpcs.commands.manage.copy_uuid")))).clickEvent(net.kyori.adventure.text.event.ClickEvent.clickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, npc.getUniqueID().toString())) - .append(Msg.translate(p, "customnpcs.commands.manage.button.edit").appendSpace().hoverEvent(HoverEvent.showText(Msg.translate(p, "customnpcs.commands.manage.button.edit.hover"))).clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/npc edit " + npc.getUniqueID()))) - .append(Msg.translate(p, "customnpcs.commands.manage.button.delete").appendSpace().hoverEvent(HoverEvent.showText(Msg.translate(p, "customnpcs.commands.manage.button.delete.hover"))).clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/npc delete " + npc.getUniqueID()))) + .append(plugin.getMiniMessage().deserialize(npc.getSettings().getName()).appendSpace().hoverEvent(HoverEvent.showText(Msg.translate(p, "customnpcs.commands.manage.copy_uuid")))).clickEvent(ClickEvent.copyToClipboard(npc.getUniqueID().toString())) + .append(Msg.translate(p, "customnpcs.commands.manage.button.edit").appendSpace().hoverEvent(HoverEvent.showText(Msg.translate(p, "customnpcs.commands.manage.button.edit.hover"))).clickEvent(ClickEvent.runCommand("/npc edit " + npc.getUniqueID()))) + .append(Msg.translate(p, "customnpcs.commands.manage.button.delete").appendSpace().hoverEvent(HoverEvent.showText(Msg.translate(p, "customnpcs.commands.manage.button.delete.hover"))).clickEvent(ClickEvent.suggestCommand("/npc delete " + npc.getUniqueID()))) .appendNewline(); message = message.append(name); } @@ -112,44 +108,38 @@ public static Component getListComponent(Locale p) { return message; } - public static boolean checkNpc(BukkitSource source, UUID npc) { + public static boolean checkNpc(CommandSender source, UUID npc) { Locale locale = Locale.getDefault(); - if (!source.isConsole()) locale = source.asPlayer().locale(); + if (source instanceof Player player) locale = player.locale(); if (npc == null) { - source.reply(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); + source.sendMessage(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); return false; } boolean valid = CustomNPCs.getInstance().npcs.containsKey(npc); - if (!valid) source.reply(Msg.translate(locale, "customnpcs.commands.invalid_uuid")); + if (!valid) source.sendMessage(Msg.translate(locale, "customnpcs.commands.invalid_uuid")); return valid; } - public static UUID parseNpc(BukkitSource source, String data) { - + public static UUID parseNpc(CommandSender source, String data) { Locale locale = Locale.getDefault(); - if (!source.isConsole()) locale = source.asPlayer().locale(); + if (source instanceof Player player) locale = player.locale(); final CustomNPCs plugin = CustomNPCs.getInstance(); UUID uuid; try { uuid = UUID.fromString(data); if (plugin.getNPCByID(uuid) == null) { - source.reply(Msg.translate(locale, "customnpcs.commands.invalid_uuid")); + source.sendMessage(Msg.translate(locale, "customnpcs.commands.invalid_uuid")); return null; } -// npc = plugin.getNPCByID(uuid); } catch (IllegalArgumentException ignored) { - - if (source.isConsole()) { + if (!(source instanceof Player p)) { return null; } - Player p = source.asPlayer(); - - // check for names instead Locale finalLocale = locale; Set uuids = plugin.npcs.values().stream().map(npc -> { if (plugin.getMiniMessage().stripTags(npc.getSettings().getName()).equalsIgnoreCase(data)) { @@ -160,17 +150,16 @@ public static UUID parseNpc(BukkitSource source, String data) { uuids.removeIf(Objects::isNull); if (uuids.isEmpty()) { - source.reply(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); + source.sendMessage(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); return null; } else if (uuids.size() > 1) { double value = Double.MAX_VALUE; uuid = null; for (UUID id : uuids) { - InternalNpc npc = plugin.getNPCByID(id); assert npc != null : "Npc is null when parsing the closest NPC"; - if (p.getWorld() != npc.getWorld()) continue; // fix error tracing distanced across worlds + if (p.getWorld() != npc.getWorld()) continue; double ds = npc.getCurrentLocation().distanceSquared(p.getLocation()); if (ds < value) { @@ -180,21 +169,18 @@ public static UUID parseNpc(BukkitSource source, String data) { } if (uuid == null) { - // meaning the player is in a different world than the npcs with the same name uuid = Iterables.getFirst(uuids, null); - // get the first one } - } else { uuid = Iterables.getFirst(uuids, null); } if (uuid == null) { - source.reply(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); + source.sendMessage(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); return null; } if (plugin.getNPCByID(uuid) == null) { - p.sendMessage(Msg.translate(p.locale(), "customnpcs.commands.invalid_uuid")); + source.sendMessage(Msg.translate(p.locale(), "customnpcs.commands.invalid_uuid")); return null; } return uuid; @@ -202,25 +188,23 @@ public static UUID parseNpc(BukkitSource source, String data) { InternalNpc npc = plugin.getNPCByID(uuid); - if (npc == null) { - source.reply(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); + source.sendMessage(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); return null; } boolean valid = CustomNPCs.getInstance().npcs.containsKey(uuid); if (!valid) { - source.reply(Msg.translate(locale, "customnpcs.commands.invalid_uuid")); + source.sendMessage(Msg.translate(locale, "customnpcs.commands.invalid_uuid")); return null; } else { return uuid; } } - public static Locale getLocale(BukkitSource source) { - if (source.isConsole()) return Locale.getDefault(); - return source.asPlayer().locale(); + public static Locale getLocale(CommandSender source) { + if (source instanceof Player player) return player.locale(); + return Locale.getDefault(); } - -} +} \ No newline at end of file diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CreateCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CreateCommand.java deleted file mode 100644 index c2a4b515..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/CreateCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.data.Equipment; -import dev.foxikle.customnpcs.data.Settings; -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; -import dev.foxikle.customnpcs.internal.menu.MenuUtils; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.SubCommand; -import dev.velix.imperat.annotations.Usage; -import dev.velix.imperat.command.AttachmentMode; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.UUID; - -@SubCommand(value = {"create"}, attachment = AttachmentMode.MAIN) -public class CreateCommand { - - @Usage - public void usage(BukkitSource source) { - if (source.isConsole()) { - source.reply(Msg.format("You can't do this :P")); - return; - } - final Player p = source.asPlayer(); - final UUID uuid = UUID.randomUUID(); - final CustomNPCs plugin = CustomNPCs.getInstance(); - InternalNpc npc = plugin.createNPC(p.getWorld(), p.getLocation(), new Equipment(), new Settings(), uuid, null, new ArrayList<>()); - plugin.getEditingNPCs().put(p.getUniqueId(), npc); - plugin.getLotus().openMenu(p, MenuUtils.NPC_MAIN); - } - -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/DeleteCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/DeleteCommand.java deleted file mode 100644 index 1c3995ea..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/DeleteCommand.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; -import dev.foxikle.customnpcs.internal.menu.MenuUtils; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.*; -import dev.velix.imperat.command.AttachmentMode; -import org.bukkit.entity.Player; - -import java.util.UUID; - -@SubCommand(value = "delete", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.delete") -@Description("Deletes the specified NPC") -public class DeleteCommand { - @Usage - public void usage( - BukkitSource source, - @Named("npc") @SuggestionProvider("current_npc") @Greedy String npc - ) { - if (source.isConsole()) { - source.reply(Msg.format("You can't do this :P")); - return; - } - - UUID uuid = CommandUtils.parseNpc(source, npc); - if (uuid == null) { - return; - } - if (!CommandUtils.checkNpc(source, uuid)) return; - - final Player p = source.asPlayer(); - final CustomNPCs plugin = CustomNPCs.getInstance(); - final InternalNpc finalNpc = plugin.getNPCByID(uuid); - - assert finalNpc != null; - plugin.getEditingNPCs().put(p.getUniqueId(), finalNpc); - plugin.getDeletionReason().put(p.getUniqueId(), false); - plugin.getLotus().openMenu(p, MenuUtils.NPC_DELETE); - } -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/EditCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/EditCommand.java deleted file mode 100644 index 86626360..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/EditCommand.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; -import dev.foxikle.customnpcs.internal.menu.MenuUtils; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.*; -import dev.velix.imperat.command.AttachmentMode; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.UUID; - -@Permission("customnpcs.edit") -@SubCommand(value = "edit", attachment = AttachmentMode.MAIN) -@Description("Edits an NPC") -public class EditCommand { - - @Usage - public void editNpc( - BukkitSource source, - @Named("npc") @SuggestionProvider("current_npc") @Greedy String npc - ) { - if (source.isConsole()) { - source.reply(Msg.format("You can't do this :P")); - return; - } - - UUID uuid = CommandUtils.parseNpc(source, npc); - if (uuid == null) { - return; - } -// if (!CommandUtils.checkNpc(source, uuid)) return; - - final Player p = source.asPlayer(); - final CustomNPCs plugin = CustomNPCs.getInstance(); - final InternalNpc finalNpc = plugin.getNPCByID(uuid); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - InternalNpc newNpc = plugin.createNPC(p.getWorld(), finalNpc.getSpawnLoc(), finalNpc.getEquipment(), finalNpc.getSettings(), finalNpc.getUniqueID(), null, finalNpc.getActions()); - plugin.getEditingNPCs().put(p.getUniqueId(), newNpc); - plugin.getLotus().openMenu(p, MenuUtils.NPC_MAIN); - }, 1); - - } -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/FixConfigCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/FixConfigCommand.java deleted file mode 100644 index aa043227..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/FixConfigCommand.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.FileManager; -import dev.foxikle.customnpcs.internal.commands.enums.FixConfigWorldStrategy; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.*; -import dev.velix.imperat.command.AttachmentMode; -import org.bukkit.Bukkit; -import org.bukkit.FluidCollisionMode; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.util.RayTraceResult; -import org.bukkit.util.Vector; - -import java.util.Locale; -import java.util.Map; -import java.util.UUID; - -@SubCommand(value = "fixconfig", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.commands.fix_config") -public class FixConfigCommand { - - @Usage - public void execute(BukkitSource source) { - Locale locale = CommandUtils.getLocale(source); - source.reply(Msg.translate(locale, "customnpcs.commands.fix_config.usage")); - } - - @Usage - public void execute(BukkitSource source, - @Named("mode") @Suggest({"world"}) String mode, - @SuggestionProvider("worlds") @Default("world") @Named("world") String world, - @Suggest({"NONE", "SAFE_LOCATION"}) @Default("NONE") @Named("strategy") String strategy, - @Suggest({"all"}) @SuggestionProvider("broken_npc") @Default("all") @Named("target") String target - ) { - Locale locale = CommandUtils.getLocale(source); - int totalFixed = 0; - int movedbyStrategy = 0; - int failedToFix = 0; - int nonExistentNpcs = 0; - - if (!mode.equalsIgnoreCase("world")) { - execute(source); - return; - } - - if (Bukkit.getWorld(world) == null) { - // invalid world - source.reply("INVALID_WORLD"); - return; - } - - World w = Bukkit.getWorld(world); - assert w != null; - - FixConfigWorldStrategy strat = FixConfigWorldStrategy.parse(strategy); - if (strat == null) { - //invalid strategy - source.reply("INVALID_STRATEGY"); - return; - } - - CustomNPCs plugin = CustomNPCs.getInstance(); - FileManager fileManager = plugin.getFileManager(); - - - if (target.equalsIgnoreCase("all")) { - // apply it to all NPCs - - for (UUID uuid : fileManager.getBrokenNPCs().keySet()) { - YamlConfiguration yml = fileManager.getNpcYaml(); - ConfigurationSection parent = yml.getConfigurationSection(uuid.toString()); - if (parent == null) { - nonExistentNpcs++; - continue; - } - - ConfigurationSection location = parent.getConfigurationSection("location"); - - Location loc; - String locString; - - //Bukkit's terrible config api didn't wipe the section - if (location != null) { - assert location != null : "Location is null"; - - double x = location.getDouble("x"); - double y = location.getDouble("y"); - double z = location.getDouble("z"); - float pitch = (float) location.getDouble("pitch"); - float yaw = (float) location.getDouble("yaw"); - - loc = new Location(w, x, y, z, pitch, yaw); - locString = "(" + x + "," + y + "," + z + ")"; - } else { - loc = new Location(w, 0, 0, 0, 0, 0); - locString = "(0, 0, 0)"; - plugin.getLogger().warning("Fixed an NPC whose location data was wiped by Bukkit's configuration API. Its location was set to (0,0,0)"); - source.reply(Msg.translate(locale, "customnpcs.commands.fix_config.bukkit_wiped_data")); - } - - - if (strat == FixConfigWorldStrategy.SAFE_LOCATION) { - - // in a wall - if (w.getBlockAt(loc).isSolid() || w.getBlockAt(loc.add(0, 1, 0)).isSolid()) { - RayTraceResult traceResult = w.rayTraceBlocks(loc.add(0, 329 - loc.y(), 0), - new Vector(0, -1, 0), 320D, FluidCollisionMode.NEVER); - - if (traceResult == null) { - // The location cannot be safe - - plugin.getLogger().warning("Failed to fix npc " + uuid + " at " + locString + " -- Location cannot be made safe."); - failedToFix++; - continue; - } - loc.setY(traceResult.getHitBlock().getY() + 1); - } - movedbyStrategy++; - - } - - parent.set("location", loc); - totalFixed++; - fileManager.saveNpcFile(yml); - } - } else { - // find npc by flag - try { - - - if (!fileManager.getBrokenNPCs().containsValue(target)) { - // it dont exist - source.reply(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid ")); - return; - } - UUID uuid = null; - for (Map.Entry entry : fileManager.getBrokenNPCs().entrySet()) { - if (entry.getValue().equals(target)) { - uuid = entry.getKey(); - break; - } - } - - if (uuid == null) { - source.reply(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); - return; - } - - YamlConfiguration yml = fileManager.getNpcYaml(); - ConfigurationSection parent = yml.getConfigurationSection(uuid.toString()); - if (parent == null) { - nonExistentNpcs++; - // hacky hacky way to do this - throw new RuntimeException("Catch me!"); - } - ConfigurationSection location = parent.getConfigurationSection("location"); - - - Location loc; - String locString; - - //Bukkit's terrible config api didn't wipe the section - if (location != null) { - assert location != null : "Location is null"; - - double x = location.getDouble("x"); - double y = location.getDouble("y"); - double z = location.getDouble("z"); - float pitch = (float) location.getDouble("pitch"); - float yaw = (float) location.getDouble("yaw"); - - loc = new Location(w, x, y, z, pitch, yaw); - locString = "(" + x + "," + y + "," + z + ")"; - } else { - loc = new Location(w, 0, 0, 0, 0, 0); - locString = "(0, 0, 0)"; - plugin.getLogger().warning("Fixed an NPC whose location data was wiped by Bukkit's configuration API. Its location was set to (0,0,0)"); - source.reply(Msg.translate(locale, "customnpcs.commands.fix_config.bukkit_wiped_data")); - } - - - if (strat == FixConfigWorldStrategy.SAFE_LOCATION) { - - // in a wall - if (w.getBlockAt(loc).isSolid() || w.getBlockAt(loc.add(0, 1, 0)).isSolid()) { - RayTraceResult traceResult = w.rayTraceBlocks(loc.add(0, 329 - loc.y(), 0), - new Vector(0, -1, 0), 320D, FluidCollisionMode.NEVER); - - if (traceResult == null) { - // The location cannot be safe - plugin.getLogger().warning("Failed to fix npc " + uuid + " at " + locString + " -- Location cannot be made safe."); - failedToFix++; - throw new RuntimeException("Catch me!"); - } - loc.setY(traceResult.getHitBlock().getY() + 1); - } - movedbyStrategy++; - - } - - parent.set("location", loc); - totalFixed++; - - fileManager.saveNpcFile(yml); - } catch (Exception ignored) { - } - - } - source.reply(Msg.translate(locale, "customnpcs.commands.fix_config.report", totalFixed, movedbyStrategy, failedToFix, nonExistentNpcs)); - } -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/HelpCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/HelpCommand.java deleted file mode 100644 index 40fb94ce..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/HelpCommand.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.Description; -import dev.velix.imperat.annotations.Permission; -import dev.velix.imperat.annotations.SubCommand; -import dev.velix.imperat.annotations.Usage; -import dev.velix.imperat.command.AttachmentMode; - -@SubCommand(value = "help", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.commands.help") -@Description("Displays a help message") -public class HelpCommand { - @Usage - public void usage(BukkitSource source) { - source.reply(CommandUtils.getHelpComponent(CommandUtils.getLocale(source))); - } -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ListCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ListCommand.java deleted file mode 100644 index b5dd3e4c..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ListCommand.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.Permission; -import dev.velix.imperat.annotations.SubCommand; -import dev.velix.imperat.annotations.Usage; -import dev.velix.imperat.command.AttachmentMode; - -import java.util.Locale; - -@SubCommand(value = "list", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.commands.manage") -public class ListCommand { - - @Usage - public void usage(BukkitSource sender) { - Locale locale = Locale.getDefault(); - if (!sender.isConsole()) locale = sender.asPlayer().locale(); - sender.reply(CommandUtils.getListComponent(locale)); - } - -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ManageCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ManageCommand.java deleted file mode 100644 index f2212303..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ManageCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.Permission; -import dev.velix.imperat.annotations.SubCommand; -import dev.velix.imperat.annotations.Usage; -import dev.velix.imperat.command.AttachmentMode; - -import java.util.Locale; - -@SubCommand(value = {"manage"}, attachment = AttachmentMode.MAIN) -@Permission("customnpcs.commands.manage") -public class ManageCommand { - - @Usage - public void usage(BukkitSource sender) { - Locale locale = Locale.getDefault(); - if (!sender.isConsole()) locale = sender.asPlayer().locale(); - sender.reply(CommandUtils.getListComponent(locale)); - } -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/MoveCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/MoveCommand.java deleted file mode 100644 index 376d829a..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/MoveCommand.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.*; -import dev.velix.imperat.command.AttachmentMode; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.UUID; - -@SubCommand(value = "movehere", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.commands.movehere") -public class MoveCommand { - - @Usage - public void usage( - BukkitSource source, - @Named("npc") @SuggestionProvider("current_npc") @Greedy String npc - ) { - if (source.isConsole()) { - source.reply(Msg.format("You can't do this :P")); - return; - } - - UUID uuid = CommandUtils.parseNpc(source, npc); - if (uuid == null) { - return; - } - if (!CommandUtils.checkNpc(source, uuid)) return; - - final Player p = source.asPlayer(); - final CustomNPCs plugin = CustomNPCs.getInstance(); - final InternalNpc finalNpc = plugin.getNPCByID(uuid); - - p.sendMessage(Msg.translate(p.locale(), "customnpcs.commands.move.nudge")); - assert finalNpc != null; - finalNpc.teleport(p.getLocation()); - finalNpc.remove(); - finalNpc.createNPC(); - Bukkit.getOnlinePlayers().forEach(finalNpc::injectPlayer); - } - -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/NpcCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/NpcCommand.java deleted file mode 100644 index e74a0b69..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/NpcCommand.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.*; - -import java.util.Locale; - -@Command("npc") -@Description("The main CustomNPCs command") -@Permission("customnpcs.commands.help") -@Inherit( - { - CloneCommand.class, CreateCommand.class, DeleteCommand.class, - EditCommand.class, FixConfigCommand.class, ListCommand.class, MoveCommand.class, - ReloadCommand.class, SetsoundCommand.class, TeleportCommand.class, - WikiCommand.class, HelpCommand.class, ManageCommand.class, DebugCommand.class - } -) -public class NpcCommand { - - @Usage - public void showHelp(BukkitSource sender) { - Locale locale = Locale.getDefault(); - if (!sender.isConsole()) locale = sender.asPlayer().locale(); - sender.reply(CommandUtils.getHelpComponent(locale)); - } -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/NpcCommandRegistrar.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/NpcCommandRegistrar.java new file mode 100644 index 00000000..a59e468d --- /dev/null +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/NpcCommandRegistrar.java @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2024-2026. Foxikle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.foxikle.customnpcs.internal.commands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.tree.LiteralCommandNode; +import dev.foxikle.customnpcs.internal.CustomNPCs; +import dev.foxikle.customnpcs.internal.FileManager; +import dev.foxikle.customnpcs.internal.commands.suggestion.NpcBrokenSuggester; +import dev.foxikle.customnpcs.internal.commands.suggestion.NpcSuggester; +import dev.foxikle.customnpcs.internal.commands.suggestion.SoundSuggester; +import dev.foxikle.customnpcs.internal.commands.suggestion.WorldSuggester; +import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; +import dev.foxikle.customnpcs.internal.menu.MenuUtils; +import dev.foxikle.customnpcs.internal.utils.Msg; +import dev.foxikle.customnpcs.internal.utils.WaitingType; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import org.bukkit.*; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; + +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; +import static com.mojang.brigadier.arguments.StringArgumentType.word; + +@SuppressWarnings("UnstableApiUsage") +public class NpcCommandRegistrar { + + private static final String PERMISSION_CREATE = "customnpcs.create"; + private static final String PERMISSION_EDIT = "customnpcs.edit"; + private static final String PERMISSION_DELETE = "customnpcs.delete"; + private static final String PERMISSION_CLONE = "customnpcs.commands.clone"; + private static final String PERMISSION_MOVEHERE = "customnpcs.commands.movehere"; + private static final String PERMISSION_GOTO = "customnpcs.commands.goto"; + private static final String PERMISSION_MANAGE = "customnpcs.commands.manage"; + private static final String PERMISSION_HELP = "customnpcs.commands.help"; + private static final String PERMISSION_RELOAD = "customnpcs.commands.reload"; + private static final String PERMISSION_WIKI = "customnpcs.command.wiki"; + private static final String PERMISSION_DEBUG = "customnpcs.edit"; + private static final String PERMISSION_FIXCONFIG = "customnpcs.commands.fix_config"; + private static final String PERMISSION_SETSOUND = "customnpcs.edit"; + + + public static LiteralCommandNode buildNode() { + LiteralCommandNode npcNode = LiteralArgumentBuilder.literal("npc") + .requires(source -> source.getSender().hasPermission(PERMISSION_HELP)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + Locale locale = getLocale(sender); + sender.sendMessage(CommandUtils.getHelpComponent(locale)); + return 1; + }) + .build(); + + registerCreateCommand(npcNode); + registerEditCommand(npcNode); + registerDeleteCommand(npcNode); + registerCloneCommand(npcNode); + registerMoveHereCommand(npcNode); + registerGotoCommand(npcNode); + registerManageCommand(npcNode); + registerListCommand(npcNode); + registerHelpCommand(npcNode); + registerReloadCommand(npcNode); + registerWikiCommand(npcNode); + registerDebugCommand(npcNode); + registerFixConfigCommand(npcNode); + registerSetsoundCommand(npcNode); + + return npcNode; + } + + private static void registerCreateCommand(LiteralCommandNode npcNode) { + LiteralCommandNode createNode = LiteralArgumentBuilder.literal("create") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_CREATE)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + if (!(sender instanceof Player player)) { + sender.sendMessage(Msg.format("You can't do this :P")); + return 0; + } + UUID uuid = UUID.randomUUID(); + CustomNPCs plugin = CustomNPCs.getInstance(); + InternalNpc npc = plugin.createNPC( + player.getWorld(), + player.getLocation(), + new dev.foxikle.customnpcs.data.Equipment(), + new dev.foxikle.customnpcs.data.Settings(), + uuid, + null, + new java.util.ArrayList<>() + ); + plugin.getEditingNPCs().put(player.getUniqueId(), npc); + plugin.getLotus().openMenu(player, MenuUtils.NPC_MAIN); + return 1; + }) + .build(); + npcNode.addChild(createNode); + } + + private static void registerEditCommand(LiteralCommandNode npcNode) { + LiteralCommandNode editNode = LiteralArgumentBuilder.literal("edit") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_EDIT)) + .then(RequiredArgumentBuilder.argument("npc", greedyString()) + .suggests(NpcSuggester.SUGGESTIONS) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + String npcArg = StringArgumentType.getString(context, "npc"); + if (!(sender instanceof Player player)) { + sender.sendMessage(Msg.format("You can't do this :P")); + return 0; + } + UUID uuid = CommandUtils.parseNpc(player, npcArg); + if (uuid == null) { + return 0; + } + CustomNPCs plugin = CustomNPCs.getInstance(); + InternalNpc finalNpc = plugin.getNPCByID(uuid); + Bukkit.getScheduler().runTaskLater(plugin, () -> { + InternalNpc newNpc = plugin.createNPC( + player.getWorld(), + finalNpc.getSpawnLoc(), + finalNpc.getEquipment(), + finalNpc.getSettings(), + finalNpc.getUniqueID(), + null, + finalNpc.getActions() + ); + plugin.getEditingNPCs().put(player.getUniqueId(), newNpc); + plugin.getLotus().openMenu(player, MenuUtils.NPC_MAIN); + }, 1); + return 1; + }) + .build()) + .build(); + npcNode.addChild(editNode); + } + + private static void registerDeleteCommand(LiteralCommandNode npcNode) { + LiteralCommandNode deleteNode = LiteralArgumentBuilder.literal("delete") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_DELETE)) + .then(RequiredArgumentBuilder.argument("npc", greedyString()) + .suggests(NpcSuggester.SUGGESTIONS) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + String npcArg = StringArgumentType.getString(context, "npc"); + if (!(sender instanceof Player player)) { + sender.sendMessage(Msg.format("You can't do this :P")); + return 0; + } + UUID uuid = CommandUtils.parseNpc(player, npcArg); + if (uuid == null) { + return 0; + } + if (!CommandUtils.checkNpc(player, uuid)) { + return 0; + } + CustomNPCs plugin = CustomNPCs.getInstance(); + InternalNpc finalNpc = plugin.getNPCByID(uuid); + plugin.getEditingNPCs().put(player.getUniqueId(), finalNpc); + plugin.getDeletionReason().put(player.getUniqueId(), false); + plugin.getLotus().openMenu(player, MenuUtils.NPC_DELETE); + return 1; + }) + .build()) + .build(); + npcNode.addChild(deleteNode); + } + + private static void registerCloneCommand(LiteralCommandNode npcNode) { + LiteralCommandNode cloneNode = LiteralArgumentBuilder.literal("clone") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_CLONE)) + .then(RequiredArgumentBuilder.argument("npc", greedyString()) + .suggests(NpcSuggester.SUGGESTIONS) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + String npcArg = StringArgumentType.getString(context, "npc"); + if (!(sender instanceof Player player)) { + sender.sendMessage(Msg.format("You can't do this :P")); + return 0; + } + UUID uuid = CommandUtils.parseNpc(player, npcArg); + if (uuid == null) { + return 0; + } + if (!CommandUtils.checkNpc(player, uuid)) { + return 0; + } + CustomNPCs plugin = CustomNPCs.getInstance(); + InternalNpc finalNpc = plugin.getNPCByID(uuid); + InternalNpc newNpc = finalNpc.clone(); + newNpc.setSpawnLoc(player.getLocation()); + newNpc.getSettings().setDirection(player.getLocation().getYaw()); + newNpc.createNPC(); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.commands.clone.success")); + return 1; + }) + .build()) + .build(); + npcNode.addChild(cloneNode); + } + + private static void registerMoveHereCommand(LiteralCommandNode npcNode) { + LiteralCommandNode movehereNode = LiteralArgumentBuilder.literal( + "movehere") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_MOVEHERE)) + .then(RequiredArgumentBuilder.argument("npc", greedyString()) + .suggests(NpcSuggester.SUGGESTIONS) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + String npcArg = StringArgumentType.getString(context, "npc"); + if (!(sender instanceof Player player)) { + sender.sendMessage(Msg.format("You can't do this :P")); + return 0; + } + UUID uuid = CommandUtils.parseNpc(player, npcArg); + if (uuid == null) { + return 0; + } + if (!CommandUtils.checkNpc(player, uuid)) { + return 0; + } + CustomNPCs plugin = CustomNPCs.getInstance(); + InternalNpc finalNpc = plugin.getNPCByID(uuid); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.commands.move.nudge")); + finalNpc.teleport(player.getLocation()); + finalNpc.remove(); + finalNpc.createNPC(); + Bukkit.getOnlinePlayers().forEach(finalNpc::injectPlayer); + return 1; + }) + .build()) + .build(); + npcNode.addChild(movehereNode); + } + + private static void registerGotoCommand(LiteralCommandNode npcNode) { + LiteralCommandNode gotoNode = LiteralArgumentBuilder.literal("goto") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_GOTO)) + .then(RequiredArgumentBuilder.argument("npc", greedyString()) + .suggests(NpcSuggester.SUGGESTIONS) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + String npcArg = StringArgumentType.getString(context, "npc"); + if (!(sender instanceof Player player)) { + sender.sendMessage(Msg.format("You can't do this :P")); + return 0; + } + UUID uuid = CommandUtils.parseNpc(player, npcArg); + if (uuid == null) { + return 0; + } + if (!CommandUtils.checkNpc(player, uuid)) { + return 0; + } + CustomNPCs plugin = CustomNPCs.getInstance(); + InternalNpc finalNpc = plugin.getNPCByID(uuid); + player.teleportAsync(finalNpc.getCurrentLocation()); + return 1; + }) + .build()) + .build(); + npcNode.addChild(gotoNode); + } + + private static void registerManageCommand(LiteralCommandNode npcNode) { + LiteralCommandNode manageNode = LiteralArgumentBuilder.literal("manage") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_MANAGE)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + Locale locale = getLocale(sender); + sender.sendMessage(CommandUtils.getListComponent(locale)); + return 1; + }) + .build(); + npcNode.addChild(manageNode); + } + + private static void registerListCommand(LiteralCommandNode npcNode) { + LiteralCommandNode listNode = LiteralArgumentBuilder.literal("list") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_MANAGE)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + Locale locale = getLocale(sender); + sender.sendMessage(CommandUtils.getListComponent(locale)); + return 1; + }) + .build(); + npcNode.addChild(listNode); + } + + private static void registerHelpCommand(LiteralCommandNode npcNode) { + LiteralCommandNode helpNode = LiteralArgumentBuilder.literal("help") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_HELP)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + sender.sendMessage(CommandUtils.getHelpComponent(getLocale(sender))); + return 1; + }) + .build(); + npcNode.addChild(helpNode); + } + + private static void registerReloadCommand(LiteralCommandNode npcNode) { + LiteralCommandNode reloadNode = LiteralArgumentBuilder.literal("reload") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_RELOAD)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + CustomNPCs plugin = CustomNPCs.getInstance(); + plugin.setReloading(true); + Locale locale = getLocale(sender); + sender.sendMessage(Msg.translate(locale, "customnpcs.commands.reload.start")); + plugin.reloadConfig(); + plugin.onDisable(); + plugin.onEnable(); + plugin.setReloading(false); + sender.sendMessage(Msg.translate(locale, "customnpcs.commands.reload.end")); + return 1; + }) + .build(); + npcNode.addChild(reloadNode); + } + + private static void registerWikiCommand(LiteralCommandNode npcNode) { + LiteralCommandNode wikiNode = LiteralArgumentBuilder.literal("wiki") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_WIKI)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + Locale locale = getLocale(sender); + sender.sendMessage( + Msg.translate(locale, "customnpcs.commands.wiki") + .clickEvent(ClickEvent.openUrl("https://docs.foxikle.dev")) + .appendSpace() + .hoverEvent(HoverEvent.showText(Msg.translate(locale, "customnpcs.commands.wiki" + + ".hover"))) + ); + return 1; + }) + .build(); + npcNode.addChild(wikiNode); + } + + private static void registerDebugCommand(LiteralCommandNode npcNode) { + LiteralCommandNode debugNode = LiteralArgumentBuilder.literal("debug") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_DEBUG)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + CustomNPCs plugin = CustomNPCs.getInstance(); + if (plugin.isDebug()) { + sender.sendMessage(Msg.translate(getLocale(sender), "customnpcs.commands.debug.message" + + ".disable")); + plugin.setDebug(false); + } else { + sender.sendMessage(Msg.translate(getLocale(sender), "customnpcs.commands.debug.message" + + ".enable")); + plugin.setDebug(true); + } + return 1; + }) + .build(); + npcNode.addChild(debugNode); + } + + private static void registerFixConfigCommand(LiteralCommandNode npcNode) { + LiteralCommandNode fixconfigNode = LiteralArgumentBuilder.literal( + "fixconfig") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_FIXCONFIG)) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + Locale locale = getLocale(sender); + sender.sendMessage(Msg.translate(locale, "customnpcs.commands.fix_config.usage")); + return 0; + }) + .then(LiteralArgumentBuilder.literal("world") + .executes(ctx -> executeFixConfig(ctx)) + .then(RequiredArgumentBuilder.argument("world", word()) + .suggests(WorldSuggester.SUGGESTIONS) + .executes(ctx -> executeFixConfig(ctx)) + .then(RequiredArgumentBuilder.argument("strategy", word()) + .suggests((ctx, builder) -> { + builder.suggest("NONE"); + builder.suggest("SAFE_LOCATION"); + return builder.buildFuture(); + }) + .executes(ctx -> executeFixConfig(ctx)) + .then(RequiredArgumentBuilder.argument("target", + greedyString()) + .suggests(NpcBrokenSuggester.SUGGESTIONS) + .executes(ctx -> executeFixConfig(ctx)) + ) + ) + ) + ) + .build(); + npcNode.addChild(fixconfigNode); + } + + private static void registerSetsoundCommand(LiteralCommandNode npcNode) { + LiteralCommandNode setsoundNode = LiteralArgumentBuilder.literal( + "setsound") + .requires(sender -> sender.getSender().hasPermission(PERMISSION_SETSOUND)) + .then(RequiredArgumentBuilder.argument("sound", greedyString()) + .suggests(SoundSuggester.SUGGESTIONS) + .executes(context -> { + CommandSender sender = context.getSource().getSender(); + if (!(sender instanceof Player player)) { + sender.sendMessage("You can't do this :P"); + return 0; + } + String soundRaw = StringArgumentType.getString(context, "sound"); + CustomNPCs plugin = CustomNPCs.getInstance(); + + if (plugin.isWaiting(player, WaitingType.SOUND)) { + String formatted = soundRaw.trim().toLowerCase(); + if (Registry.SOUNDS.get(NamespacedKey.fromString(formatted)) == null) { + player.sendMessage(Msg.translate(player.locale(), "customnpcs.commands.setsound" + + ".unknown_sound")); + } + + Bukkit.getScheduler().runTask(plugin, () -> { + plugin.waiting.remove(player.getUniqueId()); + dev.foxikle.customnpcs.actions.Action actionImpl = + plugin.editingActions.get(player.getUniqueId()); + if (actionImpl instanceof dev.foxikle.customnpcs.actions.defaultImpl.PlaySound action) { + action.setSound(formatted); + } else { + throw new IllegalArgumentException("Action " + actionImpl.getClass().getName() + " is not of type PlaySound"); + } + player.sendMessage(Msg.translate(player.locale(), "customnpcs.commands.setsound" + + ".success", Component.text(formatted))); + plugin.getLotus().openMenu(player, actionImpl.getMenu()); + }); + } else { + player.sendMessage(Msg.translate(player.locale(), "customnpcs.commands.setsound" + + ".was_not_waiting")); + } + return 1; + }) + .build()) + .build(); + npcNode.addChild(setsoundNode); + } + + private static int executeFixConfig(CommandContext context) { + CommandSender sender = context.getSource().getSender(); + CustomNPCs plugin = CustomNPCs.getInstance(); + FileManager fileManager = plugin.getFileManager(); + Locale locale = getLocale(sender); + + String worldArg = ""; + String strategyArg = ""; + String targetArg = ""; + + try { + worldArg = StringArgumentType.getString(context, "world"); + } catch (Exception ignored) { + } + + try { + strategyArg = StringArgumentType.getString(context, "strategy"); + } catch (Exception ignored) { + } + + try { + targetArg = StringArgumentType.getString(context, "target"); + } catch (Exception ignored) { + } + + if (worldArg.isEmpty()) { + sender.sendMessage(Msg.translate(locale, "customnpcs.commands.fix_config.usage")); + return 0; + } + + if (Bukkit.getWorld(worldArg) == null) { + sender.sendMessage("INVALID_WORLD"); + return 0; + } + + World w = Bukkit.getWorld(worldArg); + assert w != null; + + dev.foxikle.customnpcs.internal.commands.enums.FixConfigWorldStrategy strat = + dev.foxikle.customnpcs.internal.commands.enums.FixConfigWorldStrategy.parse(strategyArg); + if (strat == null) { + sender.sendMessage("INVALID_STRATEGY"); + return 0; + } + + AtomicInteger totalFixed = new AtomicInteger(0); + AtomicInteger movedbyStrategy = new AtomicInteger(0); + AtomicInteger failedToFix = new AtomicInteger(0); + AtomicInteger nonExistentNpcs = new AtomicInteger(0); + + if (targetArg.isEmpty() || targetArg.equalsIgnoreCase("all")) { + for (UUID uuid : fileManager.getBrokenNPCs().keySet()) { + YamlConfiguration yml = fileManager.getNpcYaml(); + ConfigurationSection parent = yml.getConfigurationSection(uuid.toString()); + if (parent == null) { + nonExistentNpcs.incrementAndGet(); + continue; + } + + ConfigurationSection location = parent.getConfigurationSection("location"); + Location loc; + String locString; + + if (location != null) { + double x = location.getDouble("x"); + double y = location.getDouble("y"); + double z = location.getDouble("z"); + float pitch = (float) location.getDouble("pitch"); + float yaw = (float) location.getDouble("yaw"); + loc = new Location(w, x, y, z, pitch, yaw); + locString = "(" + x + "," + y + "," + z + ")"; + } else { + loc = new Location(w, 0, 0, 0, 0, 0); + locString = "(0, 0, 0)"; + plugin.getLogger().warning("Fixed an NPC whose location data was wiped by Bukkit's configuration " + + "API. Its location was set to (0,0,0)"); + sender.sendMessage(Msg.translate(locale, "customnpcs.commands.fix_config.bukkit_wiped_data")); + } + + if (strat == dev.foxikle.customnpcs.internal.commands.enums.FixConfigWorldStrategy.SAFE_LOCATION) { + if (w.getBlockAt(loc).isSolid() || w.getBlockAt(loc.add(0, 1, 0)).isSolid()) { + RayTraceResult traceResult = w.rayTraceBlocks(loc.add(0, 329 - loc.y(), 0), + new Vector(0, -1, 0), 320D, FluidCollisionMode.NEVER); + + if (traceResult == null) { + plugin.getLogger().warning("Failed to fix npc " + uuid + " at " + locString + " -- " + + "Location cannot be made safe."); + failedToFix.incrementAndGet(); + continue; + } + loc.setY(traceResult.getHitBlock().getY() + 1); + } + movedbyStrategy.incrementAndGet(); + } + + parent.set("location", loc); + totalFixed.incrementAndGet(); + fileManager.saveNpcFile(yml); + } + } else { + UUID uuid = null; + for (Map.Entry entry : fileManager.getBrokenNPCs().entrySet()) { + if (entry.getValue().equals(targetArg)) { + uuid = entry.getKey(); + break; + } + } + + if (uuid == null) { + sender.sendMessage(Msg.translate(locale, "customnpcs.commands.invalid_name_or_uuid")); + return 0; + } + + YamlConfiguration yml = fileManager.getNpcYaml(); + ConfigurationSection parent = yml.getConfigurationSection(uuid.toString()); + if (parent == null) { + return 0; + } + ConfigurationSection location = parent.getConfigurationSection("location"); + Location loc; + String locString; + + if (location != null) { + double x = location.getDouble("x"); + double y = location.getDouble("y"); + double z = location.getDouble("z"); + float pitch = (float) location.getDouble("pitch"); + float yaw = (float) location.getDouble("yaw"); + loc = new Location(w, x, y, z, pitch, yaw); + locString = "(" + x + "," + y + "," + z + ")"; + } else { + loc = new Location(w, 0, 0, 0, 0, 0); + locString = "(0, 0, 0)"; + plugin.getLogger().warning("Fixed an NPC whose location data was wiped by Bukkit's configuration API." + + " Its location was set to (0,0,0)"); + sender.sendMessage(Msg.translate(locale, "customnpcs.commands.fix_config.bukkit_wiped_data")); + } + + if (strat == dev.foxikle.customnpcs.internal.commands.enums.FixConfigWorldStrategy.SAFE_LOCATION) { + if (w.getBlockAt(loc).isSolid() || w.getBlockAt(loc.add(0, 1, 0)).isSolid()) { + RayTraceResult traceResult = w.rayTraceBlocks(loc.add(0, 329 - loc.y(), 0), + new Vector(0, -1, 0), 320D, FluidCollisionMode.NEVER); + + if (traceResult == null) { + plugin.getLogger().warning("Failed to fix npc " + uuid + " at " + locString + " -- Location " + + "cannot be made safe."); + failedToFix.incrementAndGet(); + return 0; + } + loc.setY(traceResult.getHitBlock().getY() + 1); + } + movedbyStrategy.incrementAndGet(); + } + + parent.set("location", loc); + totalFixed.incrementAndGet(); + fileManager.saveNpcFile(yml); + } + + sender.sendMessage(Msg.translate(locale, "customnpcs.commands.fix_config.report", + totalFixed.get(), movedbyStrategy.get(), failedToFix.get(), nonExistentNpcs.get())); + return 1; + } + + private static Locale getLocale(CommandSender sender) { + if (sender instanceof Player player) { + return player.locale(); + } + return Locale.getDefault(); + } +} \ No newline at end of file diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ReloadCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ReloadCommand.java deleted file mode 100644 index 1a21ff41..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/ReloadCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.Description; -import dev.velix.imperat.annotations.Permission; -import dev.velix.imperat.annotations.SubCommand; -import dev.velix.imperat.annotations.Usage; -import dev.velix.imperat.command.AttachmentMode; - -import java.util.Locale; - -@SubCommand(value = "reload", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.commands.reload") -@Description("Reloads the CustomNPCs and its configuration.") -public class ReloadCommand { - - @Usage - public void usage(BukkitSource source) { - final CustomNPCs plugin = CustomNPCs.getInstance(); - plugin.setReloading(true); - Locale locale = Locale.getDefault(); - if (!source.isConsole()) source.asPlayer().locale(); - source.reply(Msg.translate(locale, "customnpcs.commands.reload.start")); - - plugin.reloadConfig(); - plugin.onDisable(); - plugin.onEnable(); - plugin.setReloading(false); - - source.reply(Msg.translate(locale, "customnpcs.commands.reload.end")); - } - -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/SetsoundCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/SetsoundCommand.java deleted file mode 100644 index c4546964..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/SetsoundCommand.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.actions.Action; -import dev.foxikle.customnpcs.actions.defaultImpl.PlaySound; -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.foxikle.customnpcs.internal.utils.WaitingType; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.*; -import dev.velix.imperat.command.AttachmentMode; -import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.entity.Player; - -@SubCommand(value = "setsound", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.edit") -@Description("Sets the sound of the playsound action!") -public class SetsoundCommand { - - @Usage - public void usage( - BukkitSource source, - @Default("minecraft:ui.button.click") @Named("sound") @SuggestionProvider("sound") @Greedy String soundRaw) { - - if (source.isConsole()) { - source.reply("You can't do this :P"); - return; - } - - final Player p = source.asPlayer(); - final CustomNPCs plugin = CustomNPCs.getInstance(); - // converts `ui button click` to `UI_BUTTON_CLICK`, like {@link Sound#UI_BUTTON_CLICK} - String formatted = soundRaw.trim().toLowerCase(); - - if (plugin.isWaiting(p, WaitingType.SOUND)) { - if (Registry.SOUNDS.get(NamespacedKey.fromString(formatted)) == null) { - p.sendMessage(Msg.translate(p.locale(), "customnpcs.commands.setsound.unknown_sound")); - } - - Bukkit.getScheduler().runTask(plugin, () -> { - plugin.waiting.remove(p.getUniqueId()); - Action actionImpl = plugin.editingActions.get(p.getUniqueId()); - if (actionImpl instanceof PlaySound action) { - action.setSound(formatted); - } else - throw new IllegalArgumentException("Action " + actionImpl.getClass().getName() + " is not of type PlaySound"); - p.sendMessage(Msg.translate(p.locale(), "customnpcs.commands.setsound.success", Component.text(formatted))); - plugin.getLotus().openMenu(p, actionImpl.getMenu()); - }); - } else { - p.sendMessage(Msg.translate(p.locale(), "customnpcs.commands.setsound.was_not_waiting")); - } - - } - -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/TeleportCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/TeleportCommand.java deleted file mode 100644 index 2b616030..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/TeleportCommand.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.*; -import dev.velix.imperat.command.AttachmentMode; -import org.bukkit.entity.Player; - -import java.util.UUID; - -@SubCommand(value = "goto", attachment = AttachmentMode.MAIN) -@Permission("customnpcs.commands.goto") -@Description("Teleports you to the specified NPC") -public class TeleportCommand { - @Usage - public void usage( - BukkitSource source, - @Named("npc") @SuggestionProvider("current_npc") @Greedy String npc - ) { - if (source.isConsole()) { - source.reply(Msg.format("You can't do this :P")); - return; - } - - UUID uuid = CommandUtils.parseNpc(source, npc); - if (uuid == null) { - return; - } - if (!CommandUtils.checkNpc(source, uuid)) return; - - final Player p = source.asPlayer(); - final CustomNPCs plugin = CustomNPCs.getInstance(); - final InternalNpc finalNpc = plugin.getNPCByID(uuid); - - assert finalNpc != null; - p.teleportAsync(finalNpc.getCurrentLocation()); - } -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/WikiCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/WikiCommand.java deleted file mode 100644 index ea32b757..00000000 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/WikiCommand.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.internal.commands; - -import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.Description; -import dev.velix.imperat.annotations.Permission; -import dev.velix.imperat.annotations.SubCommand; -import dev.velix.imperat.annotations.Usage; -import dev.velix.imperat.command.AttachmentMode; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; - -import java.util.Locale; - -@Permission("customnpcs.command.wiki") -@SubCommand(value = "wiki", attachment = AttachmentMode.MAIN) -@Description("Supplies a link to the plugin's documentation") -public class WikiCommand { - - @Usage - public void usage(BukkitSource source) { - Locale locale = Locale.getDefault(); - if (!source.isConsole()) source.asPlayer().locale(); - source.reply(Msg.translate(locale, "customnpcs.commands.wiki") - .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://docs.foxikle.dev")) - .appendSpace().hoverEvent(HoverEvent.showText(Msg.translate(locale, "customnpcs.commands.wiki.hover")))); - } - -} diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/enums/FixConfigWorldStrategy.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/enums/FixConfigWorldStrategy.java index 3b2fe3bd..47c6816b 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/enums/FixConfigWorldStrategy.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/enums/FixConfigWorldStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/NpcBrokenSuggester.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/NpcBrokenSuggester.java index 260dc2c8..af31357e 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/NpcBrokenSuggester.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/NpcBrokenSuggester.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025. Foxikle + * Copyright (c) 2025-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,24 +22,21 @@ package dev.foxikle.customnpcs.internal.commands.suggestion; +import com.mojang.brigadier.suggestion.SuggestionProvider; import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.FileManager; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.SuggestionContext; -import dev.velix.imperat.resolvers.SuggestionResolver; +import io.papermc.paper.command.brigadier.CommandSourceStack; -import java.util.List; -import java.util.UUID; +public class NpcBrokenSuggester { -public class NpcBrokenSuggester implements SuggestionResolver { - /** - * {@inheritDoc} - */ - @Override - public List autoComplete(SuggestionContext context, CommandParameter parameter) { - final CustomNPCs plugin = CustomNPCs.getInstance(); - FileManager fileManager = plugin.getFileManager(); - return fileManager.getBrokenNPCs().keySet().stream().map(UUID::toString).toList(); - } + public static final SuggestionProvider SUGGESTIONS = (context, builder) -> { + CustomNPCs plugin = CustomNPCs.getInstance(); + String input = builder.getRemaining().toLowerCase(); + + plugin.getFileManager().getBrokenNPCs().keySet().stream() + .map(uuid -> uuid.toString()) + .filter(uuid -> uuid.toLowerCase().startsWith(input)) + .forEach(builder::suggest); + + return builder.buildFuture(); + }; } \ No newline at end of file diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/NpcSuggester.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/NpcSuggester.java index a2e4b303..80d8369c 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/NpcSuggester.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/NpcSuggester.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,21 +22,21 @@ package dev.foxikle.customnpcs.internal.commands.suggestion; +import com.mojang.brigadier.suggestion.SuggestionProvider; import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.SuggestionContext; -import dev.velix.imperat.resolvers.SuggestionResolver; +import io.papermc.paper.command.brigadier.CommandSourceStack; -import java.util.List; +public class NpcSuggester { -public class NpcSuggester implements SuggestionResolver { - /** - * {@inheritDoc} - */ - @Override - public List autoComplete(SuggestionContext context, CommandParameter parameter) { - final CustomNPCs plugin = CustomNPCs.getInstance(); - return plugin.npcs.values().stream().map(npc -> plugin.getMiniMessage().stripTags(npc.getSettings().getName())).toList(); - } -} + public static final SuggestionProvider SUGGESTIONS = (context, builder) -> { + CustomNPCs plugin = CustomNPCs.getInstance(); + String input = builder.getRemaining().toLowerCase(); + + plugin.npcs.values().stream() + .map(npc -> plugin.getMiniMessage().stripTags(npc.getSettings().getName())) + .filter(name -> name.toLowerCase().startsWith(input)) + .forEach(builder::suggest); + + return builder.buildFuture(); + }; +} \ No newline at end of file diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/SoundSuggester.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/SoundSuggester.java index 375b1a13..31e4fcd0 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/SoundSuggester.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/SoundSuggester.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,40 +22,29 @@ package dev.foxikle.customnpcs.internal.commands.suggestion; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.SuggestionContext; -import dev.velix.imperat.resolvers.SuggestionResolver; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.Keyed; import org.bukkit.Registry; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; -@SuppressWarnings("unchecked") -public class SoundSuggester implements SuggestionResolver { +public class SoundSuggester { - private static final List SUGGESTIONS = new ArrayList<>(); + private static final List CACHED_SUGGESTIONS = new ArrayList<>(); static { - // use reflection because why not - try { - Class clazz = Class.forName("org.bukkit.Registry"); - Field registry = clazz.getField("SOUNDS"); - registry.setAccessible(true); - Registry val = (Registry) registry.get(null); - val.forEach(registry1 -> SUGGESTIONS.add(registry1.key().toString())); - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); + for (Keyed keyed : Registry.SOUNDS) { + CACHED_SUGGESTIONS.add(keyed.key().toString()); } } - /** - * {@inheritDoc} - */ - @Override - public List autoComplete(SuggestionContext context, CommandParameter parameter) { - return SUGGESTIONS; - } -} + public static final SuggestionProvider SUGGESTIONS = (context, builder) -> { + String input = builder.getRemaining().toLowerCase(); + CACHED_SUGGESTIONS.stream() + .filter(sound -> sound.toLowerCase().startsWith(input)) + .forEach(builder::suggest); + return builder.buildFuture(); + }; +} \ No newline at end of file diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/WorldSuggester.java b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/WorldSuggester.java index 8f7f1430..d06ed479 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/WorldSuggester.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/commands/suggestion/WorldSuggester.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,23 +22,19 @@ package dev.foxikle.customnpcs.internal.commands.suggestion; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.SuggestionContext; -import dev.velix.imperat.resolvers.SuggestionResolver; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.Bukkit; import org.bukkit.World; -import java.util.List; +public class WorldSuggester { -public class WorldSuggester implements SuggestionResolver { - /** - * @param context the context for suggestions - * @param parameter the parameter of the value to complete - * @return the auto-completed suggestions of the current argument - */ - @Override - public List autoComplete(SuggestionContext context, CommandParameter parameter) { - return Bukkit.getWorlds().stream().map(World::getName).toList(); - } -} + public static final SuggestionProvider SUGGESTIONS = (context, builder) -> { + String input = builder.getRemaining().toLowerCase(); + Bukkit.getWorlds().stream() + .map(World::getName) + .filter(name -> name.toLowerCase().startsWith(input)) + .forEach(builder::suggest); + return builder.buildFuture(); + }; +} \ No newline at end of file diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/interfaces/InternalNpc.java b/core/src/main/java/dev/foxikle/customnpcs/internal/interfaces/InternalNpc.java index eef23b32..8c1aee2f 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/interfaces/InternalNpc.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/interfaces/InternalNpc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/listeners/Listeners.java b/core/src/main/java/dev/foxikle/customnpcs/internal/listeners/Listeners.java index 50087feb..af066a5d 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/listeners/Listeners.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/listeners/Listeners.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,8 +22,8 @@ package dev.foxikle.customnpcs.internal.listeners; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; import dev.foxikle.customnpcs.actions.Action; import dev.foxikle.customnpcs.actions.conditions.Condition; import dev.foxikle.customnpcs.actions.defaultImpl.*; @@ -41,6 +41,7 @@ import io.papermc.paper.event.world.WorldGameRuleChangeEvent; import lombok.Getter; import lombok.Setter; +import me.clip.placeholderapi.PlaceholderAPIPlugin; import org.bukkit.*; import org.bukkit.block.BlockFace; import org.bukkit.command.ConsoleCommandSender; @@ -53,9 +54,11 @@ import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.player.*; +import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.util.Vector; import org.mineskin.data.CodeAndMessage; @@ -64,7 +67,6 @@ import org.mineskin.request.GenerateRequest; import org.mineskin.response.MineSkinResponse; -import java.io.InputStreamReader; import java.net.URL; import java.util.*; import java.util.concurrent.*; @@ -114,15 +116,18 @@ public Listeners(CustomNPCs plugin) { } public void start() { - Bukkit.getWorlds().forEach(world -> worldSleepingPercentages.put(world.getUID(), world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE))); + Bukkit.getWorlds().forEach(world -> worldSleepingPercentages.put(world.getUID(), + world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE))); service = Executors.newSingleThreadScheduledExecutor(); - service.scheduleAtFixedRate(() -> Bukkit.getOnlinePlayers().forEach(this::actionPlayerMovement), 1000, plugin.getConfig().getInt("LookInterval") * 50L, TimeUnit.MILLISECONDS); + service.scheduleAtFixedRate(() -> Bukkit.getOnlinePlayers().forEach(this::actionPlayerMovement), 1000, + plugin.getConfig().getInt("LookInterval") * 50L, TimeUnit.MILLISECONDS); } public void stop() { service.shutdown(); - Bukkit.getWorlds().forEach(world -> world.setGameRule(GameRule.PLAYERS_SLEEPING_PERCENTAGE, worldSleepingPercentages.get(world.getUID()))); + Bukkit.getWorlds().forEach(world -> world.setGameRule(GameRule.PLAYERS_SLEEPING_PERCENTAGE, + worldSleepingPercentages.get(world.getUID()))); CompletableFuture.runAsync(() -> { try { if (!service.awaitTermination(2, TimeUnit.SECONDS)) { @@ -152,7 +157,8 @@ private void actionPlayerMovement(Player player) { } } - private void processPlayerMovement(final Player player, final InternalNpc npc, final World world, final World npcWorld, final Location location, final UUID uuid) { + private void processPlayerMovement(final Player player, final InternalNpc npc, final World world, + final World npcWorld, final Location location, final UUID uuid) { if (player.getGameMode() == GameMode.SPECTATOR) return; // we don't care about spectators final Location npcLocation = npc.getCurrentLocation(); MovementData oldMovementData; // difference in order of initialization in if/else statement @@ -233,6 +239,16 @@ public void onPlayerInteract(PlayerInteractEntityEvent e) { } } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerMove(PlayerMoveEvent e) { + Player player = e.getPlayer(); + if (plugin.isWaiting(player, WaitingType.RECORDING)) { + FollowPresetPathAction.recordMovement(player, e.getTo()); + return; + } + actionPlayerMovement(player); + } + /** * The handler for text input * @@ -244,8 +260,33 @@ public void onPlayerInteract(PlayerInteractEntityEvent e) { public void onChat(AsyncPlayerChatEvent e) { Player player = e.getPlayer(); String message = e.getMessage(); - boolean cancel = message.equalsIgnoreCase("quit") || message.equalsIgnoreCase("exit") || message.equalsIgnoreCase("stop") || message.equalsIgnoreCase("cancel"); - if (plugin.isWaiting(player, WaitingType.COMMAND)) { + boolean cancel = message.equalsIgnoreCase("quit") || message.equalsIgnoreCase("exit") + || message.equalsIgnoreCase("stop") || message.equalsIgnoreCase("cancel"); + + if (plugin.isWaiting(player, WaitingType.RECORDING)) { + if (!message.equalsIgnoreCase("done")) return; + e.setCancelled(true); + + Action actionImpl = plugin.editingActions.get(player.getUniqueId()); + if (!(actionImpl instanceof FollowPresetPathAction follow)) { + plugin.getLogger().warning("Expected action to be an instance of 'FollowPresetPathAction', got " + actionImpl.getClass().getSimpleName()); + return; + } + + plugin.waiting.remove(player.getUniqueId()); + + if (cancel) { + SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, follow.getMenu())); + return; + } + + List path = FollowPresetPathAction.stopRecording(player); + follow.setPath(path); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.recording", + String.valueOf(path.size()))); + SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, follow.getMenu())); + return; + } else if (plugin.isWaiting(player, WaitingType.COMMAND)) { Action actionImpl = plugin.editingActions.get(player.getUniqueId()); if (!(actionImpl instanceof RunCommand runCommand)) { plugin.getLogger().warning("Expected action to be an instance of 'RunCommand', got " + actionImpl.getClass().getSimpleName()); @@ -280,7 +321,8 @@ public void onChat(AsyncPlayerChatEvent e) { plugin.waiting.remove(player.getUniqueId()); int index = HologramMenu.editingIndicies.get(player.getUniqueId()); if (npc.getSettings().getRawHolograms().length <= index) { // an addition - npc.getSettings().setRawHolograms(Arrays.copyOf(npc.getSettings().getRawHolograms(), index + 1)); // extend it by 1 + npc.getSettings().setRawHolograms(Arrays.copyOf(npc.getSettings().getRawHolograms(), index + 1)); // + // extend it by 1 } npc.getSettings().getRawHolograms()[index] = message; player.sendMessage(Msg.translate(player.locale(), "customnpcs.set.name", index + 1, Msg.format(message))); @@ -340,7 +382,8 @@ public void onChat(AsyncPlayerChatEvent e) { setTitle.setSubTitle(message); - player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.subtitle", Msg.format(message))); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.subtitle", + Msg.format(message))); SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, actionImpl.getMenu())); } else if (plugin.isWaiting(player, WaitingType.MESSAGE)) { Action actionImpl = plugin.editingActions.get(player.getUniqueId()); @@ -356,7 +399,8 @@ public void onChat(AsyncPlayerChatEvent e) { plugin.waiting.remove(player.getUniqueId()); sendMessage.setRawMessage(message); - player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.message", Msg.format(message))); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.message", + Msg.format(message))); SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, actionImpl.getMenu())); } else if (plugin.isWaiting(player, WaitingType.SERVER)) { Action actionImpl = plugin.editingActions.get(player.getUniqueId()); @@ -373,7 +417,8 @@ public void onChat(AsyncPlayerChatEvent e) { runServer.setServer(message); - player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.server", Msg.format(message))); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.server", + Msg.format(message))); SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, actionImpl.getMenu())); } else if (plugin.isWaiting(player, WaitingType.ACTIONBAR)) { Action actionImpl = plugin.editingActions.get(player.getUniqueId()); @@ -388,7 +433,8 @@ public void onChat(AsyncPlayerChatEvent e) { } plugin.waiting.remove(player.getUniqueId()); actionBar.setRawMessage(message); - player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.actionbar", Msg.format(message))); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.actionImpls.set.actionbar", + Msg.format(message))); SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, actionImpl.getMenu())); } else if (plugin.isWaiting(player, WaitingType.PLAYER)) { if (cancel) { @@ -404,27 +450,28 @@ public void onChat(AsyncPlayerChatEvent e) { return; } + // this runs on an async thread, so there isn't any need to do this async :) player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.fetching.player", message)); String name = e.getMessage(); - try { - URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + name); - InputStreamReader reader = new InputStreamReader(url.openStream()); - String uuid = new JsonParser().parse(reader).getAsJsonObject().get("id").getAsString(); - reader.close(); - - URL url2 = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false"); - reader = new InputStreamReader(url2.openStream()); - - JsonObject property = new JsonParser().parse(reader).getAsJsonObject().get("properties").getAsJsonArray().get(0).getAsJsonObject(); - String value = property.get("value").getAsString(); - String signature = property.get("signature").getAsString(); - npc.getSettings().setSkinData(signature, value, Msg.translatedString(player.locale(), "customnpcs.skins.imported_by.player_name", Msg.format(name))); - } catch (Exception ignored) { - player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.errors.player_does_not_exist", name)); + + PlayerProfile profile = Bukkit.createProfile(name); + if (!profile.complete()) { + player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.errors.player_does_not_exist", + name)); e.setCancelled(true); return; } + profile.complete(); + + + for (ProfileProperty property : profile.getProperties()) { + if (!property.getName().equals("textures")) continue; + npc.getSettings().setSkinData(property.getSignature(), property.getValue(), + Msg.translatedString(player.locale(), "customnpcs" + + ".skins.imported_by.player_name", Msg.format(name))); + } + plugin.waiting.remove(player.getUniqueId()); player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.success.player_name", name)); SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, MenuUtils.NPC_SKIN)); @@ -449,7 +496,9 @@ public void onChat(AsyncPlayerChatEvent e) { .name("URL Generated Skin") .visibility(Visibility.UNLISTED); SkinUtils.fetch(request).thenAccept(skin -> { - npc.getSettings().setSkinData(skin.texture().data().signature(), skin.texture().data().value(), Msg.translatedString(player.locale(), "customnpcs.skins.imported_by.url")); + npc.getSettings().setSkinData(skin.texture().data().signature(), + skin.texture().data().value(), Msg.translatedString(player.locale(), "customnpcs" + + ".skins.imported_by.url")); plugin.waiting.remove(player.getUniqueId()); player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.success.url", message)); SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, MenuUtils.NPC_SKIN)); @@ -466,17 +515,22 @@ public void onChat(AsyncPlayerChatEvent e) { Optional detailsOptional = response.getErrorOrMessage(); Throwable finalThrowable = throwable; detailsOptional.ifPresent(details -> { - plugin.getLogger().log(Level.SEVERE, details.code() + " : " + details, finalThrowable); + plugin.getLogger().log(Level.SEVERE, details.code() + " : " + details, + finalThrowable); System.out.println(details.code() + ": " + details.message()); }); } - if (throwable.getMessage().equalsIgnoreCase("java.lang.RuntimeException: org.mineskin.data.MineskinException: Failed to find image from url")) { - player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.errors.no_image_data")); + if (throwable.getMessage().equalsIgnoreCase("java.lang.RuntimeException: org.mineskin" + + ".data.MineskinException: Failed to find image from url")) { + player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.errors" + + ".no_image_data")); return null; } - player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.errors.unknown_url_error")); - plugin.getLogger().log(Level.SEVERE, "An error occurred whilst parsing this skin from a url.", throwable); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.skins.errors" + + ".unknown_url_error")); + plugin.getLogger().log(Level.SEVERE, "An error occurred whilst parsing this skin from a " + + "url.", throwable); return null; }); } catch (Exception ex) { @@ -496,7 +550,8 @@ public void onChat(AsyncPlayerChatEvent e) { } plugin.waiting.remove(player.getUniqueId()); e.setCancelled(true); - player.sendMessage(Msg.translate(player.locale(), "customnpcs.set.clickable_hologram", Msg.format(message))); + player.sendMessage(Msg.translate(player.locale(), "customnpcs.set.clickable_hologram", + Msg.format(message))); npc.getSettings().setCustomInteractableHologram(message); SCHEDULER.runTask(plugin, () -> plugin.getLotus().openMenu(player, MenuUtils.NPC_EXTRA_SETTINGS)); } else if (plugin.isWaiting(player, WaitingType.FACING)) { @@ -531,7 +586,8 @@ public void onChat(AsyncPlayerChatEvent e) { public void onPlayerLogin(PlayerJoinEvent e) { Player player = e.getPlayer(); - if (plugin.update && plugin.getConfig().getBoolean("AlertOnUpdate") && player.hasPermission("customnpcs.alert")) { + if (plugin.update && plugin.getConfig().getBoolean("AlertOnUpdate") && player.hasPermission("customnpcs" + + ".alert")) { player.sendMessage(Msg.translate(player.locale(), "customnpcs.should_update").appendNewline() .append(Msg.format("<#1bd96a>[Modrinth]"))); @@ -691,7 +747,8 @@ private void recalcSleepingPercentages() { world.setGameRule(GameRule.PLAYERS_SLEEPING_PERCENTAGE, target); return; } - world.setGameRule(GameRule.PLAYERS_SLEEPING_PERCENTAGE, (int) (((playercount - npcCount) / (double) playercount) * target)); + world.setGameRule(GameRule.PLAYERS_SLEEPING_PERCENTAGE, + (int) (((playercount - npcCount) / (double) playercount) * target)); }); } @@ -770,6 +827,25 @@ public void onSwapToOffhand(PlayerSwapHandItemsEvent event) { } } + @EventHandler + public void serverLoadEvent(ServerLoadEvent event) { + SCHEDULER.runTaskLater(plugin, () -> { + // setup papi, which happens after the server load event is fired. + Plugin placeholderAPI = Bukkit.getPluginManager().getPlugin("PlaceholderAPI"); + if (placeholderAPI != null) { + PlaceholderAPIPlugin papiPlugin = (PlaceholderAPIPlugin) placeholderAPI; + plugin.papiPlayerExpansion = papiPlugin.getLocalExpansionManager().getExpansion("Player") != null; + + plugin.getLogger().info("Successfully hooked into PlaceholderAPI."); + plugin.papi = true; + } else { + plugin.papi = false; + plugin.getLogger().warning("Could not find PlaceholderAPI! PlaceholderAPI isn't required, but " + + "CustomNPCs does support it."); + } + }, 30); + } + @Getter private static class MovementData { private final UUID uniqueId; diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ActionCustomizerMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ActionCustomizerMenu.java index 80f33853..3b157920 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ActionCustomizerMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ActionCustomizerMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ActionMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ActionMenu.java index f04e74a0..c357dd74 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ActionMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ActionMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ConditionCustomizerMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ConditionCustomizerMenu.java index d19a4816..7c725e8d 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ConditionCustomizerMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ConditionCustomizerMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ConditionMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ConditionMenu.java index e63b93bf..efa5dcce 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ConditionMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ConditionMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/DeleteLineMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/DeleteLineMenu.java index 1453336e..46570ffa 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/DeleteLineMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/DeleteLineMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/DeleteMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/DeleteMenu.java index 5b20c07c..851206e1 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/DeleteMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/DeleteMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/EquipmentMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/EquipmentMenu.java index c74e4751..03d866a5 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/EquipmentMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/EquipmentMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ExtraSettingsMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ExtraSettingsMenu.java index d9fb4048..da961426 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ExtraSettingsMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/ExtraSettingsMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/HologramMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/HologramMenu.java index fb104d9b..5c8e3756 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/HologramMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/HologramMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MainNPCMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MainNPCMenu.java index 550a9d3c..bdf2b181 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MainNPCMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MainNPCMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MenuItems.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MenuItems.java index 356996cc..04fa2465 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MenuItems.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MenuItems.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -112,7 +112,8 @@ public static ItemStack skinSelection(InternalNpc npc, Player player) { profile.setProperty(new ProfileProperty("textures", texture)); skullMeta.setPlayerProfile(profile); }) - .setLore(Msg.lore(player.locale(), "customnpcs.menus.main.items.skin.lore", Component.text(npc.getSettings().getSkinName()))) + .setLore(Msg.lore(player.locale(), "customnpcs.menus.main.items.skin.lore", + npc.getSettings().getSkinName())) .setDisplay(Msg.translate(player.locale(), "customnpcs.menus.main.items.skin.name")) .build(); } diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MenuUtils.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MenuUtils.java index c7e66490..b216b7d6 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MenuUtils.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/MenuUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -177,7 +177,6 @@ public SkinIcon(String value, String signature, String name, CustomNPCs plugin, public ItemStack toItem() { return ItemBuilder.modern(PLAYER_HEAD).setDisplay(Msg.format("" + name)) .setLore( - Msg.translate(locale, "customnpcs.menus.skin_catalog.items.icon.lore", name), Component.empty(), Msg.translate(locale, "customnpcs.items.click_to_select") ).modifyMeta(SkullMeta.class, skullMeta -> { diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/NewActionMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/NewActionMenu.java index 41daf52b..482e1a5a 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/NewActionMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/NewActionMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/NewConditionMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/NewConditionMenu.java index 5002518c..d9f18aff 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/NewConditionMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/NewConditionMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/PoseEditorMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/PoseEditorMenu.java index 8adde4f6..1d81a7bc 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/PoseEditorMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/PoseEditorMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025. Foxikle + * Copyright (c) 2025-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/SkinCatalog.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/SkinCatalog.java index d9760d5b..91954f8a 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/SkinCatalog.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/SkinCatalog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/SkinMenu.java b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/SkinMenu.java index e4d8d8eb..f55892b3 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/menu/SkinMenu.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/menu/SkinMenu.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/ActionbarRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/ActionbarRunnable.java index f9737ffd..e5900d41 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/ActionbarRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/ActionbarRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/CommandRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/CommandRunnable.java index 48cfff53..8c25dbd6 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/CommandRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/CommandRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/FacingDirectionRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/FacingDirectionRunnable.java index f4221565..58af0edf 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/FacingDirectionRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/FacingDirectionRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/InteractableHologramRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/InteractableHologramRunnable.java index 401d9d36..f5db2980 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/InteractableHologramRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/InteractableHologramRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/MessageRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/MessageRunnable.java index 02c819e9..ddbfc315 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/MessageRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/MessageRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/NameRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/NameRunnable.java index b17572d5..d58b7e18 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/NameRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/NameRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/NudgeRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/NudgeRunnable.java index 27cf2052..24af17f6 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/NudgeRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/NudgeRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/PlayerNameRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/PlayerNameRunnable.java index 63b9c818..93ad81ec 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/PlayerNameRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/PlayerNameRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/DebugCommand.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/RecordingRunnable.java similarity index 54% rename from core/src/main/java/dev/foxikle/customnpcs/internal/commands/DebugCommand.java rename to core/src/main/java/dev/foxikle/customnpcs/internal/runnables/RecordingRunnable.java index ae3dfd8d..638615c5 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/commands/DebugCommand.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/RecordingRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025. Foxikle + * Copyright (c) 2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,30 +20,36 @@ * SOFTWARE. */ -package dev.foxikle.customnpcs.internal.commands; +package dev.foxikle.customnpcs.internal.runnables; import dev.foxikle.customnpcs.internal.CustomNPCs; import dev.foxikle.customnpcs.internal.utils.Msg; -import dev.velix.imperat.BukkitSource; -import dev.velix.imperat.annotations.Description; -import dev.velix.imperat.annotations.Permission; -import dev.velix.imperat.annotations.SubCommand; -import dev.velix.imperat.annotations.Usage; -import dev.velix.imperat.command.AttachmentMode; +import dev.foxikle.customnpcs.internal.utils.WaitingType; +import net.kyori.adventure.title.Title; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; -@Permission("customnpcs.edit") -@SubCommand(value = "debug", attachment = AttachmentMode.MAIN) -@Description("Enables debug message. (They can be very spammy)") -public class DebugCommand { - @Usage - public void execute(BukkitSource sender) { - CustomNPCs plugin = CustomNPCs.getInstance(); - if (plugin.isDebug()) { - sender.reply(Msg.translate(CommandUtils.getLocale(sender), "customnpcs.commands.debug.message.disable")); - plugin.setDebug(false); - } else { - sender.reply(Msg.translate(CommandUtils.getLocale(sender), "customnpcs.commands.debug.message.enable")); - plugin.setDebug(true); +import java.time.Duration; + +public class RecordingRunnable extends BukkitRunnable { + private final Player player; + private final CustomNPCs plugin; + + public RecordingRunnable(Player player, CustomNPCs plugin) { + this.player = player; + this.plugin = plugin; + } + + @Override + public void run() { + if (!plugin.isWaiting(player, WaitingType.RECORDING)) { + this.cancel(); + return; } + player.showTitle(Title.title( + Msg.translate(player.locale(), "customnpcs.data.recording.title"), + Msg.translate(player.locale(), "customnpcs.data.recording.subtitle"), + Title.Times.times(Duration.ofMillis(0), Duration.ofMillis(1000L), Duration.ofMillis(0)) + )); } } diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/ServerRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/ServerRunnable.java index 93fd7345..0137abf8 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/ServerRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/ServerRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/SoundRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/SoundRunnable.java index 1a51c7da..626da5bf 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/SoundRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/SoundRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/SubtitleRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/SubtitleRunnable.java index 4e850d88..34943154 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/SubtitleRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/SubtitleRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/TargetInputRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/TargetInputRunnable.java index 3691c7fb..61ccddbd 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/TargetInputRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/TargetInputRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/TitleRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/TitleRunnable.java index 104d9dfb..1f6d97c8 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/TitleRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/TitleRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/UrlRunnable.java b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/UrlRunnable.java index 3eb78993..95fa8fb7 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/UrlRunnable.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/runnables/UrlRunnable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/translations/Translations.java b/core/src/main/java/dev/foxikle/customnpcs/internal/translations/Translations.java index 3a274478..1a171cd0 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/translations/Translations.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/translations/Translations.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,39 +24,34 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.translation.GlobalTranslator; -import net.kyori.adventure.translation.TranslationRegistry; -import net.kyori.adventure.util.UTF8ResourceBundleControl; +import net.kyori.adventure.translation.TranslationStore; +import java.text.MessageFormat; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.ResourceBundle; public class Translations { - TranslationRegistry registry = TranslationRegistry.create(Key.key("customnpcs:localization")); - - public static final Locale VIETNAMESE = new Locale("vi"); + public static final Locale RUSSIAN = new Locale("ru"); + private static final TranslationStore STORE = TranslationStore.messageFormat(Key.key("customnpcs" + + ":root")); + private static final Map BUNDLES = new HashMap<>(); + private static boolean setup = false; public void setup() { - - - ResourceBundle zh = ResourceBundle.getBundle("localization.Chinese", Locale.SIMPLIFIED_CHINESE, UTF8ResourceBundleControl.get()); - registry.registerAll(Locale.SIMPLIFIED_CHINESE, zh, true); - - - ResourceBundle ru = ResourceBundle.getBundle("localization.Russian", new Locale("ru"), UTF8ResourceBundleControl.get()); - registry.registerAll(new Locale("ru"), ru, true); - - ResourceBundle de = ResourceBundle.getBundle("localization.German", Locale.GERMAN, UTF8ResourceBundleControl.get()); - registry.registerAll(Locale.GERMAN, de, true); - - ResourceBundle en = ResourceBundle.getBundle("localization.English", Locale.US, UTF8ResourceBundleControl.get()); - registry.registerAll(Locale.ENGLISH, en, true); - - ResourceBundle vi = ResourceBundle.getBundle("localization.Vietnamese", VIETNAMESE, UTF8ResourceBundleControl.get()); - registry.registerAll(VIETNAMESE, vi, true); - - registry.defaultLocale(Locale.ENGLISH); - - GlobalTranslator.translator().addSource(registry); + if (setup) return; + BUNDLES.put(Locale.SIMPLIFIED_CHINESE, ResourceBundle.getBundle("localization.Chinese", + Locale.SIMPLIFIED_CHINESE)); + BUNDLES.put(RUSSIAN, ResourceBundle.getBundle("localization.Russian", RUSSIAN)); + BUNDLES.put(Locale.GERMAN, ResourceBundle.getBundle("localization.German", Locale.GERMAN)); + BUNDLES.put(Locale.US, ResourceBundle.getBundle("localization.English", Locale.US)); + BUNDLES.put(VIETNAMESE, ResourceBundle.getBundle("localization.Vietnamese", VIETNAMESE)); + + BUNDLES.forEach((locale, b) -> STORE.registerAll(locale, b.keySet(), s -> new MessageFormat(b.getString(s)))); + + GlobalTranslator.translator().addSource(STORE); + setup = true; } -} +} \ No newline at end of file diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ActionRegistry.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ActionRegistry.java index 36eccb21..afe65118 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ActionRegistry.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ActionRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/AutoUpdater.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/AutoUpdater.java index 2a015a16..39f67c8c 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/AutoUpdater.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/AutoUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ComponentWrapper.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ComponentWrapper.java index 4ec347f0..6a35c1fd 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ComponentWrapper.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ComponentWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/Msg.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/Msg.java index 85b37011..81075c61 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/Msg.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/Msg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/OpenButtonAction.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/OpenButtonAction.java index 9ad80e02..f0a5ef67 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/OpenButtonAction.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/OpenButtonAction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ParameterizedSupplier.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ParameterizedSupplier.java index 4bd1035e..a4619e87 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ParameterizedSupplier.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/ParameterizedSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/SkinUtils.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/SkinUtils.java index 43ba7b6f..3ef3b175 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/SkinUtils.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/SkinUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025. Foxikle + * Copyright (c) 2025-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/Utils.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/Utils.java index 2cbe8d8b..4e763467 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/Utils.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/WaitingType.java b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/WaitingType.java index 66e79ff4..0e4fb2ea 100644 --- a/core/src/main/java/dev/foxikle/customnpcs/internal/utils/WaitingType.java +++ b/core/src/main/java/dev/foxikle/customnpcs/internal/utils/WaitingType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025. Foxikle + * Copyright (c) 2025-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,5 +25,5 @@ public enum WaitingType { COMMAND, NAME, TARGET, TITLE, MESSAGE, FACING, SOUND, SERVER, ACTIONBAR, URL, PLAYER, HOLOGRAM, SUBTITLE, - NUDGE + NUDGE, RECORDING } diff --git a/core/src/main/resources/localization/Chinese_zh_CN.properties b/core/src/main/resources/localization/Chinese_zh_CN.properties index 75a107ec..cef68c7b 100644 --- a/core/src/main/resources/localization/Chinese_zh_CN.properties +++ b/core/src/main/resources/localization/Chinese_zh_CN.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2024-2025. Foxikle +# Copyright (c) 2024-2026. Foxikle # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -36,7 +36,7 @@ customnpcs.commands.wiki =\u70B9\u5 customnpcs.commands.wiki.hover =\u70B9\u51FB\u8BBF\u95EE https\://docs.foxikle.dev/ customnpcs.commands.setsound.unknown_sound =\u65E0\u6CD5\u8BC6\u522B\u7684\u58F0\u97F3\uFF0C\u8BF7\u4F7F\u7528\u6807\u7B7E\u4E0D\u5168\u3002 # {0} is the name of the sound -customnpcs.commands.setsound.success =\u6210\u529F\u5C06\u58F0\u97F3\u8BBE\u7F6E\u4E3A '{0}' +customnpcs.commands.setsound.success=\u6210\u529F\u5C06\u58F0\u97F3\u8BBE\u7F6E\u4E3A ''{0}'' customnpcs.commands.setsound.was_not_waiting =\u672A\u80FD\u8BBE\u7F6ENPC\u58F0\u97F3.NPCs\u6CA1\u6709\u7B49\u5F85\u56DE\u5E94\u3002\u5982\u679C\u60A8\u8BA4\u4E3A\u8FD9\u662F\u9519\u8BEF\uFF0C\u8BF7\u8054\u7CFBFoxikle. customnpcs.commands.invalid_uuid =\u63D0\u4F9B\u7684UUID\u4E0D\u5339\u914D\u4EFB\u4F55NPC. customnpcs.commands.invalid_name_or_uuid =\u63D0\u4F9B\u7684UUID\u6216\u540D\u79F0\u65E0\u6548. @@ -88,25 +88,25 @@ customnpcs.commands.help.wiki.hover =\u7ED9\u4F60\u8BB #=====================================# # Actions Messages # #=====================================# -customnpcs.actionImpls.set.command =\u6210\u529F\u5C06\u547D\u4EE4\u8BBE\u7F6E\u4E3A '{0}' -customnpcs.actionImpls.set.title =\u6210\u529F\u5C06\u6807\u9898\u8BBE\u7F6E\u4E3A '{0}' -customnpcs.actionImpls.set.subtitle =\u6210\u529F\u5C06\u526F\u6807\u9898\u8BBE\u7F6E\u4E3A '{0}' -customnpcs.actionImpls.set.message =\u6210\u529F\u5C06\u6D88\u606F\u8BBE\u7F6E\u4E3A '{0}' -customnpcs.actionImpls.set.server =\u6210\u529F\u5C06\u670D\u52A1\u5668\u8BBE\u7F6E\u4E3A '{0}' -customnpcs.actionImpls.set.actionbar =\u6210\u529F\u5C06\u52A8\u4F5C\u6761\u8BBE\u7F6E\u4E3A '{0}' -customnpcs.actionImpls.conditions.set.target =\u6210\u529F\u5C06\u76EE\u6807\u8BBE\u7F6E\u4E3A '{0}' +customnpcs.actionImpls.set.command=\u6210\u529F\u5C06\u547D\u4EE4\u8BBE\u7F6E\u4E3A ''{0}'' +customnpcs.actionImpls.set.title=\u6210\u529F\u5C06\u6807\u9898\u8BBE\u7F6E\u4E3A ''{0}'' +customnpcs.actionImpls.set.subtitle=\u6210\u529F\u5C06\u526F\u6807\u9898\u8BBE\u7F6E\u4E3A ''{0}'' +customnpcs.actionImpls.set.message=\u6210\u529F\u5C06\u6D88\u606F\u8BBE\u7F6E\u4E3A ''{0}'' +customnpcs.actionImpls.set.server=\u6210\u529F\u5C06\u670D\u52A1\u5668\u8BBE\u7F6E\u4E3A ''{0}'' +customnpcs.actionImpls.set.actionbar=\u6210\u529F\u5C06\u52A8\u4F5C\u6761\u8BBE\u7F6E\u4E3A ''{0}'' +customnpcs.actionImpls.conditions.set.target=\u6210\u529F\u5C06\u76EE\u6807\u8BBE\u7F6E\u4E3A ''{0}'' #=====================================# # General Messages # #=====================================# customnpcs.should_update =---------------- [\!] CustomNPCs [\!] ----------------\u6709\u65B0\u66F4\u65B0\u53EF\u7528\! \u5982\u679C\u60A8\u80FD\u66F4\u65B0\u4E00\u4E0B\uFF0C\u6211\u5C06\u975E\u5E38\u611F\u6FC0 \:) ~Foxikle # {0} is the new name -customnpcs.set.name =\u6210\u529F\u5C06\u540D\u79F0\u8BBE\u7F6E\u4E3A '{0}' +customnpcs.set.name=\u6210\u529F\u5C06\u540D\u79F0\u8BBE\u7F6E\u4E3A ''{0}'' # {0} is the input -customnpcs.error.parse_number =\u65E0\u6CD5\u89E3\u6790\u6570\u5B57 '{0}' \u8BF7\u518D\u8BD5\u4E00\u6B21. +customnpcs.error.parse_number=\u65E0\u6CD5\u89E3\u6790\u6570\u5B57 ''{0}'' \u8BF7\u518D\u8BD5\u4E00\u6B21. customnpcs.error.npc-menu-expired =\u54CE\u5440\! \u770B\u8D77\u6765\u60A8\u7684\u83DC\u5355\u5B9E\u4F8B\u5DF2\u8FC7\u671F\! \u60A8\u53EF\u4EE5\u91CD\u65B0\u6253\u5F00\u83DC\u5355\u6765\u89E3\u51B3\u8FD9\u4E2A\u95EE\u9898 \:) customnpcs.error.forgot_bailout_response =\u54CE\u5440\! \u6211\u4EEC\u5FD8\u8BB0\u4E86\u5982\u4F55\u5904\u7406\u62D2\u7EDD\u5220\u9664\u8FD9\u4E2ANPC. \u522B\u62C5\u5FC3,\u5B83\u6CA1\u6709\u88AB\u5220\u9664\! # {0} is the hologram text -customnpcs.set.clickable_hologram =\u6210\u529F\u5C06NPC\u7684\u5168\u606F\u6587\u5B57\u8BBE\u7F6E\u4E3A'{0}' +customnpcs.set.clickable_hologram=\u6210\u529F\u5C06NPC\u7684\u5168\u606F\u6587\u5B57\u8BBE\u7F6E\u4E3A''{0}'' customnpcs.set.facing_direction =\u6210\u529F\u8BBE\u7F6E\u671D\u5411\! # {0} is the name of NPC customnpcs.delete.success =\u6C38\u4E45\u5220\u9664\u4E86 {0}. @@ -118,16 +118,16 @@ customnpcs.edit.fail =\u6B64\u64CD # Skin Messages # #=====================================# # {0} is the players name -customnpcs.skins.fetching.player =\u6B63\u5728\u5C1D\u8BD5\u4ECEMojang\u7684API\u83B7\u53D6 {0}' \u7684\u76AE\u80A4.\u8FD9\u53EF\u80FD\u9700\u8981\u4E00\u4E9B\u65F6\u95F4\! +customnpcs.skins.fetching.player=\u6B63\u5728\u5C1D\u8BD5\u4ECEMojang\u7684API\u83B7\u53D6 {0}'' \u7684\u76AE\u80A4.\u8FD9\u53EF\u80FD\u9700\u8981\u4E00\u4E9B\u65F6\u95F4\! customnpcs.skins.fetching.url =\u6B63\u5728\u5C1D\u8BD5\u4ECEURL\u83B7\u53D6\u76AE\u80A4.\u8FD9\u53EF\u80FD\u9700\u8981\u4E00\u4E9B\u65F6\u95F4\! -customnpcs.skins.imported_by.player_name ={0}'\u7684\u76AE\u80A4 (\u901A\u8FC7\u540D\u79F0\u5BFC\u5165) +customnpcs.skins.imported_by.player_name={0}''\u7684\u76AE\u80A4 (\u901A\u8FC7\u540D\u79F0\u5BFC\u5165) customnpcs.skins.imported_by.url =\u901A\u8FC7URL\u5BFC\u5165\u7684\u76AE\u80A4 -customnpcs.skins.success.player_name =\u6210\u529F\u5C06NPC\u7684\u76AE\u80A4\u8BBE\u7F6E\u4E3A {0}' \u7684\u76AE\u80A4\! +customnpcs.skins.success.player_name=\u6210\u529F\u5C06NPC\u7684\u76AE\u80A4\u8BBE\u7F6E\u4E3A {0}'' \u7684\u76AE\u80A4\! customnpcs.skins.success.url =\u6210\u529F\u4ECE {0} \u5BFC\u5165NPC\u7684\u76AE\u80A4\! customnpcs.skins.errors.invalid_url =\u89E3\u6790NPC\u76AE\u80A4\u65F6\u53D1\u751F\u9519\u8BEF.\u8FD9\u4E2AURL\u6709\u6548\u5417? customnpcs.skins.errors.unknown_url_error =\u89E3\u6790\u6B64\u76AE\u80A4\u65F6\u53D1\u751F\u9519\u8BEF.\u8BF7\u68C0\u67E5\u63A7\u5236\u53F0\u4EE5\u83B7\u53D6\u8BE6\u7EC6\u4FE1\u606F. customnpcs.skins.errors.no_image_data =\u63D0\u4F9B\u7684URL\u662F \u6709\u6548\u7684, \u4F46\u5B83\u4E0D\u5305\u542B\u4EFB\u4F55\u76AE\u80A4\u6570\u636E.\u62B1\u6B49\! -customnpcs.skins.errors.player_does_not_exist =\u89E3\u6790 {0}'\u7684\u76AE\u80A4\u65F6\u51FA\u9519\! \u8FD9\u4E2A\u73A9\u5BB6\u5B58\u5728\u5417? +customnpcs.skins.errors.player_does_not_exist=\u89E3\u6790 {0}''\u7684\u76AE\u80A4\u65F6\u51FA\u9519\! \u8FD9\u4E2A\u73A9\u5BB6\u5B58\u5728\u5417? customnpcs.skins.changed_with_catalog =\u76AE\u80A4\u66F4\u6539\u4E3A {0} #=====================================# # Data Collection Messages # @@ -180,7 +180,7 @@ customnpcs.directions.north_west =\u897F\u53 customnpcs.directions.player =\u73A9\u5BB6\u65B9\u5411 customnpcs.menus.skin_catalog.title =\u9009\u62E9\u4E00\u4E2A\u76AE\u80A4 # The skin name is {0} -customnpcs.menus.skin_catalog.items.icon.lore ='{0} \u76AE\u80A4' +customnpcs.menus.skin_catalog.items.icon.lore=''{0} \u76AE\u80A4'' customnpcs.menus.delete.title =\u5220\u9664 NPC customnpcs.menus.delete.items.confirm.name =\u5220\u9664 customnpcs.menus.delete.items.confirm.lore =\u8FD9\u65E0\u6CD5\u88AB\u64A4\u6D88\u3002 @@ -358,7 +358,7 @@ customnpcs.menus.extra.hologram_visibility.description =\u4EA4\u4 customnpcs.menus.extra.hologram_visibility.description.hidden =\u9690\u85CF customnpcs.menus.extra.hologram_visibility.description.shown =\u663E\u793A customnpcs.menus.extra.hologram_text =\u66F4\u6539NPC\u70B9\u51FB\u5168\u606F\u56FE\u6587\u672C -customnpcs.menus.extra.hologram_text.description =\u8FD9\u53EA\u4F1A\u66F4\u6539 \u6B64 NPC\u7684\u4EA4\u4E92\u5F0F\u5168\u606F\u56FE.\n \u679C\u60A8\u60F3\u66F4\u6539\u9ED8\u8BA4\u503C\uFF0C\u8BF7\u66F4\u6539config.yml\u4E2D\u7684'ClickText'\u5B57\u6BB5\! +customnpcs.menus.extra.hologram_text.description=\u8FD9\u53EA\u4F1A\u66F4\u6539 \u6B64 NPC\u7684\u4EA4\u4E92\u5F0F\u5168\u606F\u56FE.\n \u679C\u60A8\u60F3\u66F4\u6539\u9ED8\u8BA4\u503C\uFF0C\u8BF7\u66F4\u6539config.yml\u4E2D\u7684''ClickText''\u5B57\u6BB5\! customnpcs.menus.extra.hologram_text.type =\u5728\u804A\u5929\u4E2D\u8F93\u5165\u65B0\u7684\u5168\u606F\u56FE\u6587\u672C\! customnpcs.favicons.teleport =\u4F20\u9001\u73A9\u5BB6 customnpcs.favicons.edit =\u5DE6\u952E\u70B9\u51FB\u4EE5\u7F16\u8F91 @@ -414,7 +414,7 @@ customnpcs.conditions.is_frozen =\u6B63\u5728\u51B customnpcs.conditions.is_gliding =\u6B63\u5728\u6ED1\u7FD4 customnpcs.comparator =\u6BD4\u8F83\u5668 customnpcs.value.select =\u9009\u62E9\u76EE\u6807\u503C -customnpcs.value.current =\u76EE\u6807\u503C\u4E3A '{0}' +customnpcs.value.current=\u76EE\u6807\u503C\u4E3A ''{0}'' customnpcs.statistic =\u7EDF\u8BA1\u6570\u636E customnpcs.menus.skins.title => \u7F16\u8F91 NPC \u76AE\u80A4 customnpcs.menus.skins.player =\u4ECE\u73A9\u5BB6\u5BFC\u5165 @@ -427,9 +427,9 @@ customnpcs.menus.actions.new.title => customnpcs.menus.conditions.title => \u7F16\u8F91\u52A8\u4F5C\u6761\u4EF6 customnpcs.menus.conditions.numeric =\u6570\u503C\u6761\u4EF6 customnpcs.menus.conditions.logical =\u903B\u8F91\u6761\u4EF6 -customnpcs.menus.conditions.comparator =\u6BD4\u8F83\u5668\: '{0}' -customnpcs.menus.conditions.value =\u503C\: '{0}' -customnpcs.menus.conditions.target =\u76EE\u6807\u503C\: '{0}' +customnpcs.menus.conditions.comparator=\u6BD4\u8F83\u5668\: ''{0}'' +customnpcs.menus.conditions.value=\u503C\: ''{0}'' +customnpcs.menus.conditions.target=\u76EE\u6807\u503C\: ''{0}'' customnpcs.menus.conditions.new_condition =\u65B0\u5EFA\u6761\u4EF6 customnpcs.menus.conditions.mode.toggle =\u66F4\u6539\u6A21\u5F0F customnpcs.menus.conditions.mode.all =\u5339\u914D\u6240\u6709\u6761\u4EF6 diff --git a/core/src/main/resources/localization/English_en_US.properties b/core/src/main/resources/localization/English_en_US.properties index fa88b9c1..0c652994 100644 --- a/core/src/main/resources/localization/English_en_US.properties +++ b/core/src/main/resources/localization/English_en_US.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2024-2025. Foxikle +# Copyright (c) 2024-2026. Foxikle # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -19,484 +19,506 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # -customnpcs.commands.no_permission =You do not have permission to do this! +customnpcs.commands.no_permission=You do not have permission to do this! # {0} is the plugin version -customnpcs.commands.header = [!] CustomNPCs {0} [!] -customnpcs.commands.manage.no_npcs =There are no NPCs! -customnpcs.commands.manage.header = [!] Manage NPCs [!] -customnpcs.commands.manage.copy_uuid =Click to copy this NPC\'s UUID. -customnpcs.commands.manage.button.edit =[EDIT] -customnpcs.commands.manage.button.edit.hover =Click to edit this NPC. -customnpcs.commands.manage.button.delete =[DELETE] -customnpcs.commands.manage.button.delete.hover =Click to permanently delete this NPC. -customnpcs.commands.clear_holograms.removed =This command was removed in 1.6.1 because of a rework in NPC hologram names. If you wish to remove any holograms that remain, please use the following command. /kill @e[tag=npcHologram] -customnpcs.commands.reload.start =Reloading NPCs! -customnpcs.commands.reload.end =Successfully reloaded all NPCs! -customnpcs.commands.wiki =Click here to visit the CustomNPCs Wiki! -customnpcs.commands.wiki.hover =Click to visit https://docs.foxikle.dev/ -customnpcs.commands.setsound.unknown_sound =Unrecognised sound, please use tab completions. +customnpcs.commands.header= [!] CustomNPCs {0} [!] +customnpcs.commands.manage.no_npcs=There are no NPCs! +customnpcs.commands.manage.header= [!] Manage NPCs [!] +customnpcs.commands.manage.copy_uuid=Click to copy this NPC''s UUID. +customnpcs.commands.manage.button.edit=[EDIT] +customnpcs.commands.manage.button.edit.hover=Click to edit this NPC. +customnpcs.commands.manage.button.delete=[DELETE] +customnpcs.commands.manage.button.delete.hover=Click to permanently delete this NPC. +customnpcs.commands.clear_holograms.removed=This command was removed in 1.6.1 because of a rework in NPC hologram names. If you wish to remove any holograms that remain, please use the following command. /kill @e[tag=npcHologram] +customnpcs.commands.reload.start=Reloading NPCs! +customnpcs.commands.reload.end=Successfully reloaded all NPCs! +customnpcs.commands.wiki=Click here to visit the CustomNPCs Wiki! +customnpcs.commands.wiki.hover=Click to visit https://docs.foxikle.dev/ +customnpcs.commands.setsound.unknown_sound=Unrecognised sound, please use tab completions. # {0} is the name of the sound -customnpcs.commands.setsound.success =Successfully set sound to be \'{0}\' -customnpcs.commands.setsound.was_not_waiting =Unsuccessfully set NPC sound. I wasn't waiting for a response. Please contact Foxikle if you think this is a mistake. -customnpcs.commands.invalid_uuid =The UUID provided does not match any NPC. -customnpcs.commands.invalid_name_or_uuid =Invalid UUID or Name provided. -customnpcs.commands.clone.success =NPC successfully cloned! -customnpcs.commands.move.nudge =Psst! After moving NPCs, you should reload them! -customnpcs.commands.unknown_command =Unrecognised sub-command. Use /npc help for a list of supported commands. +customnpcs.commands.setsound.success=Successfully set sound to be `{0}` +customnpcs.commands.setsound.was_not_waiting=Unsuccessfully set NPC sound. I wasn''t waiting for a response. Please contact Foxikle if you think this is a mistake. +customnpcs.commands.invalid_uuid=The UUID provided does not match any NPC. +customnpcs.commands.invalid_name_or_uuid=Invalid UUID or Name provided. +customnpcs.commands.clone.success=NPC successfully cloned! +customnpcs.commands.move.nudge=Psst! After moving NPCs, you should reload them! +customnpcs.commands.unknown_command=Unrecognised sub-command. Use /npc help for a list of supported commands. # Help command parts # Syntaxes are gold, aliases are white, hovers are white, descriptions are dark aqua -customnpcs.commands.help.help.syntax =\u2022 npc help -customnpcs.commands.help.help.aliases =npc -customnpcs.commands.help.help.description =Displays this message. -customnpcs.commands.help.help.hover =Displays this message. -customnpcs.commands.help.manage.syntax =\u2022 npc manage -customnpcs.commands.help.manage.aliases =npc list -customnpcs.commands.help.manage.description =Displays the current NPCs -customnpcs.commands.help.manage.hover =Displays the current NPCs with buttons to edit or delete them. -customnpcs.commands.help.create.syntax =\u2022 npc create -customnpcs.commands.help.create.aliases =npc new -customnpcs.commands.help.create.description =Opens the NPC customizer with a new NPC -customnpcs.commands.help.create.hover =Creates a new NPC -customnpcs.commands.help.delete.syntax =\u2022 npc delete -customnpcs.commands.help.delete.aliases =No Aliases -customnpcs.commands.help.delete.description =Permanently deletes the NPC -customnpcs.commands.help.delete.hover =Permanently deletes the NPC, removing it from the npcs.yml file. It also removed it from the world. -customnpcs.commands.help.edit.syntax =\u2022 npc edit -customnpcs.commands.help.edit.aliases =No Aliases -customnpcs.commands.help.edit.description =Edits the specified NPC -customnpcs.commands.help.edit.hover =Brings up the NPC edit menu -customnpcs.commands.help.movehere.syntax =\u2022 npc movehere -customnpcs.commands.help.movehere.aliases =No Aliases -customnpcs.commands.help.movehere.description =Moves the NPC to you. -customnpcs.commands.help.movehere.hover =Moves the NPC to your current location. Its pitch is set to your current pitch. Its yaw is also set to your current yaw. -customnpcs.commands.help.clone.syntax =\u2022 npc clone -customnpcs.commands.help.clone.aliases =No Aliases -customnpcs.commands.help.clone.description =Clones the NPC and moves it to you. -customnpcs.commands.help.clone.hover =Clones the NPC, and moves it to your current location. Its pitch is set to your current pitch. Its yaw is also set to your current yaw. -customnpcs.commands.help.reload.syntax =\u2022 npc reload -customnpcs.commands.help.reload.aliases =No Aliases -customnpcs.commands.help.reload.description =Reloads CustomNPCs -customnpcs.commands.help.reload.hover =Reloads the plugin and config -customnpcs.commands.help.goto.syntax =\u2022 npc goto -customnpcs.commands.help.goto.aliases =No Aliases -customnpcs.commands.help.goto.description =Teleports you to the specified NPC. -customnpcs.commands.help.goto.hover =Teleports you to the specified NPC. -customnpcs.commands.help.wiki.syntax =\u2022 npc wiki -customnpcs.commands.help.wiki.aliases =npc docs -customnpcs.commands.help.wiki.description =Sends a link to the official CustomNPCs Wiki -customnpcs.commands.help.wiki.hover =Gives you access to the Wiki! -customnpcs.commands.debug.message.enable =Debug mode enabled! -customnpcs.commands.debug.message.disable =Debug mode disabled! -customnpcs.commands.help.debug.syntax =\u2022 npc manage -customnpcs.commands.help.debug.aliases =npc debug -customnpcs.commands.help.debug.description =Debugs CustomNPCs -customnpcs.commands.help.debug.hover =Allows CustomNPCs to broadcast debug messages to the logs and authorized players. +customnpcs.commands.help.help.syntax=\u2022 npc help +customnpcs.commands.help.help.aliases=npc +customnpcs.commands.help.help.description=Displays this message. +customnpcs.commands.help.help.hover=Displays this message. +customnpcs.commands.help.manage.syntax=\u2022 npc manage +customnpcs.commands.help.manage.aliases=npc list +customnpcs.commands.help.manage.description=Displays the current NPCs +customnpcs.commands.help.manage.hover=Displays the current NPCs with buttons to edit or delete them. +customnpcs.commands.help.create.syntax=\u2022 npc create +customnpcs.commands.help.create.aliases=npc new +customnpcs.commands.help.create.description=Opens the NPC customizer with a new NPC +customnpcs.commands.help.create.hover=Creates a new NPC +customnpcs.commands.help.delete.syntax=\u2022 npc delete +customnpcs.commands.help.delete.aliases=No Aliases +customnpcs.commands.help.delete.description=Permanently deletes the NPC +customnpcs.commands.help.delete.hover=Permanently deletes the NPC, removing it from the npcs.yml file. It also removed it from the world. +customnpcs.commands.help.edit.syntax=\u2022 npc edit +customnpcs.commands.help.edit.aliases=No Aliases +customnpcs.commands.help.edit.description=Edits the specified NPC +customnpcs.commands.help.edit.hover=Brings up the NPC edit menu +customnpcs.commands.help.movehere.syntax=\u2022 npc movehere +customnpcs.commands.help.movehere.aliases=No Aliases +customnpcs.commands.help.movehere.description=Moves the NPC to you. +customnpcs.commands.help.movehere.hover=Moves the NPC to your current location. Its pitch is set to your current pitch. Its yaw is also set to your current yaw. +customnpcs.commands.help.clone.syntax=\u2022 npc clone +customnpcs.commands.help.clone.aliases=No Aliases +customnpcs.commands.help.clone.description=Clones the NPC and moves it to you. +customnpcs.commands.help.clone.hover=Clones the NPC, and moves it to your current location. Its pitch is set to your current pitch. Its yaw is also set to your current yaw. +customnpcs.commands.help.reload.syntax=\u2022 npc reload +customnpcs.commands.help.reload.aliases=No Aliases +customnpcs.commands.help.reload.description=Reloads CustomNPCs +customnpcs.commands.help.reload.hover=Reloads the plugin and config +customnpcs.commands.help.goto.syntax=\u2022 npc goto +customnpcs.commands.help.goto.aliases=No Aliases +customnpcs.commands.help.goto.description=Teleports you to the specified NPC. +customnpcs.commands.help.goto.hover=Teleports you to the specified NPC. +customnpcs.commands.help.wiki.syntax=\u2022 npc wiki +customnpcs.commands.help.wiki.aliases=npc docs +customnpcs.commands.help.wiki.description=Sends a link to the official CustomNPCs Wiki +customnpcs.commands.help.wiki.hover=Gives you access to the Wiki! +customnpcs.commands.debug.message.enable=Debug mode enabled! +customnpcs.commands.debug.message.disable=Debug mode disabled! +customnpcs.commands.help.debug.syntax=\u2022 npc manage +customnpcs.commands.help.debug.aliases=npc debug +customnpcs.commands.help.debug.description=Debugs CustomNPCs +customnpcs.commands.help.debug.hover=Allows CustomNPCs to broadcast debug messages to the logs and authorized players. #=====================================# # Actions Messages # #=====================================# -customnpcs.actionImpls.set.command =Successfully set command to be \'{0}\' -customnpcs.actionImpls.set.title =Successfully set title to \'{0}\' -customnpcs.actionImpls.set.subtitle =Successfully set title to \'{0}\' -customnpcs.actionImpls.set.message =Successfully set message to \'{0}\' -customnpcs.actionImpls.set.server =Successfully set sever to \'{0}\' -customnpcs.actionImpls.set.actionbar =Successfully set actionbar to \'{0}\' -customnpcs.actionImpls.conditions.set.target =Successfully set target to be \'{0}\' +customnpcs.actionImpls.set.command=Successfully set command to be `{0}` +customnpcs.actionImpls.set.title=Successfully set title to `{0}` +customnpcs.actionImpls.set.subtitle=Successfully set title to `{0}` +customnpcs.actionImpls.set.message=Successfully set message to `{0}` +customnpcs.actionImpls.set.server=Successfully set sever to `{0}` +customnpcs.actionImpls.set.actionbar=Successfully set actionbar to `{0}` +customnpcs.actionImpls.conditions.set.target=Successfully set target to be `{0}` +customnpcs.actionImpls.set.recording=Successfully recorded a path with {0} nodes. #=====================================# # General Messages # #=====================================# -customnpcs.should_update =---------------- [!] CustomNPCs [!] ----------------A new update is available! I'd appreciate if you updated :) ~Foxikle +customnpcs.should_update=---------------- [!] CustomNPCs [!] ----------------A new update is available! I''d appreciate if you updated :) ~Foxikle # {0} is the new name -customnpcs.set.name =Successfully set hologram line {0} to be \'{1}\' +customnpcs.set.name=Successfully set hologram line {0} to be `{1}` # {0} is the input -customnpcs.error.parse_number =Cannot parse the number \'{0}\' Please try again. -customnpcs.error.npc-menu-expired =WHOOPS! It looks like your menu instance expired! You can just reopen the menu to resolve this :) -customnpcs.error.forgot_bailout_response =WHOOPS! We forgot how to handle declining to delete this npc. Don't worry, it was not deleted! +customnpcs.error.parse_number=Cannot parse the number `{0}` Please try again. +customnpcs.error.npc-menu-expired=WHOOPS! It looks like your menu instance expired! You can just reopen the menu to resolve this :) +customnpcs.error.forgot_bailout_response=WHOOPS! We forgot how to handle declining to delete this npc. Don''t worry, it was not deleted! # {0} is the hologram text -customnpcs.set.clickable_hologram =Successfully set the NPCs individual clickable hologram to \'{0}\' -customnpcs.set.facing_direction =Successfully set facing direction! +customnpcs.set.clickable_hologram=Successfully set the NPCs individual clickable hologram to `{0}` +customnpcs.set.facing_direction=Successfully set facing direction! # {0} is the name of NPC -customnpcs.delete.success =Permanently deleted {0}. -customnpcs.delete.failed_api =WHOOPS! The npc was not deleted because a plugin canceled its deletion! -customnpcs.name.reference =For reference, the current NPC Name is: -customnpcs.name.toggle_reference_message =This message can be toggled in the config.yml! -customnpcs.remove.description =Right click to remove -customnpcs.edit.fail =This action cannot be edited! +customnpcs.delete.success=Permanently deleted {0}. +customnpcs.delete.failed_api=WHOOPS! The npc was not deleted because a plugin canceled its deletion! +customnpcs.name.reference=For reference, the current NPC Name is: +customnpcs.name.toggle_reference_message=This message can be toggled in the config.yml! +customnpcs.remove.description=Right click to remove +customnpcs.edit.fail=This action cannot be edited! #=====================================# # Skin Messages # #=====================================# # {0} is the players name -customnpcs.skins.fetching.player =Attempting to fetch {0}\'s skin from Mojang's API. This may take a moment! -customnpcs.skins.fetching.url =Attempting to fetch the skin from a URL. This may take a moment! -customnpcs.skins.imported_by.player_name ={0}\'s skin (Imported Via Name) -customnpcs.skins.imported_by.url =A skin imported via a URL -customnpcs.skins.success.player_name =Successfully set NPC\'s skin to {0}\'s skin! -customnpcs.skins.success.url =Successfully imported NPC\'s skin from {0} -customnpcs.skins.errors.invalid_url =An error occurred whilst parsing NPC skin. Is this URL valid? -customnpcs.skins.errors.unknown_url_error =An error occurred whilst parsing this skin. Check the console for details. -customnpcs.skins.errors.no_image_data =The provided URL was valid, but it doesn't contain any skin data. Sorry! -customnpcs.skins.errors.player_does_not_exist =There was an error parsing {0}\'s skin! Does this player exist? -customnpcs.skins.changed_with_catalog =Skin changed to {0} +customnpcs.skins.fetching.player=Attempting to fetch {0}''s skin from Mojang''s API. This may take a moment! +customnpcs.skins.fetching.url=Attempting to fetch the skin from a URL. This may take a moment! +customnpcs.skins.imported_by.player_name={0}''s skin (Imported Via Name) +customnpcs.skins.imported_by.url=A skin imported via a URL +customnpcs.skins.success.player_name=Successfully set NPC''s skin to {0}''s skin! +customnpcs.skins.success.url=Successfully imported NPC''s skin from {0} +customnpcs.skins.errors.invalid_url=An error occurred whilst parsing NPC skin. Is this URL valid? +customnpcs.skins.errors.unknown_url_error=An error occurred whilst parsing this skin. Check the console for details. +customnpcs.skins.errors.no_image_data=The provided URL was valid, but it doesn''t contain any skin data. Sorry! +customnpcs.skins.errors.player_does_not_exist=There was an error parsing {0}'' skin! Does this player exist? +customnpcs.skins.changed_with_catalog=Skin changed to {0} #=====================================# # Data Collection Messages # #=====================================# -customnpcs.data.actionbar.title =Type the actionbar in chat -customnpcs.data.actionbar.subtitle =Using MiniMessage format -customnpcs.data.command.title =Type the command in chat. -customnpcs.data.command.subtitle =Do NOT include the slash. -customnpcs.data.facing_direction.title =Look where you want the NPC to -customnpcs.data.facing_direction.subtitle =Then type confirm in chat. -customnpcs.data.interactable_hologram.title = Type the hologram text in chat. -customnpcs.data.interactable_hologram.subtitle =Using MiniMessage format -customnpcs.data.message.title = Type the message in chat. -customnpcs.data.message.subtitle = Using MiniMessage format -customnpcs.data.name.title = Type the NPC name in chat. -customnpcs.data.name.subtitle =Using MiniMessage format -customnpcs.data.player_name.title = Type the players name in chat. -customnpcs.data.player_name.subtitle =This is case INsensitive. -customnpcs.data.server.title = Type the name of the server in chat. -customnpcs.data.server.subtitle =Note: It should be EXACTLY what is the bungeecord/Velocity config. -customnpcs.data.sound.title =To set the sound, -customnpcs.data.sound.subtitle =use the /npc setsound command -customnpcs.data.target.title = Type the target value in chat. -customnpcs.data.target.subtitle ="" -customnpcs.data.title.title = Type the title in chat. -customnpcs.data.title.subtitle = Using MiniMessage format. -customnpcs.data.subtitle.title = Type the subtitle in chat. -customnpcs.data.subtitle.subtitle = Using MiniMessage format. -customnpcs.data.url.title =Type the texture URL in chat -customnpcs.data.url.subtitle ="" +customnpcs.data.actionbar.title=Type the actionbar in chat +customnpcs.data.actionbar.subtitle=Using MiniMessage format +customnpcs.data.command.title=Type the command in chat. +customnpcs.data.command.subtitle=Do NOT include the slash. +customnpcs.data.facing_direction.title=Look where you want the NPC to +customnpcs.data.facing_direction.subtitle=Then type confirm in chat. +customnpcs.data.interactable_hologram.title= Type the hologram text in chat. +customnpcs.data.interactable_hologram.subtitle=Using MiniMessage format +customnpcs.data.message.title= Type the message in chat. +customnpcs.data.message.subtitle= Using MiniMessage format +customnpcs.data.name.title= Type the NPC name in chat. +customnpcs.data.name.subtitle=Using MiniMessage format +customnpcs.data.player_name.title= Type the players name in chat. +customnpcs.data.player_name.subtitle=This is case INsensitive. +customnpcs.data.server.title= Type the name of the server in chat. +customnpcs.data.server.subtitle=Note: It should be EXACTLY what is the bungeecord/Velocity config. +customnpcs.data.sound.title=To set the sound, +customnpcs.data.sound.subtitle=use the /npc setsound command +customnpcs.data.target.title= Type the target value in chat. +customnpcs.data.target.subtitle="" +customnpcs.data.title.title= Type the title in chat. +customnpcs.data.title.subtitle= Using MiniMessage format. +customnpcs.data.subtitle.title= Type the subtitle in chat. +customnpcs.data.subtitle.subtitle= Using MiniMessage format. +customnpcs.data.url.title=Type the texture URL in chat +customnpcs.data.url.subtitle="" +customnpcs.data.recording.title=Move to record the path. +customnpcs.data.recording.subtitle=Type ''done'' in chat when finished. #=====================================# # Menu Items Keys # #=====================================# -customnpcs.items.error =Error Loading Equipment -customnpcs.items.error.lore =Check the console for details! -customnpcs.items.click_to_change =Click to change! -customnpcs.items.click_to_select =Click to select! -customnpcs.items.next_page =Next Page -customnpcs.items.prev_page =Previous Page -customnpcs.items.go_back =Go Back -customnpcs.menus.skin_catalog.title =Select A Skin +customnpcs.items.error=Error Loading Equipment +customnpcs.items.error.lore=Check the console for details! +customnpcs.items.click_to_change=Click to change! +customnpcs.items.click_to_select=Click to select! +customnpcs.items.next_page=Next Page +customnpcs.items.prev_page=Previous Page +customnpcs.items.go_back=Go Back +customnpcs.menus.skin_catalog.title=Select A Skin # The skin name is {0} -customnpcs.menus.skin_catalog.items.icon.lore ='The {0} Skin' -customnpcs.menus.delete.title =Delete an NPC -customnpcs.menus.delete.items.confirm.name =DELETE -customnpcs.menus.delete.items.confirm.lore =This CANNOT be undone. -customnpcs.menus.delete.items.to_safety =Back to safety! +customnpcs.menus.skin_catalog.items.icon.lore=The {0} Skin +customnpcs.menus.delete.title=Delete an NPC +customnpcs.menus.delete.items.confirm.name=DELETE +customnpcs.menus.delete.items.confirm.lore=This CANNOT be undone. +customnpcs.menus.delete.items.to_safety=Back to safety! # # # # MAIN MENU # # -customnpcs.menus.main.title => Create a New NPC -customnpcs.error.cant_open_skin_catalog =WHOOPS! An error occurred whilst opening the skin catalog! Check the server console for details and report it! -customnpcs.menus.main.error.no_npc =WHOOPS! An error occurred! -customnpcs.menus.main.error.no_npc.lore =Failed to find your cached NPC! This may be as a result of inactivity in order to save server resources. If it is NOT the case, please report this! -customnpcs.menus.main.items.name.name =Change Hologram Lines +customnpcs.menus.main.title=> Create a New NPC +customnpcs.error.cant_open_skin_catalog=WHOOPS! An error occurred whilst opening the skin catalog! Check the server console for details and report it! +customnpcs.menus.main.error.no_npc=WHOOPS! An error occurred! +customnpcs.menus.main.error.no_npc.lore=Failed to find your cached NPC! This may be as a result of inactivity in order to save server resources. If it is NOT the case, please report this! +customnpcs.menus.main.items.name.name=Change Hologram Lines # {0} is the name of the NPC -customnpcs.menus.main.items.name.current_name =The current hologram lines are: {0} -customnpcs.menus.main.items.equipment.name =Change Items -customnpcs.menus.main.items.equipment.main_hand =Main Hand: {0} -customnpcs.menus.main.items.equipment.off_hand =Offhand: {0} -customnpcs.menus.main.items.equipment.helmet =Helmet: {0} -customnpcs.menus.main.items.equipment.chestplate =Chestplate: {0} -customnpcs.menus.main.items.equipment.leggings =Leggings: {0} -customnpcs.menus.main.items.equipment.boots =Boots: {0} -customnpcs.menus.main.items.skin.name =Change Skin +customnpcs.menus.main.items.name.current_name=The current hologram lines are: {0} +customnpcs.menus.main.items.equipment.name=Change Items +customnpcs.menus.main.items.equipment.main_hand=Main Hand: {0} +customnpcs.menus.main.items.equipment.off_hand=Offhand: {0} +customnpcs.menus.main.items.equipment.helmet=Helmet: {0} +customnpcs.menus.main.items.equipment.chestplate=Chestplate: {0} +customnpcs.menus.main.items.equipment.leggings=Leggings: {0} +customnpcs.menus.main.items.equipment.boots=Boots: {0} +customnpcs.menus.main.items.skin.name=Change Skin # {0} is the name of the skin -customnpcs.menus.main.items.skin.lore =Changes the NPC's skin. The current skin is: {0} -customnpcs.menus.main.resilient.message.now_true =The NPC is now RESILIENT -customnpcs.menus.main.resilient.message.now_false =The NPC is now NOT RESILIENT -customnpcs.menus.main.items.resilient.true =RESILIENT -customnpcs.menus.main.items.resilient.false =NOT RESILIENT -customnpcs.menus.main.items.resilient.change =Change resilience -customnpcs.menus.main.interactable.message.now_true =The NPC is now INTERACTABLE -customnpcs.menus.main.interactable.message.now_false =The NPC is now NOT INTERACTABLE -customnpcs.menus.main.interactable.name =Change Actions -customnpcs.menus.main.interactable.name.toggle =Toggle Actions -customnpcs.menus.main.interactable.description =The actions performed when interacting with the npc. -customnpcs.menus.main.interactable.true =INTERACTABLE -customnpcs.menus.main.interactable.false =NOT INTERACTABLE -customnpcs.menus.main.create.name =CONFIRM -customnpcs.menus.main.create.message.temporary =Temporary NPC created! -customnpcs.menus.main.create.message.resilient =Resilient NPC created! -customnpcs.menus.main.cancel.name =CANCEL -customnpcs.menus.main.cancel.message =NPC aborted. -customnpcs.menus.main.vision.name =Toggle Vision Mode -customnpcs.menus.main.vision.tunnel =TUNNEL Visioned -customnpcs.menus.main.vision.normal =NORMAL Visioned -customnpcs.menus.main.facing.name =Set Facing Direction -customnpcs.menus.main.facing.description =This option configures the NPC's Yaw and Pitch. This sets the NPC's facing direction to 'Player Direction' +customnpcs.menus.main.items.skin.lore=Changes the NPC''s skin. The current skin is {0} +customnpcs.menus.main.resilient.message.now_true=The NPC is now RESILIENT +customnpcs.menus.main.resilient.message.now_false=The NPC is now NOT RESILIENT +customnpcs.menus.main.items.resilient.true=RESILIENT +customnpcs.menus.main.items.resilient.false=NOT RESILIENT +customnpcs.menus.main.items.resilient.change=Change resilience +customnpcs.menus.main.interactable.message.now_true=The NPC is now INTERACTABLE +customnpcs.menus.main.interactable.message.now_false=The NPC is now NOT INTERACTABLE +customnpcs.menus.main.interactable.name=Change Actions +customnpcs.menus.main.interactable.name.toggle=Toggle Actions +customnpcs.menus.main.interactable.description=The actions performed when interacting with the npc. +customnpcs.menus.main.interactable.true=INTERACTABLE +customnpcs.menus.main.interactable.false=NOT INTERACTABLE +customnpcs.menus.main.create.name=CONFIRM +customnpcs.menus.main.create.message.temporary=Temporary NPC created! +customnpcs.menus.main.create.message.resilient=Resilient NPC created! +customnpcs.menus.main.cancel.name=CANCEL +customnpcs.menus.main.cancel.message=NPC aborted. +customnpcs.menus.main.vision.name=Toggle Vision Mode +customnpcs.menus.main.vision.tunnel=TUNNEL Visioned +customnpcs.menus.main.vision.normal=NORMAL Visioned +customnpcs.menus.main.facing.name=Set Facing Direction +customnpcs.menus.main.facing.description=This option configures the NPC''s Yaw and Pitch. This sets the NPC''s facing direction to ''Player Direction'' # Random one off things -customnpcs.menus.main.settings.name =Edit Additional Settings -customnpcs.menus.main.delete.name =Delete NPC +customnpcs.menus.main.settings.name=Edit Additional Settings +customnpcs.menus.main.delete.name=Delete NPC # # # # EQUIPMENT MENU # # -customnpcs.menus.equipment.title => Edit NPC Equipment -customnpcs.menus.equipment.helmet.empty =Empty Helmet Slot -customnpcs.menus.equipment.helmet.change =Click this slot with an item to change it. -customnpcs.menus.equipment.helmet.message.success =Successfully set helmet slot to {0} -customnpcs.menus.equipment.helmet.reset =Successfully reset helmet slot -customnpcs.menus.equipment.chestplate.empty =Empty Chestplate Slot -customnpcs.menus.equipment.chestplate.change =Click this slot with a chestplate to change it. -customnpcs.menus.equipment.chestplate.message.success =Successfully set chestplate slot to {0} -customnpcs.menus.equipment.chestplate.reset =Successfully reset chestplate slot -customnpcs.menus.equipment.chestplate.message.error =That is not a chestplate! -customnpcs.menus.equipment.legs.empty =Empty Leggings Slot -customnpcs.menus.equipment.legs.change =Click this slot with a leggings to change it. -customnpcs.menus.equipment.legs.message.success =Successfully set leggings slot to {0} -customnpcs.menus.equipment.legs.reset =Successfully reset leggings slot -customnpcs.menus.equipment.legs.message.error =That is not a pair of leggings! -customnpcs.menus.equipment.boots.empty =Empty Boots Slot -customnpcs.menus.equipment.boots.change =Click this slot with a boots to change it. -customnpcs.menus.equipment.boots.message.success =Successfully set boots slot to {0} -customnpcs.menus.equipment.boots.reset =Successfully reset boots slot -customnpcs.menus.equipment.boots.message.error =That is not a pair of boots! -customnpcs.menus.equipment.hand.empty =Empty Hand Slot -customnpcs.menus.equipment.hand.change =Click this slot with an item to change it. -customnpcs.menus.equipment.hand.message.success =Successfully set hand slot to {0} -customnpcs.menus.equipment.hand.reset =Successfully reset hand slot -customnpcs.menus.equipment.offhand.empty =Empty Offhand Slot -customnpcs.menus.equipment.offhand.change =Click this slot with an item to change it. -customnpcs.menus.equipment.offhand.message.success =Successfully set offhand slot to {0} -customnpcs.menus.equipment.offhand.reset =Successfully reset offhand slot -customnpcs.menus.equipment.import =Import Player Equipment -customnpcs.menus.equipment.import.description =Imports your current armor to the NPC +customnpcs.menus.equipment.title=> Edit NPC Equipment +customnpcs.menus.equipment.helmet.empty=Empty Helmet Slot +customnpcs.menus.equipment.helmet.change=Click this slot with an item to change it. +customnpcs.menus.equipment.helmet.message.success=Successfully set helmet slot to {0} +customnpcs.menus.equipment.helmet.reset=Successfully reset helmet slot +customnpcs.menus.equipment.chestplate.empty=Empty Chestplate Slot +customnpcs.menus.equipment.chestplate.change=Click this slot with a chestplate to change it. +customnpcs.menus.equipment.chestplate.message.success=Successfully set chestplate slot to {0} +customnpcs.menus.equipment.chestplate.reset=Successfully reset chestplate slot +customnpcs.menus.equipment.chestplate.message.error=That is not a chestplate! +customnpcs.menus.equipment.legs.empty=Empty Leggings Slot +customnpcs.menus.equipment.legs.change=Click this slot with a leggings to change it. +customnpcs.menus.equipment.legs.message.success=Successfully set leggings slot to {0} +customnpcs.menus.equipment.legs.reset=Successfully reset leggings slot +customnpcs.menus.equipment.legs.message.error=That is not a pair of leggings! +customnpcs.menus.equipment.boots.empty=Empty Boots Slot +customnpcs.menus.equipment.boots.change=Click this slot with a boots to change it. +customnpcs.menus.equipment.boots.message.success=Successfully set boots slot to {0} +customnpcs.menus.equipment.boots.reset=Successfully reset boots slot +customnpcs.menus.equipment.boots.message.error=That is not a pair of boots! +customnpcs.menus.equipment.hand.empty=Empty Hand Slot +customnpcs.menus.equipment.hand.change=Click this slot with an item to change it. +customnpcs.menus.equipment.hand.message.success=Successfully set hand slot to {0} +customnpcs.menus.equipment.hand.reset=Successfully reset hand slot +customnpcs.menus.equipment.offhand.empty=Empty Offhand Slot +customnpcs.menus.equipment.offhand.change=Click this slot with an item to change it. +customnpcs.menus.equipment.offhand.message.success=Successfully set offhand slot to {0} +customnpcs.menus.equipment.offhand.reset=Successfully reset offhand slot +customnpcs.menus.equipment.import=Import Player Equipment +customnpcs.menus.equipment.import.description=Imports your current armor to the NPC # # # Action Customizer Menu # # -customnpcs.menus.action_customizer.title => Edit NPC Action +customnpcs.menus.action_customizer.title=> Edit NPC Action customnpcs.menus.action_customizer.delay.decrement.description=Left Click to remove 1\n Right Click to remove 5\n Shift + Click to remove 20 -customnpcs.menus.action_customizer.delay.decrement =Decrement Delay +customnpcs.menus.action_customizer.delay.decrement=Decrement Delay customnpcs.menus.action_customizer.delay.increment.description=Left Click to add 1\n Right Click to add 5\n Shift + Click to add 20 -customnpcs.menus.action_customizer.delay.increment =Increment Delay -customnpcs.menus.action_customizer.delay.error =The delay cannot be negative! -customnpcs.menus.action_customizer.delay.name =Delay Ticks: {0} +customnpcs.menus.action_customizer.delay.increment=Increment Delay +customnpcs.menus.action_customizer.delay.error=The delay cannot be negative! +customnpcs.menus.action_customizer.delay.name=Delay Ticks: {0} customnpcs.menus.action_customizer.cooldown.decrement.description=Left Click to remove 1\n Right Click to remove 5\n Shift + Click to remove 20 customnpcs.menus.action_customizer.cooldown.decrement=Decrement Cooldown customnpcs.menus.action_customizer.cooldown.increment.description=Left Click to add 1\n Right Click to add 5\n Shift + Click to add 20 customnpcs.menus.action_customizer.cooldown.increment=Increment Cooldown customnpcs.menus.action_customizer.cooldown.error=The cooldown cannot be negative! customnpcs.menus.action_customizer.cooldown.name=Cooldown Ticks: {0} - -customnpcs.menus.action_customizer.conditions =Edit Conditions -customnpcs.menus.action_customizer.confirm =Save Changes -customnpcs.menus.action.command.as_console.true =AS CONSOLE! -customnpcs.menus.action.command.as_console.false =AS PLAYER! -customnpcs.menus.action.command.as_console.warning =\n\nWARNING! Running commands as the console can be incredibly dangerous. Please exercise caution when using this. -customnpcs.commands.fix_config.usage =Usage: /npc fixconfig [args] -customnpcs.commands.fix_config.world.usage =Usage: /npc fixconfig world [flags] (-target all | -target | -world ) -customnpcs.commands.fix_config.report = [!] Config Fix Report [!] Successfully fixed {0} NPC configuration(s)Adjusted {1} NPC configuration(s) according to strategiesFailed to fix {2} NPC configuration(s)Failed to find {3} NPC(s)! -customnpcs.commands.fix_config.bukkit_wiped_data =<#ff6600>WARNING! Some NPCs location data was wiped by the Bukkit Configuration API. Those NPCs have had their location set to (0,0,0). -customnpcs.menus.action.title.fade_in.increase =Increase fade in duration -customnpcs.menus.action.title.fade_in.decrease =Decrease fade in duration -customnpcs.menus.action.title.stay.increase =Increase fade in duration -customnpcs.menus.action.title.stay.decrease =Decrease fade in duration -customnpcs.menus.action.title.fade_out.increase =Increase fade in duration -customnpcs.menus.action.title.fade_out.decrease =Decrease fade in duration -customnpcs.menus.action.title.duration_less_than_1 =The duration cannot be less than 1! -customnpcs.menus.action.title.display.lore =In ticks -customnpcs.menus.action.title.display.fade_in =Fade in: {0} -customnpcs.menus.action.title.display.stay =Stay: {0} -customnpcs.menus.action.title.display.fade_out =Fade Out: {0} -customnpcs.menus.action.title.current.title =Current Title -customnpcs.menus.action.title.current.subtitle =Current Subtitle -customnpcs.menus.action.teleport.increase_x =Increase X Coordinate -customnpcs.menus.action.teleport.increase_y =Increase Y Coordinate -customnpcs.menus.action.teleport.increase_z =Increase Z Coordinate -customnpcs.menus.action.teleport.increase_pitch =Increase Pitch -customnpcs.menus.action.teleport.increase_yaw =Increase Yaw -customnpcs.menus.action.teleport.yaw_over_180 =The yaw cannot be greater than 180! -customnpcs.menus.action.teleport.pitch_over_90 =The pitch cannot be greater than 90! -customnpcs.menus.action.teleport.decrease_x =Decrease X Coordinate -customnpcs.menus.action.teleport.decrease_y =Decrease Y Coordinate -customnpcs.menus.action.teleport.decrease_z =Decrease Z Coordinate -customnpcs.menus.action.teleport.decrease_pitch =Decrease Pitch -customnpcs.menus.action.teleport.decrease_yaw =Decrease Yaw -customnpcs.menus.action.teleport.yaw_under_180 =The yaw cannot be less than -180! -customnpcs.menus.action.teleport.pitch_under_90 =The pitch cannot be less than -90! -customnpcs.menus.action.teleport.in_blocks =In blocks -customnpcs.menus.action.teleport.display.x =X: {0} -customnpcs.menus.action.teleport.display.y =Y: {0} -customnpcs.menus.action.teleport.display.z =Z: {0} -customnpcs.menus.action.teleport.display.yaw =Yaw: {0} -customnpcs.menus.action.teleport.display.pitch =Pitch: {0} -customnpcs.menus.action.sound.increase =Click to add .1 -customnpcs.menus.action.sound.decrease =Click to remove .1 -customnpcs.menus.action.sound.increase_pitch =Increase Pitch -customnpcs.menus.action.sound.increase_volume =Increase Volume -customnpcs.menus.action.sound.decrease_pitch =Decrease Pitch -customnpcs.menus.action.sound.decrease_volume =Decrease Volume -customnpcs.menus.action.sound.invalid_pitch =The pitch cannot be less than or equal 0! -customnpcs.menus.action.sound.invalid_volume =The volume cannot be less than or equal 0! -customnpcs.menus.action.sound.pitch =Pitch: {0} -customnpcs.menus.action.sound.volume =Volume: {0} -customnpcs.menus.action.sound.sound =Sound: {0} -customnpcs.menus.action.give_xp.increase =Increase XP -customnpcs.menus.action.give_xp.decrease =Increase XP -customnpcs.menus.action.give_xp.xp =XP to give: {0} -customnpcs.menus.action.give_xp.xp_less_one =The XP cannot be less than one! -customnpcs.menus.action.give_xp.awarding =Awarding: {0} -customnpcs.menus.action.xp.levels =Levels -customnpcs.menus.action.xp.points =Points -customnpcs.menus.action.remove_xp.xp =XP to take: {0} -customnpcs.menus.action.remove_xp.awarding =Taking: {0} -customnpcs.menus.action.give_effect.duration.increase =Increase effect duration -customnpcs.menus.action.give_effect.amplifier.increase =Increase effect amplifier -customnpcs.menus.action.give_effect.amplifier_over_255 =The amplifier cannot be greater than 255! -customnpcs.menus.action.give_effect.duration.decrease =Decrease effect duration -customnpcs.menus.action.give_effect.amplifier.decrease =Decrease effect amplifier -customnpcs.menus.action.give_effect.duration_under_1 =The duration cannot be less than one! -customnpcs.menus.action.give_effect.amplifier_under_0 =The amplifier cannot be less than zero! -customnpcs.menus.action.give_effect.duration =Duration: {0} -customnpcs.menus.action.give_effect.amplifier =Amplifier: {0} -customnpcs.menus.action.give_effect.particles =Hide Particles: {0} -customnpcs.menus.action.give_effect.effect =Effect to give -customnpcs.menus.action.remove_effect.effect =Effect to remove -customnpcs.actions.already_has =This NPC already has this action, and it cannot be duplicated! -customnpcs.menus.extra.title => Extra NPC Settings -customnpcs.menus.extra.hologram_visibility =Toggle Interactable Hologram Visibility -customnpcs.menus.extra.hologram_visibility.description =The Interactable Holograms is: -customnpcs.menus.extra.hologram_visibility.description.hidden =HIDDEN -customnpcs.menus.extra.hologram_visibility.description.shown =SHOWN +customnpcs.menus.action_customizer.conditions=Edit Conditions +customnpcs.menus.action_customizer.confirm=Save Changes +customnpcs.menus.action.command.as_console.true=AS CONSOLE! +customnpcs.menus.action.command.as_console.false=AS PLAYER! +customnpcs.menus.action.command.as_console.warning=\n\nWARNING! Running commands as the console can be incredibly dangerous. Please exercise caution when using this. +customnpcs.menus.action.command.papi_tip.title=TIP! You can use PlaceholderAPI! +customnpcs.menus.action.command.papi_tip.download.plugin=PlaceholderAPI! You can download PlaceholderAPI [HERE] +customnpcs.menus.action.command.papi_tip.download.expansion=PlaceholderAPI! You can download the PlaceholderAPI Player expansion [HERE] +customnpcs.menus.action.command.papi_tip.no_papi=You can use PlaceholderAPI to modify the command string to run privileged commands for the player.Click to Download +customnpcs.menus.action.command.papi_tip.no_expansion=You can use PlaceholderAPI''s Player Expansion in the command string to run privileged commands for the player. It does require an expansion. Click to Download +customnpcs.menus.action.command.papi_tip.all_good=You can use %player_name% or %player_uuid% to replace a generic value in the command. For example, ''/kick %player_name%''. You probably want to run these as console. +customnpcs.commands.fix_config.usage=Usage: /npc fixconfig [args] +customnpcs.commands.fix_config.world.usage=Usage: /npc fixconfig world [flags] (-target all | -target | -world ) +customnpcs.commands.fix_config.report= [!] Config Fix Report [!] Successfully fixed {0} NPC configuration(s)Adjusted {1} NPC configuration(s) according to strategiesFailed to fix {2} NPC configuration(s)Failed to find {3} NPC(s)! +customnpcs.commands.fix_config.bukkit_wiped_data=<#ff6600>WARNING! Some NPCs location data was wiped by the Bukkit Configuration API. Those NPCs have had their location set to (0,0,0). +customnpcs.menus.action.title.fade_in.increase=Increase fade in duration +customnpcs.menus.action.title.fade_in.decrease=Decrease fade in duration +customnpcs.menus.action.title.stay.increase=Increase fade in duration +customnpcs.menus.action.title.stay.decrease=Decrease fade in duration +customnpcs.menus.action.title.fade_out.increase=Increase fade in duration +customnpcs.menus.action.title.fade_out.decrease=Decrease fade in duration +customnpcs.menus.action.title.duration_less_than_1=The duration cannot be less than 1! +customnpcs.menus.action.title.display.lore=In ticks +customnpcs.menus.action.title.display.fade_in=Fade in: {0} +customnpcs.menus.action.title.display.stay=Stay: {0} +customnpcs.menus.action.title.display.fade_out=Fade Out: {0} +customnpcs.menus.action.title.current.title=Current Title +customnpcs.menus.action.title.current.subtitle=Current Subtitle +customnpcs.menus.action.teleport.increase_x=Increase X Coordinate +customnpcs.menus.action.teleport.increase_y=Increase Y Coordinate +customnpcs.menus.action.teleport.increase_z=Increase Z Coordinate +customnpcs.menus.action.teleport.increase_pitch=Increase Pitch +customnpcs.menus.action.teleport.increase_yaw=Increase Yaw +customnpcs.menus.action.teleport.yaw_over_180=The yaw cannot be greater than 180! +customnpcs.menus.action.teleport.pitch_over_90=The pitch cannot be greater than 90! +customnpcs.menus.action.teleport.decrease_x=Decrease X Coordinate +customnpcs.menus.action.teleport.decrease_y=Decrease Y Coordinate +customnpcs.menus.action.teleport.decrease_z=Decrease Z Coordinate +customnpcs.menus.action.teleport.decrease_pitch=Decrease Pitch +customnpcs.menus.action.teleport.decrease_yaw=Decrease Yaw +customnpcs.menus.action.teleport.yaw_under_180=The yaw cannot be less than -180! +customnpcs.menus.action.teleport.pitch_under_90=The pitch cannot be less than -90! +customnpcs.menus.action.teleport.in_blocks=In blocks +customnpcs.menus.action.teleport.display.x=X: {0} +customnpcs.menus.action.teleport.display.y=Y: {0} +customnpcs.menus.action.teleport.display.z=Z: {0} +customnpcs.menus.action.teleport.display.yaw=Yaw: {0} +customnpcs.menus.action.teleport.display.pitch=Pitch: {0} +customnpcs.menus.action.sound.increase=Click to add .1 +customnpcs.menus.action.sound.decrease=Click to remove .1 +customnpcs.menus.action.sound.increase_pitch=Increase Pitch +customnpcs.menus.action.sound.increase_volume=Increase Volume +customnpcs.menus.action.sound.decrease_pitch=Decrease Pitch +customnpcs.menus.action.sound.decrease_volume=Decrease Volume +customnpcs.menus.action.sound.invalid_pitch=The pitch cannot be less than or equal 0! +customnpcs.menus.action.sound.invalid_volume=The volume cannot be less than or equal 0! +customnpcs.menus.action.sound.pitch=Pitch: {0} +customnpcs.menus.action.sound.volume=Volume: {0} +customnpcs.menus.action.sound.sound=Sound: {0} +customnpcs.menus.action.give_xp.increase=Increase XP +customnpcs.menus.action.give_xp.decrease=Increase XP +customnpcs.menus.action.give_xp.xp=XP to give: {0} +customnpcs.menus.action.give_xp.xp_less_one=The XP cannot be less than one! +customnpcs.menus.action.give_xp.awarding=Awarding: {0} +customnpcs.menus.action.xp.levels=Levels +customnpcs.menus.action.xp.points=Points +customnpcs.menus.action.remove_xp.xp=XP to take: {0} +customnpcs.menus.action.remove_xp.awarding=Taking: {0} +customnpcs.menus.action.give_effect.duration.increase=Increase effect duration +customnpcs.menus.action.give_effect.amplifier.increase=Increase effect amplifier +customnpcs.menus.action.give_effect.amplifier_over_255=The amplifier cannot be greater than 255! +customnpcs.menus.action.give_effect.duration.decrease=Decrease effect duration +customnpcs.menus.action.give_effect.amplifier.decrease=Decrease effect amplifier +customnpcs.menus.action.give_effect.duration_under_1=The duration cannot be less than one! +customnpcs.menus.action.give_effect.amplifier_under_0=The amplifier cannot be less than zero! +customnpcs.menus.action.give_effect.duration=Duration: {0} +customnpcs.menus.action.give_effect.amplifier=Amplifier: {0} +customnpcs.menus.action.give_effect.particles=Hide Particles: {0} +customnpcs.menus.action.give_effect.effect=Effect to give +customnpcs.menus.action.remove_effect.effect=Effect to remove +customnpcs.actions.already_has=This NPC already has this action, and it cannot be duplicated! +customnpcs.menus.extra.title=> Extra NPC Settings +customnpcs.menus.extra.hologram_visibility=Toggle Interactable Hologram Visibility +customnpcs.menus.extra.hologram_visibility.description=The Interactable Holograms is: +customnpcs.menus.extra.hologram_visibility.description.hidden=HIDDEN +customnpcs.menus.extra.hologram_visibility.description.shown=SHOWN customnpcs.menus.extra.upside_down=Toggle model mirroring customnpcs.menus.extra.upside_down.description=The NPC is currently: customnpcs.menus.extra.upside_down.description.false=RIGHT SIDE UP customnpcs.menus.extra.upside_down.description.true=UPSIDE DOWN\n\nYou may need to run /npc reload after changing this for it to take effect! -customnpcs.menus.extra.hologram_text =Change NPC Clickable Hologram Text -customnpcs.menus.extra.hologram_text.description =This changes only THIS NPC's interactable hologram.\n If you would like to change the default, change the 'ClickText' field in the config.yml! -customnpcs.menus.extra.hologram_text.type =Type the new hologram text in chat! -customnpcs.favicons.teleport =Teleport Player -customnpcs.favicons.edit =Left click to edit -customnpcs.favicons.remove =Right click to remove -customnpcs.favicons.delay =Delay: {0} -customnpcs.favicons.preview =Preview: -customnpcs.favicons.actionbar =Send Actionbar -customnpcs.favicons.title =Send Title -customnpcs.favicons.remove_effect =Remove Effect -customnpcs.favicons.give_effect =Give Effect -customnpcs.favicons.give_effect.effect =Effect: {0} -customnpcs.favicons.give_effect.duration =Duration: {0} -customnpcs.favicons.give_effect.amplifier =Amplifier: {0} -customnpcs.favicons.give_effect.particles =Hide Particles: {0} -customnpcs.favicons.give_xp =Give XP -customnpcs.favicons.remove_xp =Remove XP -customnpcs.favicons.command =Run command -customnpcs.favicons.command.syntax =Syntax: /{0} -customnpcs.favicons.command.as_console =As Console: {0} -customnpcs.favicons.message =Send Message -customnpcs.favicons.server =Send To Server -customnpcs.favicons.server.target =Target Server: {0} -customnpcs.favicons.sound =Play Sound -customnpcs.favicons.actionbar.description =Sends the player an actionbar. -customnpcs.favicons.title.description =Displays a title for the player. -customnpcs.favicons.remove_effect.description =Removes an effect from the player. -customnpcs.favicons.give_effect.description =Gives an effect to the player. -customnpcs.favicons.give_xp.description =Gives the player XP. -customnpcs.favicons.remove_xp.description =Removes XP from the player. -customnpcs.favicons.command.description =Runs a command either as the player or console. -customnpcs.favicons.message.description =Sends the player a message. -customnpcs.favicons.server.description =Sends a player to a Bungeecord/Velocity server. -customnpcs.favicons.sound.description =Plays a sound for the player. -customnpcs.favicons.teleport.description =Teleports the player. -customnpcs.menus.actions.title => Edit NPC Actions -customnpcs.menus.actions.new =New Action -customnpcs.menus.condition_customizer.title => Edit Action Condition -customnpcs.conditions.xp_levels =XP Levels -customnpcs.conditions.xp_points =XP Points -customnpcs.conditions.health =Health -customnpcs.conditions.absorption =Absorption -customnpcs.conditions.y_coord =Y Coordinate -customnpcs.conditions.x_coord =X Coordinate -customnpcs.conditions.z_coord =Z Coordinate -customnpcs.conditions.gamemode =Gamemode +customnpcs.menus.extra.hologram_text=Change NPC Clickable Hologram Text +customnpcs.menus.extra.hologram_text.description=This changes only THIS NPC''s interactable hologram.\n If you would like to change the default, change the ''ClickText'' field in the config.yml! +customnpcs.menus.extra.hologram_text.type=Type the new hologram text in chat! +customnpcs.favicons.teleport=Teleport Player +customnpcs.favicons.edit=Left click to edit +customnpcs.favicons.remove=Right click to remove +customnpcs.favicons.delay=Delay: {0} +customnpcs.favicons.preview=Preview: +customnpcs.favicons.actionbar=Send Actionbar +customnpcs.favicons.title=Send Title +customnpcs.menus.action.follow_path.title=> Follow Preset Path +customnpcs.menus.action.follow_path.favicon=Follow Preset Path +customnpcs.menus.action.follow_path.description=Replays a recorded movement path. +customnpcs.menus.action.follow_path.record=Start Recording +customnpcs.menus.action.follow_path.record.lore=Click to start recording your movement. +customnpcs.menus.action.follow_path.rerecord=Start Recording +customnpcs.menus.action.follow_path.rerecord.lore=Your path has {0} nodes. Click to record it again. +customnpcs.menus.action.follow_path.nodes=Path Nodes: {0} +customnpcs.menus.action.follow_path.looped=Looped: {0} +customnpcs.menus.action.follow_path.loop.true=Looped Path +customnpcs.menus.action.follow_path.loop.false=Standard Path +customnpcs.menus.action.follow_path.loop.true.description=The NPC will walk this path forever, teleporting to the first node after finishing the path. Once started, this action cannot be run again! +customnpcs.menus.action.follow_path.loop.false.description=The NPC will walk this path once per interaction. + +customnpcs.favicons.remove_effect=Remove Effect +customnpcs.favicons.give_effect=Give Effect +customnpcs.favicons.give_effect.effect=Effect: {0} +customnpcs.favicons.give_effect.duration=Duration: {0} +customnpcs.favicons.give_effect.amplifier=Amplifier: {0} +customnpcs.favicons.give_effect.particles=Hide Particles: {0} +customnpcs.favicons.give_xp=Give XP +customnpcs.favicons.remove_xp=Remove XP +customnpcs.favicons.command=Run command +customnpcs.favicons.command.syntax=Syntax: /{0} +customnpcs.favicons.command.as_console=As Console: {0} +customnpcs.favicons.message=Send Message +customnpcs.favicons.server=Send To Server +customnpcs.favicons.server.target=Target Server: {0} +customnpcs.favicons.sound=Play Sound +customnpcs.favicons.actionbar.description=Sends the player an actionbar. +customnpcs.favicons.title.description=Displays a title for the player. +customnpcs.favicons.remove_effect.description=Removes an effect from the player. +customnpcs.favicons.give_effect.description=Gives an effect to the player. +customnpcs.favicons.give_xp.description=Gives the player XP. +customnpcs.favicons.remove_xp.description=Removes XP from the player. +customnpcs.favicons.command.description=Runs a command either as the player or console. +customnpcs.favicons.message.description=Sends the player a message. +customnpcs.favicons.server.description=Sends a player to a Bungeecord/Velocity server. +customnpcs.favicons.sound.description=Plays a sound for the player. +customnpcs.favicons.teleport.description=Teleports the player. +customnpcs.menus.actions.title=> Edit NPC Actions +customnpcs.menus.actions.new=New Action +customnpcs.menus.condition_customizer.title=> Edit Action Condition +customnpcs.conditions.xp_levels=XP Levels +customnpcs.conditions.xp_points=XP Points +customnpcs.conditions.health=Health +customnpcs.conditions.absorption=Absorption +customnpcs.conditions.y_coord=Y Coordinate +customnpcs.conditions.x_coord=X Coordinate +customnpcs.conditions.z_coord=Z Coordinate +customnpcs.conditions.gamemode=Gamemode # These are boolean conditions -customnpcs.conditions.has_effect =Has Effect -customnpcs.conditions.has_permission =Has Permission -customnpcs.conditions.is_flying =Is Flying -customnpcs.conditions.is_sprinting =Is Sprinting -customnpcs.conditions.is_sneaking =Is Sneaking -customnpcs.conditions.is_frozen =Is Frozen -customnpcs.conditions.is_gliding =Is Gliding -customnpcs.comparator =Comparator -customnpcs.value.select =Select Target Value -customnpcs.value.current =The target value is '{0}' -customnpcs.statistic =Statistic -customnpcs.menus.skins.title => Edit NPC Skin -customnpcs.menus.skins.player =Import from Player -customnpcs.menus.skins.player.description =Fetches a player's skin by name. \nThis may fail randomly on 3rd party hosts, as it makes requests to Mojang's API. -customnpcs.menus.skins.catalog =Browse Skin Catalogue -customnpcs.menus.skins.catalog.description =Use a preset skin -customnpcs.menus.skins.url =Import from URL -customnpcs.menus.skins.url.description =Fetches a skin from a URL -customnpcs.menus.actions.new.title => New NPC Action -customnpcs.menus.conditions.title => Edit Action Conditions -customnpcs.menus.conditions.numeric =Numeric Condition -customnpcs.menus.conditions.logical =Logical Condition -customnpcs.menus.conditions.comparator =Comparator: '{0}' -customnpcs.menus.conditions.value =Value: '{0}' -customnpcs.menus.conditions.target =Target Value: '{0}' -customnpcs.menus.conditions.new_condition =New Condition -customnpcs.menus.conditions.mode.toggle =Change Mode -customnpcs.menus.conditions.mode.all =Match ALL Conditions -customnpcs.menus.conditions.mode.one =Match ONE Condition -customnpcs.menus.conditions.new => New Action Condition -customnpcs.menus.conditions.new.numeric =Numeric Condition -customnpcs.menus.conditions.new.numeric.description =Compares numbers -customnpcs.menus.conditions.new.logical =Logic Condition -customnpcs.menus.conditions.new.logical.description =Compares things with a fixed quantity of possible values +customnpcs.conditions.has_effect=Has Effect +customnpcs.conditions.has_permission=Has Permission +customnpcs.conditions.is_flying=Is Flying +customnpcs.conditions.is_sprinting=Is Sprinting +customnpcs.conditions.is_sneaking=Is Sneaking +customnpcs.conditions.is_frozen=Is Frozen +customnpcs.conditions.is_gliding=Is Gliding +customnpcs.comparator=Comparator +customnpcs.value.select=Select Target Value +customnpcs.value.current=The target value is ''{0}'' +customnpcs.statistic=Statistic +customnpcs.menus.skins.title=> Edit NPC Skin +customnpcs.menus.skins.player=Import from Player +customnpcs.menus.skins.player.description=Fetches a player''s skin by name. \nThis may fail randomly on 3rd party hosts, as it makes requests to Mojang''s API. +customnpcs.menus.skins.catalog=Browse Skin Catalogue +customnpcs.menus.skins.catalog.description=Use a preset skin +customnpcs.menus.skins.url=Import from URL +customnpcs.menus.skins.url.description=Fetches a skin from a URL +customnpcs.menus.actions.new.title=> New NPC Action +customnpcs.menus.conditions.title=> Edit Action Conditions +customnpcs.menus.conditions.numeric=Numeric Condition +customnpcs.menus.conditions.logical=Logical Condition +customnpcs.menus.conditions.comparator=Comparator: ''{0}'' +customnpcs.menus.conditions.value=Value: ''{0}'' +customnpcs.menus.conditions.target=Target Value: ''{0}'' +customnpcs.menus.conditions.new_condition=New Condition +customnpcs.menus.conditions.mode.toggle=Change Mode +customnpcs.menus.conditions.mode.all=Match ALL Conditions +customnpcs.menus.conditions.mode.one=Match ONE Condition +customnpcs.menus.conditions.new=> New Action Condition +customnpcs.menus.conditions.new.numeric=Numeric Condition +customnpcs.menus.conditions.new.numeric.description=Compares numbers +customnpcs.menus.conditions.new.logical=Logic Condition +customnpcs.menus.conditions.new.logical.description=Compares things with a fixed quantity of possible values # Comparators -customnpcs.conditions.greater_than_or_equal_to =Greater Than or Equal to (>=) -customnpcs.conditions.greater_than =Greater Than (>) -customnpcs.conditions.less_than_or_equal_to =Less Than or Equal to (<=) -customnpcs.conditions.less_than =Less Than (<) -customnpcs.conditions.equal_to =Equal to (==) -customnpcs.conditions.not_equal_to =Not Equal to (!=) -customnpcs.messages.empty_string = +customnpcs.conditions.greater_than_or_equal_to=Greater Than or Equal to (>=) +customnpcs.conditions.greater_than=Greater Than (>) +customnpcs.conditions.less_than_or_equal_to=Less Than or Equal to (<=) +customnpcs.conditions.less_than=Less Than (<) +customnpcs.conditions.equal_to=Equal to (==) +customnpcs.conditions.not_equal_to=Not Equal to (!=) +customnpcs.messages.empty_string= # # Hologram Menus # -customnpcs.menus.holograms.title => Edit NPC Holograms -customnpcs.menus.holograms.new_line =New Line -customnpcs.menus.holograms.line =Line {0} -customnpcs.menus.holograms.edit = Press 'Swap With Offhand' (F) to edit -customnpcs.menus.holograms.delete = Press 'Drop' (Q) to remove -customnpcs.menus.holograms.move_down = \uD83D\uDDB0' Click to move this line DOWN \u21E8 -customnpcs.menus.holograms.move_up = '\uD83D\uDDB0 Click to move this line UP \u21E6 -customnpcs.menus.holograms.move_down_fail =WHOOPS! This line cannot be moved farther down! -customnpcs.menus.holograms.move_up_fail =WHOOPS! This line cannot be moved farther up! +customnpcs.menus.holograms.title=> Edit NPC Holograms +customnpcs.menus.holograms.new_line=New Line +customnpcs.menus.holograms.line=Line {0} +customnpcs.menus.holograms.edit= Press ''Swap With Offhand'' (F) to edit +customnpcs.menus.holograms.delete= Press ''Drop'' (Q) to remove +customnpcs.menus.holograms.move_down= \uD83D\uDDB0'' Click to move this line DOWN \u21E8 +customnpcs.menus.holograms.move_up= ''\uD83D\uDDB0 Click to move this line UP \u21E6 +customnpcs.menus.holograms.move_down_fail=WHOOPS! This line cannot be moved farther down! +customnpcs.menus.holograms.move_up_fail=WHOOPS! This line cannot be moved farther up! # # Line delete menu # -customnpcs.menus.hologram.delete.title => Delete NPC Hologram Line -customnpcs.menus.hologram.delete.confirm =Delete this line -customnpcs.menus.hologram.delete.confirm.lore = You are deleting the line'{0}'. Are you sure?TIP! You can use the 'Drop Stack' (Ctrl + Q) keybind to bypass this menu. -customnpcs.menus.hologram.delete.to_safety =Cancel! -customnpcs.menus.pose.title => Set NPC Pose -customnpcs.menus.pose.standing =Standing -customnpcs.menus.pose.standing.lore =Makes the NPC appear to be standing, like a normal player. -customnpcs.menus.pose.crouching =Crouching -customnpcs.menus.pose.crouching.lore =Makes the NPC appear to be crouching, or "sneaking". -customnpcs.menus.pose.swimming =Swimming -customnpcs.menus.pose.swimming.lore =Makes the NPC appear to be swimming, or crawling. Looking at players may be a little strange with this pose! \n\nNOTE! This pose has a small hitbox, and is more challenging to interact with! -customnpcs.menus.pose.sitting =Sitting -customnpcs.menus.pose.sitting.lore =Makes the NPC appear to be sitting. -customnpcs.menus.pose.dying =Dying -customnpcs.menus.pose.dying.lore =Makes the NPC appear to be dying. The skin is tinted red. Looking at players may be a little strange with this pose! \n\nNOTE! This pose has a tiny hitbox, and is much more challenging to interact with! -customnpcs.menus.pose.sleeping =Sleeping -customnpcs.menus.pose.sleeping.lore =Makes the NPC appear to be sleeping. Looking at players may be a little strange with this pose! \n\nNOTE! This pose has a tiny hitbox, and is much more challenging to interact with! -customnpcs.pose.already =WHOOPS! The NPC is already {0}! -customnpcs.pose.pose_editor =Pose Editor -customnpcs.data.nudge.title =Scroll to move the NPC -customnpcs.data.nudge.subtitle =Press to exit! -customnpcs.menus.pose.nudge =Enter Nudge Mode -customnpcs.menus.pose.nudge.lore =Nudge mode allows you to move the npc small amounts to finely tweak its position. \n\nUse your scroll wheel to push the npc in the direction you're facing! +customnpcs.menus.hologram.delete.title=> Delete NPC Hologram Line +customnpcs.menus.hologram.delete.confirm=Delete this line +customnpcs.menus.hologram.delete.confirm.lore= You are deleting the line''{0}''. Are you sure?TIP! You can use the ''Drop Stack'' (Ctrl + Q) keybind to bypass this menu. +customnpcs.menus.hologram.delete.to_safety=Cancel! +customnpcs.menus.pose.title=> Set NPC Pose +customnpcs.menus.pose.standing=Standing +customnpcs.menus.pose.standing.lore=Makes the NPC appear to be standing, like a normal player. +customnpcs.menus.pose.crouching=Crouching +customnpcs.menus.pose.crouching.lore=Makes the NPC appear to be crouching, or "sneaking". +customnpcs.menus.pose.swimming=Swimming +customnpcs.menus.pose.swimming.lore=Makes the NPC appear to be swimming, or crawling. Looking at players may be a little strange with this pose! \n\nNOTE! This pose has a small hitbox, and is more challenging to interact with! +customnpcs.menus.pose.sitting=Sitting +customnpcs.menus.pose.sitting.lore=Makes the NPC appear to be sitting. +customnpcs.menus.pose.dying=Dying +customnpcs.menus.pose.dying.lore=Makes the NPC appear to be dying. The skin is tinted red. Looking at players may be a little strange with this pose! \n\nNOTE! This pose has a tiny hitbox, and is much more challenging to interact with! +customnpcs.menus.pose.sleeping=Sleeping +customnpcs.menus.pose.sleeping.lore=Makes the NPC appear to be sleeping. Looking at players may be a little strange with this pose! \n\nNOTE! This pose has a tiny hitbox, and is much more challenging to interact with! +customnpcs.pose.already=WHOOPS! The NPC is already {0}! +customnpcs.pose.pose_editor=Pose Editor +customnpcs.data.nudge.title=Scroll to move the NPC +customnpcs.data.nudge.subtitle=Press to exit! +customnpcs.menus.pose.nudge=Enter Nudge Mode +customnpcs.menus.pose.nudge.lore=Nudge mode allows you to move the npc small amounts to finely tweak its position. \n\nUse your scroll wheel to push the npc in the direction you''re facing! diff --git a/core/src/main/resources/localization/German_de.properties b/core/src/main/resources/localization/German_de.properties index da1126e3..44e62218 100644 --- a/core/src/main/resources/localization/German_de.properties +++ b/core/src/main/resources/localization/German_de.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2024-2025. Foxikle +# Copyright (c) 2024-2026. Foxikle # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -42,7 +42,7 @@ customnpcs.commands.invalid_uuid =Die bereitge customnpcs.commands.invalid_name_or_uuid =Falsche UUID oder Namen bereitgestellt. customnpcs.commands.clone.success =NPC erfolgreich geklont\! customnpcs.commands.move.nudge =Psst\! Nach dem Entfernen von NPCs, solltest du sie neu laden\! -customnpcs.commands.unknown_command = Unbekannter Unterbefehl. Nutze '/npc help' f\u00FCr eine Liste m\u00F6glicher Befehle. +customnpcs.commands.unknown_command= Unbekannter Unterbefehl. Nutze ''/npc help'' f\u00FCr eine Liste m\u00F6glicher Befehle. # Help command parts # Syntaxes are gold, aliases are white, hovers are white, descriptions are dark aqua customnpcs.commands.help.help.syntax =\u2022 npc help @@ -110,7 +110,7 @@ customnpcs.set.name=Successfully set hologram line {0} to be ''{1} # {0} is the input customnpcs.error.parse_number =Cannot parse the number ''{0}'' Please try again. customnpcs.error.npc-menu-expired=WHOOPS\! Es sieht so aus als w\u00E4re ihr Men\u00FC-Instanz abgelaufen\! Sie k\u00F6nnen das Men\u00FC erneut \u00F6ffnen um dieses Problem zu beheben \:) -customnpcs.error.forgot_bailout_response =WHOOPS\! We forgot how to handle declining to delete this npc. Don't worry, it was not deleted\! +customnpcs.error.forgot_bailout_response=WHOOPS\! We forgot how to handle declining to delete this npc. Don''t worry, it was not deleted\! # {0} is the hologram text customnpcs.set.clickable_hologram =Successfully set the NPCs individual clickable hologram to ''{0}'' customnpcs.set.facing_direction =Successfully set facing direction\! @@ -133,7 +133,7 @@ customnpcs.skins.success.player_name =Successful customnpcs.skins.success.url =Successfully imported NPC''s skin from {0} customnpcs.skins.errors.invalid_url =An error occurred whilst parsing NPC skin. Is this URL valid? customnpcs.skins.errors.unknown_url_error =An error occurred whilst parsing this skin. Check the console for details. -customnpcs.skins.errors.no_image_data =The provided URL was valid, but it doesn't contain any skin data. Sorry\! +customnpcs.skins.errors.no_image_data=The provided URL was valid, but it doesn''t contain any skin data. Sorry\! customnpcs.skins.errors.player_does_not_exist =There was an error parsing {0}''s skin\! Does this player exist? customnpcs.skins.changed_with_catalog =Skin changed to {0} #=====================================# @@ -177,7 +177,7 @@ customnpcs.items.prev_page =Previous customnpcs.items.go_back =Go Back customnpcs.menus.skin_catalog.title =Select A Skin # The skin name is {0} -customnpcs.menus.skin_catalog.items.icon.lore ='The {0} Skin' +customnpcs.menus.skin_catalog.items.icon.lore=''The {0} Skin'' customnpcs.menus.delete.title =Delete an NPC customnpcs.menus.delete.items.confirm.name=L\u00D6SCHEN customnpcs.menus.delete.items.confirm.lore =This CANNOT be undone. @@ -226,7 +226,7 @@ customnpcs.menus.main.vision.name =Toggle Visi customnpcs.menus.main.vision.tunnel =TUNNEL Visioned customnpcs.menus.main.vision.normal =NORMAL Visioned customnpcs.menus.main.facing.name =Set Facing Direction -customnpcs.menus.main.facing.description =This option configures the NPC's Yaw and Pitch. This sets the NPC's facing direction to 'Player Direction' +customnpcs.menus.main.facing.description=This option configures the NPC''s Yaw and Pitch. This sets the NPC''s facing direction to ''Player Direction'' # Random one off things customnpcs.menus.main.settings.name =Edit Additional Settings customnpcs.menus.main.delete.name =Delete NPC @@ -370,7 +370,7 @@ customnpcs.menus.extra.upside_down.description=The NPC is currently\: customnpcs.menus.extra.upside_down.description.false=RIGHT SIDE UP customnpcs.menus.extra.upside_down.description.true=UPSIDE DOWN customnpcs.menus.extra.hologram_text =Change NPC Clickable Hologram Text -customnpcs.menus.extra.hologram_text.description =This changes only THIS NPC's interactable hologram.\n If you would like to change the default, change the 'ClickText' field in the config.yml\! +customnpcs.menus.extra.hologram_text.description=This changes only THIS NPC''s interactable hologram.\n If you would like to change the default, change the ''ClickText'' field in the config.yml\! customnpcs.menus.extra.hologram_text.type =Type the new hologram text in chat\! customnpcs.favicons.teleport =Teleport Player customnpcs.favicons.edit =Left click to edit @@ -430,7 +430,7 @@ customnpcs.value.current =The targe customnpcs.statistic =Statistic customnpcs.menus.skins.title => Edit NPC Skin customnpcs.menus.skins.player =Import from Player -customnpcs.menus.skins.player.description =Fetches a player's skin by name. \nThis may fail randomly on 3rd party hosts, as it makes requests to Mojang's API. +customnpcs.menus.skins.player.description=Fetches a player''s skin by name. \nThis may fail randomly on 3rd party hosts, as it makes requests to Mojang''s API. customnpcs.menus.skins.catalog =Browse Skin Catalogue customnpcs.menus.skins.catalog.description =Use a preset skin customnpcs.menus.skins.url =Import from URL @@ -465,10 +465,10 @@ customnpcs.messages.empty_string = customnpcs.menus.holograms.title=> Edit NPC Holograms customnpcs.menus.holograms.new_line=New Line customnpcs.menus.holograms.line=Line {0} -customnpcs.menus.holograms.edit= Press 'Swap With Offhand' (F) to edit -customnpcs.menus.holograms.delete= Press 'Drop' (Q) to remove -customnpcs.menus.holograms.move_down= \uD83D\uDDB0' Click to move this line DOWN \u21E8 -customnpcs.menus.holograms.move_up= '\uD83D\uDDB0 Click to move this line UP \u21E6 +customnpcs.menus.holograms.edit= Press ''Swap With Offhand'' (F) to edit +customnpcs.menus.holograms.delete= Press ''Drop'' (Q) to remove +customnpcs.menus.holograms.move_down= \uD83D\uDDB0'' Click to move this line DOWN \u21E8 +customnpcs.menus.holograms.move_up= ''\uD83D\uDDB0 Click to move this line UP \u21E6 customnpcs.menus.holograms.move_down_fail=WHOOPS\! This line cannot be moved farther down\! customnpcs.menus.holograms.move_up_fail=WHOOPS\! This line cannot be moved farther up\! # @@ -496,7 +496,7 @@ customnpcs.pose.pose_editor=Pose Editor customnpcs.data.nudge.title=Scroll to move the NPC customnpcs.data.nudge.subtitle=Press to exit\! customnpcs.menus.pose.nudge=Enter Nudge Mode -customnpcs.menus.pose.nudge.lore=Nudge mode allows you to move the npc small amounts to finely tweak its position. \n\nUse your scroll wheel to push the npc in the direction you're facing\! +customnpcs.menus.pose.nudge.lore=Nudge mode allows you to move the npc small amounts to finely tweak its position. \n\nUse your scroll wheel to push the npc in the direction you''re facing\! diff --git a/core/src/main/resources/localization/Russian_ru.properties b/core/src/main/resources/localization/Russian_ru.properties index 48c39b20..547dc551 100644 --- a/core/src/main/resources/localization/Russian_ru.properties +++ b/core/src/main/resources/localization/Russian_ru.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2024-2025. Foxikle +# Copyright (c) 2024-2026. Foxikle # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -104,13 +104,13 @@ customnpcs.actionImpls.conditions.set.target =Successful #=====================================# # General Messages # #=====================================# -customnpcs.should_update =---------------- [\!] CustomNPCs [\!] ----------------A new update is available\! I'd appreciate if you updated \:) ~Foxikle +customnpcs.should_update=---------------- [\!] CustomNPCs [\!] ----------------A new update is available\! I''d appreciate if you updated \:) ~Foxikle # {0} is the new name customnpcs.set.name=Successfully set hologram line {0} to be ''{1}'' # {0} is the input customnpcs.error.parse_number =Cannot parse the number ''{0}'' Please try again. customnpcs.error.npc-menu-expired =WHOOPS\! It looks like your menu instance expired\! You can just reopen the menu to resolve this \:) -customnpcs.error.forgot_bailout_response =WHOOPS\! We forgot how to handle declining to delete this npc. Don't worry, it was not deleted\! +customnpcs.error.forgot_bailout_response=WHOOPS\! We forgot how to handle declining to delete this npc. Don''t worry, it was not deleted\! # {0} is the hologram text customnpcs.set.clickable_hologram =Successfully set the NPCs individual clickable hologram to ''{0}'' customnpcs.set.facing_direction =Successfully set facing direction\! @@ -177,7 +177,7 @@ customnpcs.items.prev_page=Previous Page customnpcs.items.go_back=Go Back customnpcs.menus.skin_catalog.title =Select A Skin # The skin name is {0} -customnpcs.menus.skin_catalog.items.icon.lore='The {0} Skin' +customnpcs.menus.skin_catalog.items.icon.lore=''The {0} Skin'' customnpcs.menus.delete.title=Delete an NPC customnpcs.menus.delete.items.confirm.name=DELETE customnpcs.menus.delete.items.confirm.lore=This CANNOT be undone. @@ -226,7 +226,7 @@ customnpcs.menus.main.vision.name=Toggle Vision Mode customnpcs.menus.main.vision.tunnel=TUNNEL Visioned customnpcs.menus.main.vision.normal=NORMAL Visioned customnpcs.menus.main.facing.name=Set Facing Direction -customnpcs.menus.main.facing.description=This option configures the NPC's Yaw and Pitch. This sets the NPC's facing direction to 'Player Direction' +customnpcs.menus.main.facing.description=This option configures the NPC''s Yaw and Pitch. This sets the NPC''s facing direction to ''Player Direction'' # Random one off things customnpcs.menus.main.settings.name=Edit Additional Settings customnpcs.menus.main.delete.name=Delete NPC @@ -369,7 +369,7 @@ customnpcs.menus.extra.upside_down.description=The NPC is currently\: customnpcs.menus.extra.upside_down.description.false=RIGHT SIDE UP customnpcs.menus.extra.upside_down.description.true=UPSIDE DOWN customnpcs.menus.extra.hologram_text=Change NPC Clickable Hologram Text -customnpcs.menus.extra.hologram_text.description =This changes only THIS NPC's interactable hologram.\n If you would like to change the default, change the 'ClickText' field in the config.yml\! +customnpcs.menus.extra.hologram_text.description=This changes only THIS NPC''s interactable hologram.\n If you would like to change the default, change the ''ClickText'' field in the config.yml\! customnpcs.menus.extra.hologram_text.type =Type the new hologram text in chat\! customnpcs.favicons.teleport=Teleport Player customnpcs.favicons.edit=Left click to edit @@ -429,7 +429,7 @@ customnpcs.value.current =The targe customnpcs.statistic=Statistic customnpcs.menus.skins.title=> Edit NPC Skin customnpcs.menus.skins.player=Import from Player -customnpcs.menus.skins.player.description=Fetches a player's skin by name. \nThis may fail randomly on 3rd party hosts, as it makes requests to Mojang's API. +customnpcs.menus.skins.player.description=Fetches a player''s skin by name. \nThis may fail randomly on 3rd party hosts, as it makes requests to Mojang''s API. customnpcs.menus.skins.catalog=Browse Skin Catalogue customnpcs.menus.skins.catalog.description=Use a preset skin customnpcs.menus.skins.url=Import from URL @@ -464,10 +464,10 @@ customnpcs.messages.empty_string= customnpcs.menus.holograms.title=> Edit NPC Holograms customnpcs.menus.holograms.new_line=New Line customnpcs.menus.holograms.line=Line {0} -customnpcs.menus.holograms.edit= Press 'Swap With Offhand' (F) to edit -customnpcs.menus.holograms.delete= Press 'Drop' (Q) to remove -customnpcs.menus.holograms.move_down= \uD83D\uDDB0' Click to move this line DOWN \u21E8 -customnpcs.menus.holograms.move_up= '\uD83D\uDDB0 Click to move this line UP \u21E6 +customnpcs.menus.holograms.edit= Press ''Swap With Offhand'' (F) to edit +customnpcs.menus.holograms.delete= Press ''Drop'' (Q) to remove +customnpcs.menus.holograms.move_down= \uD83D\uDDB0'' Click to move this line DOWN \u21E8 +customnpcs.menus.holograms.move_up= ''\uD83D\uDDB0 Click to move this line UP \u21E6 customnpcs.menus.holograms.move_down_fail=WHOOPS\! This line cannot be moved farther down\! customnpcs.menus.holograms.move_up_fail=WHOOPS\! This line cannot be moved farther up\! # @@ -495,7 +495,7 @@ customnpcs.pose.pose_editor=Pose Editor customnpcs.data.nudge.title=Scroll to move the NPC customnpcs.data.nudge.subtitle=Press to exit\! customnpcs.menus.pose.nudge=Enter Nudge Mode -customnpcs.menus.pose.nudge.lore=Nudge mode allows you to move the npc small amounts to finely tweak its position. \n\nUse your scroll wheel to push the npc in the direction you're facing\! +customnpcs.menus.pose.nudge.lore=Nudge mode allows you to move the npc small amounts to finely tweak its position. \n\nUse your scroll wheel to push the npc in the direction you''re facing\! diff --git a/core/src/main/resources/localization/Vietnamese_vi.properties b/core/src/main/resources/localization/Vietnamese_vi.properties index a3181972..3709ed12 100644 --- a/core/src/main/resources/localization/Vietnamese_vi.properties +++ b/core/src/main/resources/localization/Vietnamese_vi.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2024-2025. Foxikle +# Copyright (c) 2024-2026. Foxikle # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -24,7 +24,7 @@ customnpcs.commands.no_permission =B\u1EA1n kh\ customnpcs.commands.header = [\!] T\u1EF1 t\u1EA1o npc {0} [\!] customnpcs.commands.manage.no_npcs =Kh\u00F4ng c\u00F3 NPC n\u00E0o c\u1EA3\! customnpcs.commands.manage.header = [\!] Qu\u1EA3n L\u00FD NPCs [\!] -customnpcs.commands.manage.copy_uuid =\u1EA4n \u0111\u1EC3 copy NPC's UUID. +customnpcs.commands.manage.copy_uuid=\u1EA4n \u0111\u1EC3 copy NPC''s UUID. customnpcs.commands.manage.button.edit =[Ch\u1EC9nh S\u1EEDa] customnpcs.commands.manage.button.edit.hover =\u1EA4n \u0111\u1EC3 ch\u1EC9nh s\u1EEDa NPC n\u00E0y. customnpcs.commands.manage.button.delete =[Xo\u00E1] @@ -181,7 +181,7 @@ customnpcs.directions.north_west =T\u00E2y B customnpcs.directions.player =H\u01B0\u1EDBng ng\u01B0\u1EDDi ch\u01A1i customnpcs.menus.skin_catalog.title =Ch\u1ECDn giao di\u1EC7n # The skin name is {0} -customnpcs.menus.skin_catalog.items.icon.lore ='Giao di\u1EC7n {0}' +customnpcs.menus.skin_catalog.items.icon.lore=''Giao di\u1EC7n {0}'' customnpcs.menus.delete.title =X\u00F3a m\u1ED9t NPC customnpcs.menus.delete.items.confirm.name =X\u00D3A customnpcs.menus.delete.items.confirm.lore =Thao t\u00E1c n\u00E0y KH\u00D4NG th\u1EC3 ho\u00E0n t\u00E1c. @@ -230,7 +230,7 @@ customnpcs.menus.main.vision.name =B\u1EADt/T\ customnpcs.menus.main.vision.tunnel =T\u1EA6M NH\u00CCN H\u1EB8P customnpcs.menus.main.vision.normal =T\u1EA6M NH\u00CCN B\u00CCNH TH\u01AF\u1EDCNG customnpcs.menus.main.facing.name =\u0110\u1EB7t H\u01B0\u1EDBng Nh\u00ECn -customnpcs.menus.main.facing.description =T\u00F9y ch\u1ECDn n\u00E0y c\u1EA5u h\u00ECnh Yaw v\u00E0 Pitch c\u1EE7a NPC. N\u00F3 thi\u1EBFt l\u1EADp h\u01B0\u1EDBng nh\u00ECn c\u1EE7a NPC th\u00E0nh 'H\u01B0\u1EDBng Ng\u01B0\u1EDDi ch\u01A1i' +customnpcs.menus.main.facing.description=T\u00F9y ch\u1ECDn n\u00E0y c\u1EA5u h\u00ECnh Yaw v\u00E0 Pitch c\u1EE7a NPC. N\u00F3 thi\u1EBFt l\u1EADp h\u01B0\u1EDBng nh\u00ECn c\u1EE7a NPC th\u00E0nh ''H\u01B0\u1EDBng Ng\u01B0\u1EDDi ch\u01A1i'' # Random one off things customnpcs.menus.main.settings.name =Ch\u1EC9nh s\u1EEDa C\u00E0i \u0111\u1EB7t B\u1ED5 sung customnpcs.menus.main.delete.name =X\u00F3a NPC @@ -363,7 +363,7 @@ customnpcs.menus.extra.hologram_visibility.description =Tr\u1EA1n customnpcs.menus.extra.hologram_visibility.description.hidden =\u1EA8N customnpcs.menus.extra.hologram_visibility.description.shown =HI\u1EC2N TH\u1ECA customnpcs.menus.extra.hologram_text =Thay \u0111\u1ED5i V\u0103n b\u1EA3n Hologram T\u01B0\u01A1ng t\u00E1c c\u1EE7a NPC -customnpcs.menus.extra.hologram_text.description =Ch\u1EC9 thay \u0111\u1ED5i Hologram N\u00C0Y c\u1EE7a NPC.\n N\u1EBFu mu\u1ED1n thay \u0111\u1ED5i m\u1EB7c \u0111\u1ECBnh, ch\u1EC9nh s\u1EEDa tr\u01B0\u1EDDng 'ClickText' trong config.yml\! +customnpcs.menus.extra.hologram_text.description=Ch\u1EC9 thay \u0111\u1ED5i Hologram N\u00C0Y c\u1EE7a NPC.\n N\u1EBFu mu\u1ED1n thay \u0111\u1ED5i m\u1EB7c \u0111\u1ECBnh, ch\u1EC9nh s\u1EEDa tr\u01B0\u1EDDng ''ClickText'' trong config.yml\! customnpcs.menus.extra.hologram_text.type =Nh\u1EADp v\u0103n b\u1EA3n hologram m\u1EDBi trong chat\! customnpcs.favicons.teleport =D\u1ECBch chuy\u1EC3n Ng\u01B0\u1EDDi ch\u01A1i customnpcs.favicons.edit =Nh\u1EA5p chu\u1ED9t tr\u00E1i \u0111\u1EC3 ch\u1EC9nh s\u1EEDa diff --git a/core/src/test/java/dev/foxikle/customnpcs/PropertiesValidationTest.java b/core/src/test/java/dev/foxikle/customnpcs/PropertiesValidationTest.java new file mode 100644 index 00000000..dfb3bcb1 --- /dev/null +++ b/core/src/test/java/dev/foxikle/customnpcs/PropertiesValidationTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2026. Foxikle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.foxikle.customnpcs; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PropertiesValidationTest { + + private record LocaleInfo(String baseName, Locale locale, String label) { + } + + static Stream localeProvider() { + return Stream.of( + Arguments.of(new LocaleInfo("localization.English", Locale.US, "English_en_US")), + Arguments.of(new LocaleInfo("localization.Chinese", Locale.SIMPLIFIED_CHINESE, "Chinese_zh_CN")), + Arguments.of(new LocaleInfo("localization.German", Locale.GERMAN, "German_de")), + Arguments.of(new LocaleInfo("localization.Russian", new Locale("ru"), "Russian_ru")), + Arguments.of(new LocaleInfo("localization.Vietnamese", new Locale("vi"), "Vietnamese_vi")) + ); + } + + @ParameterizedTest + @MethodSource("localeProvider") + void testNoUnescapedSingleQuotes(LocaleInfo info) { + ResourceBundle bundle = ResourceBundle.getBundle(info.baseName, info.locale); + List failures = new ArrayList<>(); + + for (String key : bundle.keySet()) { + String value = bundle.getString(key); + + int maxPlaceholder = -1; + for (int i = 0; i <= 9; i++) { + if (value.contains("{" + i + "}")) { + maxPlaceholder = i; + } + } + + if (maxPlaceholder >= 0) { + Object[] args = new Object[maxPlaceholder + 1]; + for (int i = 0; i <= maxPlaceholder; i++) { + args[i] = "ARG" + i; + } + try { + String result = MessageFormat.format(value, args); + for (int i = 0; i <= maxPlaceholder; i++) { + String placeholder = "{" + i + "}"; + if (result.contains(placeholder)) { + failures.add(key + "=" + value + " -> `" + placeholder + "` not resolved (got: " + result + ")"); + } + } + } catch (Exception e) { + failures.add(key + "=" + value + " -> " + e.getClass().getSimpleName() + ": " + e.getMessage()); + } + } + + for (int i = 0; i < value.length(); i++) { + if (value.charAt(i) == '\'') { + if (i + 1 < value.length() && value.charAt(i + 1) == '\'') { + i++; + continue; + } + int braceIdx = value.indexOf('{', i + 1); + int nextQuote = value.indexOf('\'', i + 1); + if (braceIdx >= 0 && (nextQuote < 0 || braceIdx < nextQuote)) { + failures.add(key + "=" + value + " -> lone `'` before `{` at position " + i); + } + } + } + } + + assertTrue(failures.isEmpty(), + "Found " + failures.size() + " issue(s) in " + info.label + ":\n " + String.join("\n ", failures)); + } +} diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index b9bb139f..622d5337 --- a/gradlew +++ b/gradlew @@ -1,21 +1,25 @@ #!/bin/sh # -# Copyright © 2015 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2026. Foxikle +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. # ############################################################################## diff --git a/settings.gradle.kts b/settings.gradle.kts index 72d9973c..842dfd4e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,19 @@ * SOFTWARE. */ +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://repo.papermc.io/repository/maven-public/") + } +} + plugins { id("org.gradle.toolchains.foojay-resolver-convention") version("1.0.0") } rootProject.name = "customnpcs" include("core") -include("v1_20_R3") -include("v1_20_R2") -include("v1_20_R1") include("v1_20_R4") include("v1_21_R0") include("v1_21_R1") @@ -37,4 +41,7 @@ include("v1_21_R3") include("v1_21_R4") include("v1_21_R5") include("v1_21_R6") +include("v26_1_R1") +include("v26_2_R1") + diff --git a/v1_20_R1/build.gradle.kts b/v1_20_R1/build.gradle.kts deleted file mode 100644 index f5401437..00000000 --- a/v1_20_R1/build.gradle.kts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -plugins { - id("java") - id("io.freefair.lombok") version "9.5.0" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" -} - -repositories { - mavenLocal() - mavenCentral() - maven("https://jitpack.io") - maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") -} - -dependencies { - compileOnly("me.clip:placeholderapi:2.12.1") - compileOnly(project(":core")) - paperweight.paperDevBundle("1.20.1-R0.1-SNAPSHOT") -} - -tasks { - java { - toolchain.languageVersion = JavaLanguageVersion.of(21) - } - - compileJava { - options.release = 21 - } - - jar { - archiveClassifier = "v1_20_R1" - } -} \ No newline at end of file diff --git a/v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R1.java b/v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R1.java deleted file mode 100644 index 69742ac7..00000000 --- a/v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R1.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.versions; - -import net.minecraft.network.Connection; -import net.minecraft.network.protocol.Packet; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.network.ServerGamePacketListenerImpl; -import org.jetbrains.annotations.NotNull; - -/** - * A fake packet listener for the NPCs - */ -public class FakeListener_v1_20_R1 extends ServerGamePacketListenerImpl { - /** - *

Creates a fake ServerGamePacketListenerImpl for NPCs - *

- * @param server The server - * @param connection The connection - * @param npc The NPC - */ - public FakeListener_v1_20_R1(MinecraftServer server, Connection connection, ServerPlayer npc) { - super(server, connection, npc); - } - /** - *

Overrides the default ServerGamePacketListenerImpl's send packet method - *

- * @param packet The packet that won't be sent. - */ - @Override - public void send(@NotNull Packet packet) {} -} diff --git a/v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R2.java b/v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R2.java deleted file mode 100644 index bf1e873e..00000000 --- a/v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R2.java +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright (c) 2024-2025. Foxikle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package dev.foxikle.customnpcs.versions; - -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import com.mojang.datafixers.util.Pair; -import dev.foxikle.customnpcs.actions.Action; -import dev.foxikle.customnpcs.api.Pose; -import dev.foxikle.customnpcs.data.Equipment; -import dev.foxikle.customnpcs.data.Settings; -import dev.foxikle.customnpcs.internal.CustomNPCs; -import dev.foxikle.customnpcs.internal.InjectionManager; -import dev.foxikle.customnpcs.internal.LookAtAnchor; -import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; -import dev.foxikle.customnpcs.internal.utils.Utils; -import lombok.Getter; -import lombok.Setter; -import me.clip.placeholderapi.PlaceholderAPI; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.json.JSONComponentSerializer; -import net.minecraft.commands.arguments.EntityAnchorArgument; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.PacketFlow; -import net.minecraft.network.protocol.game.*; -import net.minecraft.network.syncher.EntityDataAccessor; -import net.minecraft.network.syncher.EntityDataSerializers; -import net.minecraft.network.syncher.SynchedEntityData; -import net.minecraft.server.level.ClientInformation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.MoverType; -import net.minecraft.world.phys.Vec3; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Particle; -import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R2.CraftServer; -import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R2.entity.CraftArmorStand; -import org.bukkit.craftbukkit.v1_20_R2.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R2.entity.CraftTextDisplay; -import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; -import org.bukkit.entity.*; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.Transformation; -import org.bukkit.util.Vector; -import org.joml.Vector3f; - -import javax.annotation.Nullable; -import java.lang.reflect.Field; -import java.util.*; - -/** - * The object representing the NPC - */ -public class NPC_v1_20_R2 extends ServerPlayer implements InternalNpc { - - @Getter - private final UUID uniqueID; - @Getter - private final Particle spawnParticle = Particle.EXPLOSION_LARGE; - private final CustomNPCs plugin; - @Getter - private final World world; - private final EntityDataAccessor TEXT_DISPLAY_ACCESSOR; - private final Map loops = new HashMap<>(); - @Getter - @Setter - private Settings settings; - @Getter - @Setter - private Equipment equipment; - @Getter - @Setter - private Location spawnLoc; - private @Nullable ArmorStand seat; - @Getter - private TextDisplay clickableHologram; - @Getter - private List holograms; - @Getter - @Setter - private Player target; - @Getter - @Setter - private List actions; - private String clickableName = "ERROR"; - private InjectionManager injectionManager; - - public NPC_v1_20_R2(CustomNPCs plugin, World world, Location spawnLoc, Equipment equipment, Settings settings, UUID uniqueID, @Nullable Player target, List actions) { - super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), new GameProfile(uniqueID, Utils.getNpcName(settings, uniqueID)), ClientInformation.createDefault()); - this.spawnLoc = spawnLoc; - this.settings = settings; - this.equipment = equipment; - this.world = spawnLoc.getWorld(); - this.uniqueID = uniqueID; - this.target = target; - this.actions = new ArrayList<>(actions); - super.connection = new FakeListener_v1_20_R2(((CraftServer) Bukkit.getServer()).getServer(), new FakeConnection_v1_20_R2(PacketFlow.CLIENTBOUND), this); - this.plugin = plugin; - - //aM - try { - Field field = net.minecraft.world.entity.Display.TextDisplay.class.getDeclaredField("aM"); - field.setAccessible(true); - TEXT_DISPLAY_ACCESSOR = (EntityDataAccessor) field.get(new EntityDataAccessor<>(0, EntityDataSerializers.COMPONENT)); - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new RuntimeException(e); - } - } - - public void setPosRot(Location location) { - this.setPos(location.getX(), location.getY(), location.getZ()); - this.setXRot(location.getPitch()); - this.setYRot(location.getYaw()); - lookAt(Utils.calcLocation(this)); - } - - public void createNPC() { - if (plugin.npcs.containsKey(uniqueID)) { - plugin.getNPCByID(uniqueID).remove(); - plugin.getNPCByID(uniqueID).delete(); - } - - if (isRemoved()) { - unsetRemoved(); - } - - setupHolograms(); - if (settings.isInteractable() && !settings.isHideClickableHologram()) { - setupClickableHologram(settings.getCustomInteractableHologram() == null || settings.getCustomInteractableHologram().isEmpty() ? plugin.getConfig().getString("ClickText") : settings.getCustomInteractableHologram()); - } - - setSkin(); - setPosRot(spawnLoc); - this.getBukkitEntity().setInvulnerable(true); - this.getBukkitEntity().setNoDamageTicks(Integer.MAX_VALUE); - super.getCommandSenderWorld().addFreshEntity(this); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HAND, equipment.getHand(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.OFF_HAND, equipment.getOffhand(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HEAD, equipment.getHead(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.CHEST, equipment.getChest(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.LEGS, equipment.getLegs(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.FEET, equipment.getBoots(), true); - super.getBukkitEntity().addScoreboardTag("NPC"); - super.getBukkitEntity().setItemInHand(equipment.getHand()); - setPose(setupPose(settings.getPose())); - - if (settings.isResilient()) plugin.getFileManager().addNPC(this); - plugin.addNPC(this, holograms); - - injectionManager = new InjectionManager(plugin, this); - injectionManager.setup(); - } - - public void setSkin() { - super.getGameProfile().getProperties().removeAll("textures"); - super.getGameProfile().getProperties().put("textures", new Property("textures", settings.getValue(), settings.getSignature())); - byte bitmask = (byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40); - super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, bitmask); - } - - public void setupHolograms() { - final double space = 0.28; - double startingOffset = getPoseOffset(settings.getPose()); - boolean displayClickable = settings.isInteractable() && !settings.isHideClickableHologram() && plugin.getConfig().getBoolean("DisplayClickText"); - if (displayClickable) { - startingOffset += space; - } - List holograms = new ArrayList<>(); - for (int i = 0; i < settings.getRawHolograms().length; i++) { - double y = startingOffset + (i * space); - TextDisplay hologram = (TextDisplay) spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.TEXT_DISPLAY); - hologram.setInvulnerable(true); - hologram.setBillboard(Display.Billboard.CENTER); - hologram.addScoreboardTag("npcHologram"); - hologram.setTeleportDuration(settings.getInterpolationDuration()); - hologram.setTransformation(new Transformation( - new Vector3f(0, (float) y, 0), - hologram.getTransformation().getLeftRotation(), - hologram.getTransformation().getScale(), - hologram.getTransformation().getRightRotation() - )); - holograms.add(hologram); - ((CraftTextDisplay) hologram).getHandle().startRiding(this, true); - } - this.holograms = holograms.reversed(); - } - - public void setupClickableHologram(String name) { - clickableName = name; - clickableHologram = (TextDisplay) spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.TEXT_DISPLAY); - clickableHologram.setInvulnerable(true); - clickableHologram.setBillboard(Display.Billboard.CENTER); - clickableHologram.text(Component.empty()); - clickableHologram.addScoreboardTag("npcHologram"); - clickableHologram.setTeleportDuration(settings.getInterpolationDuration()); - - clickableHologram.setTransformation(new Transformation( - new Vector3f(0, (float) getPoseOffset(settings.getPose()), 0), - clickableHologram.getTransformation().getLeftRotation(), - clickableHologram.getTransformation().getScale(), - clickableHologram.getTransformation().getRightRotation() - )); - ((CraftTextDisplay) clickableHologram).getHandle().startRiding(this, true); - } - - public Location getCurrentLocation() { - if (seat != null) { - return seat.getLocation(); - } - return super.getBukkitEntity().getLocation(); - } - - @Override - public void addAction(Action actionImpl) { - actions.add(actionImpl); - } - - @Override - public boolean removeAction(Action actionImpl) { - return actions.remove(actionImpl); - } - - @Override - public void injectPlayer(Player p) { - if (p.getWorld() != spawnLoc.getWorld()) return; - List> stuffs = new ArrayList<>(); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, CraftItemStack.asNMSCopy(equipment.getHand()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(equipment.getOffhand()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.HEAD, CraftItemStack.asNMSCopy(equipment.getHead()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.CHEST, CraftItemStack.asNMSCopy(equipment.getChest()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.LEGS, CraftItemStack.asNMSCopy(equipment.getLegs()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.FEET, CraftItemStack.asNMSCopy(equipment.getBoots()))); - - ClientboundPlayerInfoUpdatePacket playerInfoAdd = new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, this); - ClientboundAddEntityPacket namedEntitySpawn = new ClientboundAddEntityPacket(this); - ClientboundPlayerInfoRemovePacket playerInforemove = new ClientboundPlayerInfoRemovePacket(Collections.singletonList(super.getUUID())); - ClientboundSetEquipmentPacket equipmentPacket = new ClientboundSetEquipmentPacket(super.getId(), stuffs); - ClientboundMoveEntityPacket rotation = new ClientboundMoveEntityPacket.Rot(this.getBukkitEntity().getEntityId(), (byte) (getYaw() * 256 / 360), (byte) (getXRot() * 256 / 360), true); - ClientboundSetPassengersPacket hideName = new ClientboundSetPassengersPacket(this); - setSkin(); - - ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; - connection.send(playerInfoAdd); - connection.send(namedEntitySpawn); - connection.send(equipmentPacket); - connection.send(rotation); - connection.send(hideName); - super.getEntityData().refresh(((CraftPlayer) p).getHandle()); - - if (seat != null) { - connection.send(new ClientboundSetPassengersPacket(((CraftArmorStand) seat).getHandle())); - } - - if (plugin.isDebug()) { - plugin.getLogger().info("[DEBUG] Injected npc '" + this.displayName + "' to player '" + p.getName() + "'"); - } - - Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> connection.send(playerInforemove), 30); - if (!settings.isUpsideDown()) { - super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) (0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80)); - } - - if (loops.containsKey(p.getUniqueId())) { - Bukkit.getScheduler().cancelTask(loops.get(p.getUniqueId())); - } - - // create them - injectHolograms(p); - - loops.put(p.getUniqueId(), - new BukkitRunnable() { - @Override - public void run() { - if (!p.isOnline()) this.cancel(); - injectHolograms(p); - } - }.runTaskTimerAsynchronously(plugin, 0, plugin.getConfig().getInt("HologramUpdateInterval")) - .getTaskId()); - } - - private void injectHolograms(Player p) { - ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; - String[] hologramText = new String[settings.getRawHolograms().length]; - String clickableText = clickableName; - if (plugin.papi) { - for (int i = 0; i < settings.getRawHolograms().length; i++) { - hologramText[i] = PlaceholderAPI.setPlaceholders(p, settings.getRawHolograms()[i]); - } - clickableText = PlaceholderAPI.setPlaceholders(p, clickableName); - } else { - for (int i = 0; i < settings.getRawHolograms().length; i++) { - hologramText[i] = settings.getRawHolograms()[i]; - } - } - List> packets = new ArrayList<>(); - - for (int i = 0; i < holograms.size(); i++) { - TextDisplay hologram = holograms.get(i); - packets.add(createMojComponent(hologramText[i], hologram)); - } - - - if (clickableHologram != null && settings.isInteractable() && !settings.isHideClickableHologram()) { - packets.add(createMojComponent(clickableText, clickableHologram)); - } - packets.forEach(connection::send); - Bukkit.getScheduler().runTaskLater(plugin, () -> packets.forEach(connection::send), 5); - } - - private Packet createMojComponent(String clickableText, TextDisplay clickableHologram) { - List> meta = ((CraftTextDisplay) clickableHologram).getHandle().getEntityData().getNonDefaultValues(); - String serialized_component = JSONComponentSerializer.json().serialize(plugin.getMiniMessage().deserialize(clickableText)); - net.minecraft.network.chat.Component clickableComponent = net.minecraft.network.chat.Component.Serializer.fromJson(serialized_component); - meta.set(0, SynchedEntityData.DataValue.create(TEXT_DISPLAY_ACCESSOR, clickableComponent)); - - return new ClientboundSetEntityDataPacket(clickableHologram.getEntityId(), meta); - } - - @Override - public void remove() { - injectionManager.shutDown(); - loops.forEach((uuid1, integer) -> Bukkit.getScheduler().cancelTask(integer)); - loops.clear(); - - List> packets = new ArrayList<>(); - if (holograms != null) { - for (TextDisplay hologram : holograms) { - packets.add(new ClientboundRemoveEntitiesPacket(hologram.getEntityId())); - hologram.remove(); - } - } - if (seat != null) { - seat.remove(); - } - if (clickableHologram != null) { - packets.add(new ClientboundRemoveEntitiesPacket(clickableHologram.getEntityId())); - clickableHologram.remove(); - } - packets.add(new ClientboundRemoveEntitiesPacket(super.getId())); - - super.remove(RemovalReason.DISCARDED); - for (Player p : Bukkit.getOnlinePlayers()) { - ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; - packets.forEach(connection::send); - } - } - - @Override - public void moveTo(Vector v) { - if (isRemoved()) return; - if (seat != null) { - ((CraftArmorStand) seat).getHandle().move(MoverType.PLAYER, new Vec3(v.getX(), v.getY(), v.getZ())); - spawnLoc = seat.getLocation(); - } else { - super.move(MoverType.PLAYER, new Vec3(v.getX(), v.getY(), v.getZ())); - spawnLoc = getCurrentLocation(); - } - } - - @Override - public void teleport(Location loc) { - teleportTo(loc.x(), loc.y(), loc.z()); - spawnLoc = loc; - } - - public void delete() { - plugin.getFileManager().remove(this.uniqueID); - } - - @Override - public void lookAt(LookAtAnchor anchor, Entity e) { - switch (anchor) { - case HEAD -> - super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), EntityAnchorArgument.Anchor.EYES); - case FEET -> - super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), EntityAnchorArgument.Anchor.FEET); - } - } - - public void lookAt(Location loc) { - super.lookAt(EntityAnchorArgument.Anchor.EYES, new Vec3(loc.x(), loc.y(), loc.z())); - } - - @Override - public void updateSkin() { - setSkin(); - } - - @Override - public void swingArm() { - super.swing(InteractionHand.MAIN_HAND, true); - } - - @Override - public void setYRotation(float f) { - super.setYRot(f); - super.setYBodyRot(f); - super.setYHeadRot(f); - lookAt(Utils.calcLocation(this)); - } - - @Override - public void setXRotation(float f) { - super.setXRot(f); - lookAt(Utils.calcLocation(this)); - } - - @Override - public void reloadSettings() { - if (seat != null) { - seat.remove(); - seat = null; - } - - if (holograms != null) { - for (TextDisplay hologram : holograms) { - Bukkit.getScheduler().runTask(plugin, hologram::remove); - hologram.remove(); - } - holograms.clear(); - } - - if (clickableHologram != null) - clickableHologram.remove(); - - setPose(setupPose(settings.getPose())); - - - setupHolograms(); - for (TextDisplay hologram : holograms) { - hologram.setBackgroundColor(settings.isHideBackgroundHologram() ? null : settings.getHologramBackground()); - } - - if (settings.isInteractable() && !settings.isHideClickableHologram()) { - if (settings.getCustomInteractableHologram().isEmpty()) { - setupClickableHologram(plugin.getConfig().getString("ClickText")); - } else { - setupClickableHologram(settings.getCustomInteractableHologram()); - } - if (settings.isHideBackgroundHologram()) clickableHologram.setBackgroundColor(null); - if (settings.getHologramBackground() != null) { - clickableHologram.setBackgroundColor(settings.getHologramBackground()); - } - } - - - setSkin(); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HAND, equipment.getHand(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.OFF_HAND, equipment.getOffhand(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HEAD, equipment.getHead(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.CHEST, equipment.getChest(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.LEGS, equipment.getLegs(), true); - super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.FEET, equipment.getBoots(), true); - super.getBukkitEntity().setItemInHand(equipment.getHand()); - } - - private net.minecraft.world.entity.Pose setupPose(Pose pose) { - return switch (pose) { - case SLEEPING -> net.minecraft.world.entity.Pose.SLEEPING; - case SWIMMING -> net.minecraft.world.entity.Pose.SWIMMING; - case CROUCHING -> net.minecraft.world.entity.Pose.CROUCHING; - case SITTING -> { - seat = world.spawn(new Location(world, 0, 0, 0), ArmorStand.class); - seat.setMarker(true); - seat.setVisible(false); - seat.teleport(spawnLoc); - startRiding(((CraftArmorStand) seat).getHandle(), true); - yield net.minecraft.world.entity.Pose.STANDING; - } - case DYING -> { - setHealth(0.0F); // looks like dying - yield net.minecraft.world.entity.Pose.DYING; - } - default -> net.minecraft.world.entity.Pose.STANDING; - }; - } - - public double getPoseOffset(Pose pose) { - return switch (pose) { - case STANDING -> 0.20D; - case SITTING -> 0.20D; - case CROUCHING -> 0.175D; - case SWIMMING -> 0.14D; - case DYING -> 0.05D; - case SLEEPING -> 0.10D; - }; - } - - @Override - public InternalNpc clone() { - return new NPC_v1_20_R2(plugin, world, spawnLoc.clone(), equipment.clone(), settings.clone(), UUID.randomUUID(), target, new ArrayList<>(actions)); - } - - - @Override - public float getYaw() { - return getYRot(); - } - - @Override - public float getPitch() { - return getXRot(); - } -} - diff --git a/v1_20_R4/build.gradle.kts b/v1_20_R4/build.gradle.kts index b1897d81..dc0ed725 100644 --- a/v1_20_R4/build.gradle.kts +++ b/v1_20_R4/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ plugins { id("java") id("io.freefair.lombok") version "9.5.0" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" } repositories { @@ -40,6 +40,8 @@ dependencies { } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + java { toolchain.languageVersion = JavaLanguageVersion.of(21) } diff --git a/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R4.java b/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R4.java index 6d0f2bcf..cc8cfb39 100644 --- a/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R4.java +++ b/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R4.java b/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R4.java index b2f7c166..3460ef19 100644 --- a/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R4.java +++ b/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R4.java b/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R4.java index dc6a1ce3..16ebd081 100644 --- a/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R4.java +++ b/v1_20_R4/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R0/build.gradle.kts b/v1_21_R0/build.gradle.kts index c9245260..9bcd9d9a 100644 --- a/v1_21_R0/build.gradle.kts +++ b/v1_21_R0/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ plugins { id("java") id("io.freefair.lombok") version "9.5.0" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" } repositories { @@ -40,6 +40,8 @@ dependencies { } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + java { toolchain.languageVersion = JavaLanguageVersion.of(21) } diff --git a/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R0.java b/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R0.java index f7090fe9..2b99ad1e 100644 --- a/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R0.java +++ b/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R0.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R0.java b/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R0.java index 6a12d891..5e286f1f 100644 --- a/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R0.java +++ b/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R0.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R0.java b/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R0.java index 361b90d0..56fa6b74 100644 --- a/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R0.java +++ b/v1_21_R0/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R0.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R1/build.gradle.kts b/v1_21_R1/build.gradle.kts index 3ebd69d5..4681d330 100644 --- a/v1_21_R1/build.gradle.kts +++ b/v1_21_R1/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ plugins { id("java") id("io.freefair.lombok") version "9.5.0" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" } repositories { @@ -40,6 +40,9 @@ dependencies { } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + + java { toolchain.languageVersion = JavaLanguageVersion.of(21) } diff --git a/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R1.java b/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R1.java index 814bb300..88de7e66 100644 --- a/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R1.java +++ b/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R1.java b/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R1.java index 27af65fd..989fe909 100644 --- a/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R1.java +++ b/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R1.java b/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R1.java index 760e7844..28b5b981 100644 --- a/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R1.java +++ b/v1_21_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R2/build.gradle.kts b/v1_21_R2/build.gradle.kts index d219f9ea..909348d2 100644 --- a/v1_21_R2/build.gradle.kts +++ b/v1_21_R2/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ plugins { id("java") - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" id("io.freefair.lombok") version "9.5.0" } @@ -40,6 +40,9 @@ dependencies { } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + + java { toolchain.languageVersion = JavaLanguageVersion.of(21) } diff --git a/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R2.java b/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R2.java index ee186203..d0e11ec6 100644 --- a/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R2.java +++ b/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R2.java b/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R2.java index c79f36e9..473aa242 100644 --- a/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R2.java +++ b/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R2.java b/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R2.java index 665f24dc..6eeb4ab4 100644 --- a/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R2.java +++ b/v1_21_R2/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R3/build.gradle.kts b/v1_21_R3/build.gradle.kts index b0d0fd99..5e5f4667 100644 --- a/v1_21_R3/build.gradle.kts +++ b/v1_21_R3/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ plugins { id("java") - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" id("io.freefair.lombok") version "9.5.0" } @@ -40,6 +40,8 @@ dependencies { } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + java { toolchain.languageVersion = JavaLanguageVersion.of(21) } diff --git a/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R3.java b/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R3.java index a98c90d7..05c8ba16 100644 --- a/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R3.java +++ b/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R3.java b/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R3.java index 17dcc40d..faf2b176 100644 --- a/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R3.java +++ b/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R3.java b/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R3.java index fde836bf..9a40d3ff 100644 --- a/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R3.java +++ b/v1_21_R3/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R4/build.gradle.kts b/v1_21_R4/build.gradle.kts index 10910a66..ab260eae 100644 --- a/v1_21_R4/build.gradle.kts +++ b/v1_21_R4/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ plugins { id("java") - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" id("io.freefair.lombok") version "9.5.0" } @@ -40,6 +40,8 @@ dependencies { } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + java { toolchain.languageVersion = JavaLanguageVersion.of(21) } diff --git a/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R4.java b/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R4.java index cb387167..b49f5b05 100644 --- a/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R4.java +++ b/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R4.java b/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R4.java index ea53e676..1ccdd653 100644 --- a/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R4.java +++ b/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R4.java b/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R4.java index 24711976..97b12e9d 100644 --- a/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R4.java +++ b/v1_21_R4/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R5/build.gradle.kts b/v1_21_R5/build.gradle.kts index 7442dde1..a8913290 100644 --- a/v1_21_R5/build.gradle.kts +++ b/v1_21_R5/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ plugins { id("java") - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" id("io.freefair.lombok") version "9.5.0" } @@ -41,6 +41,8 @@ dependencies { } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + java { toolchain.languageVersion = JavaLanguageVersion.of(21) } diff --git a/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R5.java b/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R5.java index 8741f55d..8a18d47a 100644 --- a/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R5.java +++ b/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R5.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R5.java b/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R5.java index ceda2a9c..1fa8b2c9 100644 --- a/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R5.java +++ b/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R5.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R5.java b/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R5.java index 6248b47a..b7432bd7 100644 --- a/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R5.java +++ b/v1_21_R5/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R5.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -133,9 +133,8 @@ public class NPC_v1_21_R5 extends ServerPlayer implements InternalNpc { public NPC_v1_21_R5(CustomNPCs plugin, World world, Location spawnLoc, Equipment equipment, Settings settings, UUID uuid, @Nullable Player target, List actions) { - super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), new GameProfile(uuid, - Utils.getNpcName(settings, uuid), new PropertyMap(ImmutableMultimap.of("textures", new Property( - "textures", settings.getValue(), settings.getSignature())))), ClientInformation.createDefault()); + super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), + createGameProfile(settings, uuid), ClientInformation.createDefault()); this.spawnLoc = spawnLoc; this.equipment = equipment; this.settings = settings; @@ -148,6 +147,12 @@ public NPC_v1_21_R5(CustomNPCs plugin, World world, Location spawnLoc, Equipment this.plugin = plugin; } + private static GameProfile createGameProfile(Settings s, UUID uuid) { + return new GameProfile(uuid, Utils.getNpcName(s, uuid), + new PropertyMap(ImmutableMultimap.of( + "textures", new Property("textures", s.getValue(), s.getSignature())))); + } + @Override public void setPosRot(Location location) { this.setPos(location.getX(), location.getY(), location.getZ()); @@ -177,7 +182,7 @@ public void createNPC() { } }); - setSkin(); + setSkinFlags(); setPosRot(spawnLoc); this.getBukkitEntity().setInvulnerable(true); this.getBukkitEntity().setNoDamageTicks(Integer.MAX_VALUE); @@ -199,7 +204,7 @@ public void createNPC() { injectionManager.setup(); } - public void setSkin() { + public void setSkinFlags() { byte bitmask = (byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40); super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, bitmask); } @@ -297,7 +302,7 @@ public void injectPlayer(Player p) { ClientboundMoveEntityPacket rotation = new ClientboundMoveEntityPacket.Rot(this.getBukkitEntity().getEntityId(), (byte) (getYRot() * 256 / 360), (byte) (getXRot() * 256 / 360), true); - setSkin(); + setSkinFlags(); ClientboundSetPassengersPacket hideName = new ClientboundSetPassengersPacket(this); ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; @@ -456,7 +461,8 @@ public void lookAt(Location loc) { @Override public void updateSkin() { - setSkin(); + super.gameProfile = createGameProfile(settings, uniqueID); + setSkinFlags(); } @Override @@ -502,7 +508,7 @@ public void reloadSettings() { } - setSkin(); + setSkinFlags(); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HAND, equipment.getHand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.OFF_HAND, equipment.getOffhand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HEAD, equipment.getHead(), true); diff --git a/v1_21_R6/build.gradle.kts b/v1_21_R6/build.gradle.kts index 83002ac5..4e9f7844 100644 --- a/v1_21_R6/build.gradle.kts +++ b/v1_21_R6/build.gradle.kts @@ -22,7 +22,7 @@ plugins { id("java") - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" id("io.freefair.lombok") version "9.5.0" } @@ -40,12 +40,16 @@ dependencies { } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + java { - toolchain.languageVersion = JavaLanguageVersion.of(21) + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } } compileJava { - options.release = 21 + options.release = 25 } jar { diff --git a/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R6.java b/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R6.java index b155f332..c25e20e2 100644 --- a/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R6.java +++ b/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_21_R6.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R6.java b/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R6.java index 4d490b51..147c69f0 100644 --- a/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R6.java +++ b/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_21_R6.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R6.java b/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R6.java index d24f8407..09733c41 100644 --- a/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R6.java +++ b/v1_21_R6/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_21_R6.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -133,9 +133,8 @@ public class NPC_v1_21_R6 extends ServerPlayer implements InternalNpc { public NPC_v1_21_R6(CustomNPCs plugin, World world, Location spawnLoc, Equipment equipment, Settings settings, UUID uuid, @Nullable Player target, List actions) { - super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), new GameProfile(uuid, - Utils.getNpcName(settings, uuid), new PropertyMap(ImmutableMultimap.of("textures", new Property( - "textures", settings.getValue(), settings.getSignature())))), ClientInformation.createDefault()); + super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), + createGameProfile(settings, uuid), ClientInformation.createDefault()); this.spawnLoc = spawnLoc; this.equipment = equipment; this.settings = settings; @@ -148,6 +147,12 @@ public NPC_v1_21_R6(CustomNPCs plugin, World world, Location spawnLoc, Equipment this.plugin = plugin; } + private static GameProfile createGameProfile(Settings s, UUID uuid) { + return new GameProfile(uuid, Utils.getNpcName(s, uuid), + new PropertyMap(ImmutableMultimap.of( + "textures", new Property("textures", s.getValue(), s.getSignature())))); + } + @Override public void setPosRot(Location location) { this.setPos(location.getX(), location.getY(), location.getZ()); @@ -177,7 +182,7 @@ public void createNPC() { } }); - setSkin(); + setSkinFlags(); setPosRot(spawnLoc); this.getBukkitEntity().setInvulnerable(true); this.getBukkitEntity().setNoDamageTicks(Integer.MAX_VALUE); @@ -199,7 +204,7 @@ public void createNPC() { injectionManager.setup(); } - public void setSkin() { + public void setSkinFlags() { byte bitmask = (byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40); super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, bitmask); } @@ -297,7 +302,7 @@ public void injectPlayer(Player p) { ClientboundMoveEntityPacket rotation = new ClientboundMoveEntityPacket.Rot(this.getBukkitEntity().getEntityId(), (byte) (getYRot() * 256 / 360), (byte) (getXRot() * 256 / 360), true); - setSkin(); + setSkinFlags(); ClientboundSetPassengersPacket hideName = new ClientboundSetPassengersPacket(this); ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; @@ -456,7 +461,8 @@ public void lookAt(Location loc) { @Override public void updateSkin() { - setSkin(); + super.gameProfile = createGameProfile(settings, uniqueID); + setSkinFlags(); } @Override @@ -502,7 +508,7 @@ public void reloadSettings() { } - setSkin(); + setSkinFlags(); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HAND, equipment.getHand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.OFF_HAND, equipment.getOffhand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HEAD, equipment.getHead(), true); diff --git a/v1_20_R3/build.gradle.kts b/v26_1_R1/build.gradle.kts similarity index 78% rename from v1_20_R3/build.gradle.kts rename to v26_1_R1/build.gradle.kts index 24a646af..c753293d 100644 --- a/v1_20_R3/build.gradle.kts +++ b/v26_1_R1/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ plugins { id("java") id("io.freefair.lombok") version "9.5.0" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" } repositories { @@ -36,21 +36,21 @@ repositories { dependencies { compileOnly("me.clip:placeholderapi:2.12.1") compileOnly(project(":core")) - paperweight.paperDevBundle("1.20.3-R0.1-SNAPSHOT") + paperweight.paperDevBundle("26.1.2.build.+") } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } + toolchain.languageVersion = JavaLanguageVersion.of(25) } compileJava { - options.release = 21 + options.release = 25 } jar { - archiveClassifier = "v1_20_R3" + archiveClassifier = "v26_1_R1" } } \ No newline at end of file diff --git a/v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R1.java b/v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v26_1_R1.java similarity index 83% rename from v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R1.java rename to v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v26_1_R1.java index 18d96dd7..63668497 100644 --- a/v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R1.java +++ b/v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v26_1_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,24 +23,19 @@ package dev.foxikle.customnpcs.versions; import net.minecraft.network.Connection; -import net.minecraft.network.PacketListener; import net.minecraft.network.protocol.PacketFlow; /** * A fake connection for the NPCs */ -public class FakeConnection_v1_20_R1 extends Connection { +public class FakeConnection_v26_1_R1 extends Connection { /** *

Creates a fake Connection for NPC *

+ * * @param enumprotocoldirection The protocol direction */ - public FakeConnection_v1_20_R1(PacketFlow enumprotocoldirection) { + public FakeConnection_v26_1_R1(PacketFlow enumprotocoldirection) { super(enumprotocoldirection); } - - @Override - public void setListener(PacketListener packetListener) { - - } } diff --git a/v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R3.java b/v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v26_1_R1.java similarity index 85% rename from v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R3.java rename to v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v26_1_R1.java index f8833fc5..059f413e 100644 --- a/v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R3.java +++ b/v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v26_1_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,22 +33,26 @@ /** * A fake packet listener for the NPCs */ -public class FakeListener_v1_20_R3 extends ServerGamePacketListenerImpl { +public class FakeListener_v26_1_R1 extends ServerGamePacketListenerImpl { /** *

Creates a fake ServerGamePacketListenerImpl for NPCs *

- * @param server The server + * + * @param server The server * @param connection The connection - * @param npc The NPC + * @param npc The NPC */ - public FakeListener_v1_20_R3(MinecraftServer server, Connection connection, ServerPlayer npc) { - super(server, connection, npc, CommonListenerCookie.createInitial(npc.gameProfile)); + public FakeListener_v26_1_R1(MinecraftServer server, Connection connection, ServerPlayer npc) { + super(server, connection, npc, CommonListenerCookie.createInitial(npc.gameProfile, false)); } + /** *

Overrides the default ServerGamePacketListenerImpl's send packet method *

+ * * @param packet The packet that won't be sent. */ @Override - public void send(@NotNull Packet packet) {} + public void send(@NotNull Packet packet) { + } } diff --git a/v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R3.java b/v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v26_1_R1.java similarity index 75% rename from v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R3.java rename to v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v26_1_R1.java index 134ae629..4e4da080 100644 --- a/v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R3.java +++ b/v26_1_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v26_1_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,9 +22,14 @@ package dev.foxikle.customnpcs.versions; + +import com.google.common.collect.ImmutableMultimap; +import com.google.gson.JsonParser; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.JsonOps; import dev.foxikle.customnpcs.actions.Action; import dev.foxikle.customnpcs.api.Pose; import dev.foxikle.customnpcs.data.Equipment; @@ -33,6 +38,7 @@ import dev.foxikle.customnpcs.internal.InjectionManager; import dev.foxikle.customnpcs.internal.LookAtAnchor; import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; +import dev.foxikle.customnpcs.internal.utils.Msg; import dev.foxikle.customnpcs.internal.utils.Utils; import lombok.Getter; import lombok.Setter; @@ -40,6 +46,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.json.JSONComponentSerializer; import net.minecraft.commands.arguments.EntityAnchorArgument; +import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.game.*; @@ -51,18 +58,19 @@ import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.MoverType; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.CraftServer; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftArmorStand; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftTextDisplay; -import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftArmorStand; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftTextDisplay; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.*; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.scheduler.BukkitRunnable; @@ -77,12 +85,24 @@ /** * The object representing the NPC */ -public class NPC_v1_20_R3 extends ServerPlayer implements InternalNpc { +public class NPC_v26_1_R1 extends ServerPlayer implements InternalNpc { // reflection for data accessors - private final EntityDataAccessor TEXT_DISPLAY_ACCESSOR; - @Getter - private final Particle spawnParticle = Particle.EXPLOSION_LARGE; + private static final EntityDataAccessor TEXT_DISPLAY_ACCESSOR; + + static { + try { + // needs reflection because its private. + Field field = net.minecraft.world.entity.Display.TextDisplay.class.getDeclaredField("DATA_TEXT_ID"); + field.setAccessible(true); + TEXT_DISPLAY_ACCESSOR = + (EntityDataAccessor) field.get(new EntityDataAccessor<>(0, + EntityDataSerializers.COMPONENT)); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + @Getter private final UUID uniqueID; private final CustomNPCs plugin; @@ -98,11 +118,11 @@ public class NPC_v1_20_R3 extends ServerPlayer implements InternalNpc { @Getter @Setter private Location spawnLoc; - private @Nullable ArmorStand seat; @Getter - private TextDisplay clickableHologram; + private @Nullable TextDisplay clickableHologram; @Getter private List holograms; + private @Nullable ArmorStand seat; @Getter @Setter private Player target; @@ -112,28 +132,29 @@ public class NPC_v1_20_R3 extends ServerPlayer implements InternalNpc { private String clickableName = "ERROR"; private InjectionManager injectionManager; - public NPC_v1_20_R3(CustomNPCs plugin, World world, Location spawnLoc, Equipment equipment, Settings settings, UUID uniqueID, @Nullable Player target, List actions) { - super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), new GameProfile(uniqueID, Utils.getNpcName(settings, uniqueID)), ClientInformation.createDefault()); + public NPC_v26_1_R1(CustomNPCs plugin, World world, Location spawnLoc, Equipment equipment, Settings settings, + UUID uuid, @Nullable Player target, List actions) { + super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), + createGameProfile(settings, uuid), ClientInformation.createDefault()); this.spawnLoc = spawnLoc; this.equipment = equipment; this.settings = settings; this.world = spawnLoc.getWorld(); - this.uniqueID = uniqueID; + this.uniqueID = uuid; this.target = target; this.actions = actions; - super.connection = new FakeListener_v1_20_R3(((CraftServer) Bukkit.getServer()).getServer(), new FakeConnection_v1_20_R3(PacketFlow.CLIENTBOUND), this); + super.connection = new FakeListener_v26_1_R1(((CraftServer) Bukkit.getServer()).getServer(), + new FakeConnection_v26_1_R1(PacketFlow.CLIENTBOUND), this); this.plugin = plugin; + } - // aM - try { - Field field = net.minecraft.world.entity.Display.TextDisplay.class.getDeclaredField("aM"); - field.setAccessible(true); - TEXT_DISPLAY_ACCESSOR = (EntityDataAccessor) field.get(new EntityDataAccessor<>(0, EntityDataSerializers.COMPONENT)); - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new RuntimeException(e); - } + private static GameProfile createGameProfile(Settings s, UUID uuid) { + return new GameProfile(uuid, Utils.getNpcName(s, uuid), + new PropertyMap(ImmutableMultimap.of( + "textures", new Property("textures", s.getValue(), s.getSignature())))); } + @Override public void setPosRot(Location location) { this.setPos(location.getX(), location.getY(), location.getZ()); this.setXRot(location.getPitch()); @@ -152,25 +173,28 @@ public void createNPC() { } setupHolograms(); - if (settings.isInteractable() && !settings.isHideClickableHologram()) { - if (settings.getCustomInteractableHologram() == null || settings.getCustomInteractableHologram().isEmpty()) { - setupClickableHologram(plugin.getConfig().getString("ClickText")); - } else { - setupClickableHologram(settings.getCustomInteractableHologram()); + Bukkit.getScheduler().runTask(plugin, () -> { + if (settings.isInteractable() && !settings.isHideClickableHologram()) { + if (settings.getCustomInteractableHologram() == null || settings.getCustomInteractableHologram().isEmpty()) { + setupClickableHologram(plugin.getConfig().getString("ClickText")); + } else { + setupClickableHologram(settings.getCustomInteractableHologram()); + } } - } + }); - setSkin(); + setSkinFlags(); setPosRot(spawnLoc); this.getBukkitEntity().setInvulnerable(true); this.getBukkitEntity().setNoDamageTicks(Integer.MAX_VALUE); - super.getCommandSenderWorld().addFreshEntity(this); + ((CraftWorld) getWorld()).getHandle().addFreshEntity(this); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HAND, equipment.getHand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.OFF_HAND, equipment.getOffhand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HEAD, equipment.getHead(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.CHEST, equipment.getChest(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.LEGS, equipment.getLegs(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.FEET, equipment.getBoots(), true); + super.getBukkitEntity().addScoreboardTag("NPC"); super.getBukkitEntity().setItemInHand(equipment.getHand()); setPose(setupPose(settings.getPose())); @@ -181,9 +205,7 @@ public void createNPC() { injectionManager.setup(); } - public void setSkin() { - super.getGameProfile().getProperties().removeAll("textures"); - super.getGameProfile().getProperties().put("textures", new Property("textures", settings.getValue(), settings.getSignature())); + public void setSkinFlags() { byte bitmask = (byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40); super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, bitmask); } @@ -191,7 +213,9 @@ public void setSkin() { public void setupHolograms() { final double space = 0.28; double startingOffset = getPoseOffset(settings.getPose()); - boolean displayClickable = settings.isInteractable() && !settings.isHideClickableHologram() && plugin.getConfig().getBoolean("DisplayClickText"); + boolean displayClickable = + settings.isInteractable() && !settings.isHideClickableHologram() && plugin.getConfig().getBoolean( + "DisplayClickText"); if (displayClickable) { startingOffset += space; } @@ -210,7 +234,7 @@ public void setupHolograms() { hologram.getTransformation().getRightRotation() )); holograms.add(hologram); - ((CraftTextDisplay) hologram).getHandle().startRiding(this, true); + ((CraftTextDisplay) hologram).getHandle().startRiding(this, true, true); } this.holograms = holograms.reversed(); } @@ -230,7 +254,7 @@ public void setupClickableHologram(String name) { clickableHologram.getTransformation().getScale(), clickableHologram.getTransformation().getRightRotation() )); - ((CraftTextDisplay) clickableHologram).getHandle().startRiding(this, true); + ((CraftTextDisplay) clickableHologram).getHandle().startRiding(this, true, true); } public Location getCurrentLocation() { @@ -249,29 +273,46 @@ public boolean removeAction(Action actionImpl) { } public void injectPlayer(Player p) { - if (p.getWorld() != spawnLoc.getWorld()) return; - List> stuffs = new ArrayList<>(); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, CraftItemStack.asNMSCopy(equipment.getHand()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(equipment.getOffhand()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.HEAD, CraftItemStack.asNMSCopy(equipment.getHead()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.CHEST, CraftItemStack.asNMSCopy(equipment.getChest()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.LEGS, CraftItemStack.asNMSCopy(equipment.getLegs()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.FEET, CraftItemStack.asNMSCopy(equipment.getBoots()))); - - ClientboundPlayerInfoUpdatePacket playerInfoAdd = new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, this); - ClientboundAddEntityPacket namedEntitySpawn = new ClientboundAddEntityPacket(this); - ClientboundPlayerInfoRemovePacket playerInforemove = new ClientboundPlayerInfoRemovePacket(Collections.singletonList(super.getUUID())); + + if (world != p.getWorld()) { + return; + } + + List> stuffs = new ArrayList<>(); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, + CraftItemStack.asNMSCopy(equipment.getHand()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, + CraftItemStack.asNMSCopy(equipment.getOffhand()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.HEAD, + CraftItemStack.asNMSCopy(equipment.getHead()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.CHEST, + CraftItemStack.asNMSCopy(equipment.getChest()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.LEGS, + CraftItemStack.asNMSCopy(equipment.getLegs()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.FEET, + CraftItemStack.asNMSCopy(equipment.getBoots()))); + + ClientboundPlayerInfoUpdatePacket playerInfoAdd = + new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, this); + ClientboundAddEntityPacket namedEntitySpawn = new ClientboundAddEntityPacket(getId(), uniqueID, spawnLoc.x(), + spawnLoc.y(), spawnLoc.z(), + getYRot(), getXRot(), net.minecraft.world.entity.EntityType.PLAYER, 0, new Vec3(0, 0, 0), getYRot()); + ClientboundPlayerInfoRemovePacket playerInforemove = + new ClientboundPlayerInfoRemovePacket(Collections.singletonList(super.getUUID())); ClientboundSetEquipmentPacket equipmentPacket = new ClientboundSetEquipmentPacket(super.getId(), stuffs); - ClientboundMoveEntityPacket rotation = new ClientboundMoveEntityPacket.Rot(this.getBukkitEntity().getEntityId(), (byte) (getYaw() * 256 / 360), (byte) (getXRot() * 256 / 360), true); + ClientboundMoveEntityPacket rotation = + new ClientboundMoveEntityPacket.Rot(this.getBukkitEntity().getEntityId(), + (byte) (getYRot() * 256 / 360), (byte) (getXRot() * 256 / 360), true); + setSkinFlags(); ClientboundSetPassengersPacket hideName = new ClientboundSetPassengersPacket(this); - setSkin(); ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; + connection.send(playerInfoAdd); connection.send(namedEntitySpawn); connection.send(equipmentPacket); connection.send(rotation); connection.send(hideName); - super.getEntityData().refresh(((CraftPlayer) p).getHandle()); + connection.send(new ClientboundSetEntityDataPacket(getId(), super.getEntityData().packAll())); if (seat != null) { connection.send(new ClientboundSetPassengersPacket(((CraftArmorStand) seat).getHandle())); @@ -283,12 +324,14 @@ public void injectPlayer(Player p) { Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> connection.send(playerInforemove), 30); if (!settings.isUpsideDown()) { - super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) (0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80)); + super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, + (byte) (0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80)); } // create them Bukkit.getScheduler().runTaskLater(plugin, () -> injectHolograms(p), 3); injectHolograms(p); + // we only want to update them if the server is running placeholder API if (plugin.papi) { if (loops.containsKey(p.getUniqueId())) { @@ -338,38 +381,41 @@ private void injectHolograms(Player p) { } private Packet createMojComponent(String clickableText, TextDisplay clickableHologram) { - List> meta = ((CraftTextDisplay) clickableHologram).getHandle().getEntityData().getNonDefaultValues(); - String serialized_component = JSONComponentSerializer.json().serialize(plugin.getMiniMessage().deserialize(clickableText)); - net.minecraft.network.chat.Component clickableComponent = net.minecraft.network.chat.Component.Serializer.fromJson(serialized_component); + List> meta = + ((CraftTextDisplay) clickableHologram).getHandle().getEntityData().getNonDefaultValues(); + String serialized_component = JSONComponentSerializer.json().serialize(Msg.format(clickableText)); + net.minecraft.network.chat.Component clickableComponent = ComponentSerialization.CODEC + .decode(JsonOps.INSTANCE, JsonParser.parseString(serialized_component)) + .getOrThrow() + .getFirst(); meta.set(0, SynchedEntityData.DataValue.create(TEXT_DISPLAY_ACCESSOR, clickableComponent)); return new ClientboundSetEntityDataPacket(clickableHologram.getEntityId(), meta); } - /** - *

Despawns the NPC - *

- */ public void remove() { injectionManager.shutDown(); loops.forEach((uuid1, integer) -> Bukkit.getScheduler().cancelTask(integer)); loops.clear(); + interpolation.cancel(); List> packets = new ArrayList<>(); if (holograms != null) { for (TextDisplay hologram : holograms) { packets.add(new ClientboundRemoveEntitiesPacket(hologram.getEntityId())); hologram.remove(); } + holograms.clear(); } + if (seat != null) { seat.remove(); } + if (clickableHologram != null) { packets.add(new ClientboundRemoveEntitiesPacket(clickableHologram.getEntityId())); clickableHologram.remove(); } packets.add(new ClientboundRemoveEntitiesPacket(super.getId())); - super.remove(RemovalReason.DISCARDED); for (Player p : Bukkit.getOnlinePlayers()) { ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; @@ -391,6 +437,7 @@ public void moveTo(Vector v) { @Override public void teleport(Location loc) { + if (isRemoved()) return; teleportTo(loc.x(), loc.y(), loc.z()); spawnLoc = loc; } @@ -402,10 +449,10 @@ public void delete() { @Override public void lookAt(LookAtAnchor anchor, Entity e) { switch (anchor) { - case HEAD -> - super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), EntityAnchorArgument.Anchor.EYES); - case FEET -> - super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), EntityAnchorArgument.Anchor.FEET); + case HEAD -> super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), + EntityAnchorArgument.Anchor.EYES); + case FEET -> super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), + EntityAnchorArgument.Anchor.FEET); } } @@ -415,7 +462,8 @@ public void lookAt(Location loc) { @Override public void updateSkin() { - setSkin(); + super.gameProfile = createGameProfile(settings, uniqueID); + setSkinFlags(); } @Override @@ -461,7 +509,7 @@ public void reloadSettings() { } - setSkin(); + setSkinFlags(); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HAND, equipment.getHand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.OFF_HAND, equipment.getOffhand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HEAD, equipment.getHead(), true); @@ -469,6 +517,7 @@ public void reloadSettings() { super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.LEGS, equipment.getLegs(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.FEET, equipment.getBoots(), true); super.getBukkitEntity().setItemInHand(equipment.getHand()); + lookAt(Utils.calcLocation(this)); } @Override @@ -485,6 +534,25 @@ public void setXRotation(float f) { lookAt(Utils.calcLocation(this)); } + + @Override + public float getYaw() { + return getYRot(); + } + + @Override + public float getPitch() { + return getXRot(); + } + + /** + * @return Paper goobery + */ + @Override + public Particle getSpawnParticle() { + return Particle.EXPLOSION; + } + private net.minecraft.world.entity.Pose setupPose(Pose pose) { return switch (pose) { case SLEEPING -> net.minecraft.world.entity.Pose.SLEEPING; @@ -495,18 +563,18 @@ private net.minecraft.world.entity.Pose setupPose(Pose pose) { seat.setMarker(true); seat.setVisible(false); seat.teleport(spawnLoc); - startRiding(((CraftArmorStand) seat).getHandle(), true); + startRiding(((CraftArmorStand) seat).getHandle(), true, true); yield net.minecraft.world.entity.Pose.STANDING; } case DYING -> { - setHealth(0.0F); // looks like dying + setHealth(0.0F); // it looks like dying yield net.minecraft.world.entity.Pose.DYING; } default -> net.minecraft.world.entity.Pose.STANDING; }; } - public double getPoseOffset(Pose pose) { + private double getPoseOffset(Pose pose) { return switch (pose) { case STANDING -> 0.20D; case SITTING -> 0.20D; @@ -517,21 +585,9 @@ public double getPoseOffset(Pose pose) { }; } - @Override public InternalNpc clone() { - return new NPC_v1_20_R3(plugin, world, spawnLoc.clone(), equipment.clone(), settings.clone(), UUID.randomUUID(), target, new ArrayList<>(actions)); - } - - - @Override - public float getYaw() { - return getYRot(); - } - - @Override - public float getPitch() { - return getXRot(); + return new NPC_v26_1_R1(plugin, world, spawnLoc.clone(), equipment.clone(), settings.clone(), + UUID.randomUUID(), target, new ArrayList<>(actions)); } } - diff --git a/v1_20_R2/build.gradle.kts b/v26_2_R1/build.gradle.kts similarity index 78% rename from v1_20_R2/build.gradle.kts rename to v26_2_R1/build.gradle.kts index 66f32b49..fd855bd9 100644 --- a/v1_20_R2/build.gradle.kts +++ b/v26_2_R1/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ plugins { id("java") id("io.freefair.lombok") version "9.5.0" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" + id("io.papermc.paperweight.userdev") version "2.0.0-SNAPSHOT" } repositories { @@ -36,21 +36,21 @@ repositories { dependencies { compileOnly("me.clip:placeholderapi:2.12.1") compileOnly(project(":core")) - paperweight.paperDevBundle("1.20.2-R0.1-SNAPSHOT") + paperweight.paperDevBundle("26.2-rc-2.build.+") } tasks { + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } + toolchain.languageVersion = JavaLanguageVersion.of(25) } compileJava { - options.release = 21 + options.release = 25 } jar { - archiveClassifier = "v1_20_R2" + archiveClassifier = "v26_1_R1" } } \ No newline at end of file diff --git a/v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R3.java b/v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v26_2_R1.java similarity index 83% rename from v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R3.java rename to v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v26_2_R1.java index b6190706..ee8e6902 100644 --- a/v1_20_R3/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v1_20_R3.java +++ b/v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeConnection_v26_2_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,24 +23,19 @@ package dev.foxikle.customnpcs.versions; import net.minecraft.network.Connection; -import net.minecraft.network.PacketListener; import net.minecraft.network.protocol.PacketFlow; /** * A fake connection for the NPCs */ -public class FakeConnection_v1_20_R3 extends Connection { +public class FakeConnection_v26_2_R1 extends Connection { /** *

Creates a fake Connection for NPC *

+ * * @param enumprotocoldirection The protocol direction */ - public FakeConnection_v1_20_R3(PacketFlow enumprotocoldirection) { + public FakeConnection_v26_2_R1(PacketFlow enumprotocoldirection) { super(enumprotocoldirection); } - - @Override - public void setListener(PacketListener packetListener) { - - } } diff --git a/v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R2.java b/v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v26_2_R1.java similarity index 85% rename from v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R2.java rename to v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v26_2_R1.java index 749bf3eb..a0b622cd 100644 --- a/v1_20_R2/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v1_20_R2.java +++ b/v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/FakeListener_v26_2_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,22 +33,26 @@ /** * A fake packet listener for the NPCs */ -public class FakeListener_v1_20_R2 extends ServerGamePacketListenerImpl { +public class FakeListener_v26_2_R1 extends ServerGamePacketListenerImpl { /** *

Creates a fake ServerGamePacketListenerImpl for NPCs *

- * @param server The server + * + * @param server The server * @param connection The connection - * @param npc The NPC + * @param npc The NPC */ - public FakeListener_v1_20_R2(MinecraftServer server, Connection connection, ServerPlayer npc) { - super(server, connection, npc, CommonListenerCookie.createInitial(npc.gameProfile)); + public FakeListener_v26_2_R1(MinecraftServer server, Connection connection, ServerPlayer npc) { + super(server, connection, npc, CommonListenerCookie.createInitial(npc.gameProfile, false)); } + /** *

Overrides the default ServerGamePacketListenerImpl's send packet method *

+ * * @param packet The packet that won't be sent. */ @Override - public void send(@NotNull Packet packet) {} + public void send(@NotNull Packet packet) { + } } diff --git a/v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R1.java b/v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v26_2_R1.java similarity index 72% rename from v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R1.java rename to v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v26_2_R1.java index cd1fc194..373d23a2 100644 --- a/v1_20_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v1_20_R1.java +++ b/v26_2_R1/src/main/java/dev/foxikle/customnpcs/versions/NPC_v26_2_R1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024-2025. Foxikle + * Copyright (c) 2024-2026. Foxikle * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,9 +22,14 @@ package dev.foxikle.customnpcs.versions; + +import com.google.common.collect.ImmutableMultimap; +import com.google.gson.JsonParser; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.JsonOps; import dev.foxikle.customnpcs.actions.Action; import dev.foxikle.customnpcs.api.Pose; import dev.foxikle.customnpcs.data.Equipment; @@ -33,6 +38,7 @@ import dev.foxikle.customnpcs.internal.InjectionManager; import dev.foxikle.customnpcs.internal.LookAtAnchor; import dev.foxikle.customnpcs.internal.interfaces.InternalNpc; +import dev.foxikle.customnpcs.internal.utils.Msg; import dev.foxikle.customnpcs.internal.utils.Utils; import lombok.Getter; import lombok.Setter; @@ -40,12 +46,16 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.json.JSONComponentSerializer; import net.minecraft.commands.arguments.EntityAnchorArgument; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.game.*; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ClientInformation; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.world.InteractionHand; @@ -56,13 +66,13 @@ import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R1.CraftServer; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftArmorStand; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftTextDisplay; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftArmorStand; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.entity.CraftTextDisplay; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.*; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.scheduler.BukkitRunnable; @@ -77,31 +87,44 @@ /** * The object representing the NPC */ -public class NPC_v1_20_R1 extends ServerPlayer implements InternalNpc { +public class NPC_v26_2_R1 extends ServerPlayer implements InternalNpc { + + // reflection for data accessors + private static final EntityDataAccessor TEXT_DISPLAY_ACCESSOR; + + static { + try { + // needs reflection because its private. + Field field = net.minecraft.world.entity.Display.TextDisplay.class.getDeclaredField("DATA_TEXT_ID"); + field.setAccessible(true); + TEXT_DISPLAY_ACCESSOR = + (EntityDataAccessor) field.get(new EntityDataAccessor<>(0, + EntityDataSerializers.COMPONENT)); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException(e); + } + } @Getter private final UUID uniqueID; - @Getter - private final Particle spawnParticle = Particle.EXPLOSION_LARGE; private final CustomNPCs plugin; @Getter private final World world; - private final EntityDataAccessor TEXT_DISPLAY_ACCESSOR; private final Map loops = new HashMap<>(); @Getter @Setter - private Equipment equipment; + private Settings settings; @Getter @Setter - private Settings settings; + private Equipment equipment; @Getter @Setter private Location spawnLoc; - private @Nullable ArmorStand seat; @Getter - private TextDisplay clickableHologram; + private @Nullable TextDisplay clickableHologram; @Getter private List holograms; + private @Nullable ArmorStand seat; @Getter @Setter private Player target; @@ -111,39 +134,26 @@ public class NPC_v1_20_R1 extends ServerPlayer implements InternalNpc { private String clickableName = "ERROR"; private InjectionManager injectionManager; - /** - *

Gets a new NPC - *

- * - * @param actions The actions for the NPC to execute on interaction - * @param plugin The instance of the Main class - * @param uniqueID The UUID of the NPC (Should be the same as the gameprofile's uuid) - * @param spawnLoc The location to spawn the NPC - * @param target The Entity the NPC should follow - * @param world The world the NPC resides in - * @param equipment The NPC's equipment - * @param settings The NPC's settings - */ - public NPC_v1_20_R1(CustomNPCs plugin, World world, Location spawnLoc, Equipment equipment, Settings settings, UUID uniqueID, @Nullable Player target, List actions) { - super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), new GameProfile(uniqueID, Utils.getNpcName(settings, uniqueID))); + public NPC_v26_2_R1(CustomNPCs plugin, World world, Location spawnLoc, Equipment equipment, Settings settings, + UUID uuid, @Nullable Player target, List actions) { + super(((CraftServer) Bukkit.getServer()).getServer(), ((CraftWorld) world).getHandle(), + createGameProfile(settings, uuid), ClientInformation.createDefault()); this.spawnLoc = spawnLoc; this.equipment = equipment; - this.world = spawnLoc.getWorld(); - this.uniqueID = uniqueID; this.settings = settings; + this.world = spawnLoc.getWorld(); + this.uniqueID = uuid; this.target = target; - this.actions = new ArrayList<>(actions); - super.connection = new FakeListener_v1_20_R1(((CraftServer) Bukkit.getServer()).getServer(), new FakeConnection_v1_20_R1(PacketFlow.CLIENTBOUND), this); + this.actions = actions; + super.connection = new FakeListener_v26_2_R1(((CraftServer) Bukkit.getServer()).getServer(), + new FakeConnection_v26_2_R1(PacketFlow.CLIENTBOUND), this); this.plugin = plugin; + } - //aL is the text data accessor - try { - Field field = net.minecraft.world.entity.Display.TextDisplay.class.getDeclaredField("aL"); - field.setAccessible(true); - TEXT_DISPLAY_ACCESSOR = (EntityDataAccessor) field.get(new EntityDataAccessor<>(0, EntityDataSerializers.COMPONENT)); - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new RuntimeException(e); - } + private static GameProfile createGameProfile(Settings s, UUID uuid) { + return new GameProfile(uuid, Utils.getNpcName(s, uuid), + new PropertyMap(ImmutableMultimap.of( + "textures", new Property("textures", s.getValue(), s.getSignature())))); } @Override @@ -154,7 +164,6 @@ public void setPosRot(Location location) { lookAt(Utils.calcLocation(this)); } - @Override public void createNPC() { if (plugin.npcs.containsKey(uniqueID)) { plugin.getNPCByID(uniqueID).remove(); @@ -165,18 +174,22 @@ public void createNPC() { unsetRemoved(); } - Bukkit.getScheduler().runTask(plugin, this::setupHolograms); + setupHolograms(); Bukkit.getScheduler().runTask(plugin, () -> { if (settings.isInteractable() && !settings.isHideClickableHologram()) { - setupClickableHologram(settings.getCustomInteractableHologram() == null || settings.getCustomInteractableHologram().isEmpty() ? plugin.getConfig().getString("ClickText") : settings.getCustomInteractableHologram()); + if (settings.getCustomInteractableHologram() == null || settings.getCustomInteractableHologram().isEmpty()) { + setupClickableHologram(plugin.getConfig().getString("ClickText")); + } else { + setupClickableHologram(settings.getCustomInteractableHologram()); + } } }); - setSkin(); + setSkinFlags(); setPosRot(spawnLoc); this.getBukkitEntity().setInvulnerable(true); this.getBukkitEntity().setNoDamageTicks(Integer.MAX_VALUE); - super.getCommandSenderWorld().addFreshEntity(this); + ((CraftWorld) getWorld()).getHandle().addFreshEntity(this); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HAND, equipment.getHand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.OFF_HAND, equipment.getOffhand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HEAD, equipment.getHead(), true); @@ -194,9 +207,7 @@ public void createNPC() { injectionManager.setup(); } - public void setSkin() { - super.getGameProfile().getProperties().removeAll("textures"); - super.getGameProfile().getProperties().put("textures", new Property("textures", settings.getValue(), settings.getSignature())); + public void setSkinFlags() { byte bitmask = (byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40); super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, bitmask); } @@ -204,7 +215,9 @@ public void setSkin() { public void setupHolograms() { final double space = 0.28; double startingOffset = getPoseOffset(settings.getPose()); - boolean displayClickable = settings.isInteractable() && !settings.isHideClickableHologram() && plugin.getConfig().getBoolean("DisplayClickText"); + boolean displayClickable = + settings.isInteractable() && !settings.isHideClickableHologram() && plugin.getConfig().getBoolean( + "DisplayClickText"); if (displayClickable) { startingOffset += space; } @@ -215,6 +228,7 @@ public void setupHolograms() { hologram.setInvulnerable(true); hologram.setBillboard(Display.Billboard.CENTER); hologram.addScoreboardTag("npcHologram"); + hologram.setTeleportDuration(settings.getInterpolationDuration()); hologram.setTransformation(new Transformation( new Vector3f(0, (float) y, 0), hologram.getTransformation().getLeftRotation(), @@ -222,12 +236,11 @@ public void setupHolograms() { hologram.getTransformation().getRightRotation() )); holograms.add(hologram); - ((CraftTextDisplay) hologram).getHandle().startRiding(this, true); + ((CraftTextDisplay) hologram).getHandle().startRiding(this, true, true); } this.holograms = holograms.reversed(); } - @Override public void setupClickableHologram(String name) { clickableName = name; clickableHologram = (TextDisplay) spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.TEXT_DISPLAY); @@ -235,7 +248,7 @@ public void setupClickableHologram(String name) { clickableHologram.setBillboard(Display.Billboard.CENTER); clickableHologram.text(Component.empty()); clickableHologram.addScoreboardTag("npcHologram"); - // teleport interpolation isnt a thing in this version :( + clickableHologram.setTeleportDuration(settings.getInterpolationDuration()); clickableHologram.setTransformation(new Transformation( new Vector3f(0, (float) getPoseOffset(settings.getPose()), 0), @@ -243,10 +256,9 @@ public void setupClickableHologram(String name) { clickableHologram.getTransformation().getScale(), clickableHologram.getTransformation().getRightRotation() )); - ((CraftTextDisplay) clickableHologram).getHandle().startRiding(this, true); + ((CraftTextDisplay) clickableHologram).getHandle().startRiding(this, true, true); } - @Override public Location getCurrentLocation() { if (seat != null) { return seat.getLocation(); @@ -254,62 +266,56 @@ public Location getCurrentLocation() { return super.getBukkitEntity().getLocation(); } - /** - *

Adds an action to the NPC's actions - *

- * - * @param actionImpl The action to add - */ - @Override public void addAction(Action actionImpl) { actions.add(actionImpl); } - /** - *

Removes an action from the NPC's actions - *

- * - * @param actionImpl The action to remove - * @return if it was successfully removed - */ - @Override public boolean removeAction(Action actionImpl) { return actions.remove(actionImpl); } - - /** - *

Injects packets into the specified player's connection - *

- * - * @param p The player to inject - */ - @Override public void injectPlayer(Player p) { - if (p.getWorld() != spawnLoc.getWorld()) return; + + if (world != p.getWorld()) { + return; + } + List> stuffs = new ArrayList<>(); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, CraftItemStack.asNMSCopy(equipment.getHand()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, CraftItemStack.asNMSCopy(equipment.getOffhand()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.HEAD, CraftItemStack.asNMSCopy(equipment.getHead()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.CHEST, CraftItemStack.asNMSCopy(equipment.getChest()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.LEGS, CraftItemStack.asNMSCopy(equipment.getLegs()))); - stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.FEET, CraftItemStack.asNMSCopy(equipment.getBoots()))); - - ClientboundPlayerInfoUpdatePacket playerInfoAdd = new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, this); - ClientboundAddPlayerPacket namedEntitySpawn = new ClientboundAddPlayerPacket(this); - ClientboundPlayerInfoRemovePacket playerInforemove = new ClientboundPlayerInfoRemovePacket(Collections.singletonList(super.getUUID())); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.MAINHAND, + CraftItemStack.asNMSCopy(equipment.getHand()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.OFFHAND, + CraftItemStack.asNMSCopy(equipment.getOffhand()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.HEAD, + CraftItemStack.asNMSCopy(equipment.getHead()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.CHEST, + CraftItemStack.asNMSCopy(equipment.getChest()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.LEGS, + CraftItemStack.asNMSCopy(equipment.getLegs()))); + stuffs.add(new Pair<>(net.minecraft.world.entity.EquipmentSlot.FEET, + CraftItemStack.asNMSCopy(equipment.getBoots()))); + + ClientboundPlayerInfoUpdatePacket playerInfoAdd = + new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, this); + ClientboundAddEntityPacket namedEntitySpawn = new ClientboundAddEntityPacket(getId(), uniqueID, spawnLoc.x(), + spawnLoc.y(), spawnLoc.z(), + getYRot(), getXRot(), BuiltInRegistries.ENTITY_TYPE.getValue(Identifier.parse("minecraft:player")), 0 + , new Vec3(0, 0, 0), getYRot()); + ClientboundPlayerInfoRemovePacket playerInforemove = + new ClientboundPlayerInfoRemovePacket(Collections.singletonList(super.getUUID())); ClientboundSetEquipmentPacket equipmentPacket = new ClientboundSetEquipmentPacket(super.getId(), stuffs); - ClientboundMoveEntityPacket rotation = new ClientboundMoveEntityPacket.Rot(this.getBukkitEntity().getEntityId(), (byte) (getYRot() * 256 / 360), (byte) (getXRot() * 256 / 360), true); + ClientboundMoveEntityPacket rotation = + new ClientboundMoveEntityPacket.Rot(this.getBukkitEntity().getEntityId(), + (byte) (getYRot() * 256 / 360), (byte) (getXRot() * 256 / 360), true); + setSkinFlags(); ClientboundSetPassengersPacket hideName = new ClientboundSetPassengersPacket(this); - super.detectEquipmentUpdates(); - setSkin(); ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; + connection.send(playerInfoAdd); connection.send(namedEntitySpawn); connection.send(equipmentPacket); connection.send(rotation); connection.send(hideName); - super.getEntityData().refresh(((CraftPlayer) p).getHandle()); + connection.send(new ClientboundSetEntityDataPacket(getId(), super.getEntityData().packAll())); if (seat != null) { connection.send(new ClientboundSetPassengersPacket(((CraftArmorStand) seat).getHandle())); @@ -319,14 +325,16 @@ public void injectPlayer(Player p) { plugin.getLogger().info("[DEBUG] Injected npc '" + this.displayName + "' to player '" + p.getName() + "'"); } - Bukkit.getScheduler().runTaskLaterAsynchronously(CustomNPCs.getInstance(), () -> connection.send(playerInforemove), 30); - + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> connection.send(playerInforemove), 30); if (!settings.isUpsideDown()) { - super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) (0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80)); + super.getEntityData().set(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION, + (byte) (0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80)); } // create them + Bukkit.getScheduler().runTaskLater(plugin, () -> injectHolograms(p), 3); injectHolograms(p); + // we only want to update them if the server is running placeholder API if (plugin.papi) { if (loops.containsKey(p.getUniqueId())) { @@ -362,11 +370,9 @@ private void injectHolograms(Player p) { } List> packets = new ArrayList<>(); - if (holograms != null) { // during settings reload - for (int i = 0; i < holograms.size(); i++) { - TextDisplay hologram = holograms.get(i); - packets.add(createMojComponent(hologramText[i], hologram)); - } + for (int i = 0; i < holograms.size(); i++) { + TextDisplay hologram = holograms.get(i); + packets.add(createMojComponent(hologramText[i], hologram)); } @@ -378,43 +384,45 @@ private void injectHolograms(Player p) { } private Packet createMojComponent(String clickableText, TextDisplay clickableHologram) { - List> meta = ((CraftTextDisplay) clickableHologram).getHandle().getEntityData().getNonDefaultValues(); - String serialized_component = JSONComponentSerializer.json().serialize(plugin.getMiniMessage().deserialize(clickableText)); - net.minecraft.network.chat.Component clickableComponent = net.minecraft.network.chat.Component.Serializer.fromJson(serialized_component); + List> meta = + ((CraftTextDisplay) clickableHologram).getHandle().getEntityData().getNonDefaultValues(); + String serialized_component = JSONComponentSerializer.json().serialize(Msg.format(clickableText)); + net.minecraft.network.chat.Component clickableComponent = ComponentSerialization.CODEC + .decode(JsonOps.INSTANCE, JsonParser.parseString(serialized_component)) + .getOrThrow() + .getFirst(); meta.set(0, SynchedEntityData.DataValue.create(TEXT_DISPLAY_ACCESSOR, clickableComponent)); return new ClientboundSetEntityDataPacket(clickableHologram.getEntityId(), meta); } - @Override public void remove() { injectionManager.shutDown(); loops.forEach((uuid1, integer) -> Bukkit.getScheduler().cancelTask(integer)); loops.clear(); + interpolation.cancel(); List> packets = new ArrayList<>(); - if (holograms != null) { for (TextDisplay hologram : holograms) { packets.add(new ClientboundRemoveEntitiesPacket(hologram.getEntityId())); hologram.remove(); } + holograms.clear(); } + if (seat != null) { seat.remove(); } + if (clickableHologram != null) { packets.add(new ClientboundRemoveEntitiesPacket(clickableHologram.getEntityId())); clickableHologram.remove(); } - var npcpacket = new ClientboundRemoveEntitiesPacket(super.getId()); + packets.add(new ClientboundRemoveEntitiesPacket(super.getId())); super.remove(RemovalReason.DISCARDED); for (Player p : Bukkit.getOnlinePlayers()) { ServerGamePacketListenerImpl connection = ((CraftPlayer) p).getHandle().connection; - connection.send(npcpacket); packets.forEach(connection::send); - if (plugin.isEnabled()) { - Bukkit.getScheduler().runTaskLater(plugin, () -> packets.forEach(connection::send), 1); - } } } @@ -432,12 +440,11 @@ public void moveTo(Vector v) { @Override public void teleport(Location loc) { + if (isRemoved()) return; teleportTo(loc.x(), loc.y(), loc.z()); spawnLoc = loc; } - - @Override public void delete() { plugin.getFileManager().remove(this.uniqueID); } @@ -445,21 +452,21 @@ public void delete() { @Override public void lookAt(LookAtAnchor anchor, Entity e) { switch (anchor) { - case HEAD -> - super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), EntityAnchorArgument.Anchor.EYES); - case FEET -> - super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), EntityAnchorArgument.Anchor.FEET); + case HEAD -> super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), + EntityAnchorArgument.Anchor.EYES); + case FEET -> super.lookAt(EntityAnchorArgument.Anchor.EYES, ((CraftEntity) e).getHandle(), + EntityAnchorArgument.Anchor.FEET); } } - @Override public void lookAt(Location loc) { super.lookAt(EntityAnchorArgument.Anchor.EYES, new Vec3(loc.x(), loc.y(), loc.z())); } @Override public void updateSkin() { - setSkin(); + setSkinFlags(); + super.gameProfile = createGameProfile(settings, uniqueID); } @Override @@ -467,20 +474,6 @@ public void swingArm() { super.swing(InteractionHand.MAIN_HAND, true); } - @Override - public void setYRotation(float f) { - super.setYRot(f); - super.setYBodyRot(f); - super.setYHeadRot(f); - lookAt(Utils.calcLocation(this)); - } - - @Override - public void setXRotation(float f) { - super.setXRot(f); - lookAt(Utils.calcLocation(this)); - } - @Override public void reloadSettings() { if (seat != null) { @@ -519,7 +512,7 @@ public void reloadSettings() { } - setSkin(); + updateSkin(); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HAND, equipment.getHand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.OFF_HAND, equipment.getOffhand(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.HEAD, equipment.getHead(), true); @@ -527,8 +520,41 @@ public void reloadSettings() { super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.LEGS, equipment.getLegs(), true); super.getBukkitEntity().getEquipment().setItem(EquipmentSlot.FEET, equipment.getBoots(), true); super.getBukkitEntity().setItemInHand(equipment.getHand()); + lookAt(Utils.calcLocation(this)); + } + + @Override + public void setYRotation(float f) { + super.setYRot(f); + super.setYBodyRot(f); + super.setYHeadRot(f); + lookAt(Utils.calcLocation(this)); + } + + @Override + public void setXRotation(float f) { + super.setXRot(f); + lookAt(Utils.calcLocation(this)); + } + + + @Override + public float getYaw() { + return getYRot(); } + @Override + public float getPitch() { + return getXRot(); + } + + /** + * @return Paper goobery + */ + @Override + public Particle getSpawnParticle() { + return Particle.EXPLOSION; + } private net.minecraft.world.entity.Pose setupPose(Pose pose) { return switch (pose) { @@ -540,22 +566,22 @@ private net.minecraft.world.entity.Pose setupPose(Pose pose) { seat.setMarker(true); seat.setVisible(false); seat.teleport(spawnLoc); - startRiding(((CraftArmorStand) seat).getHandle(), true); + startRiding(((CraftArmorStand) seat).getHandle(), true, true); yield net.minecraft.world.entity.Pose.STANDING; } case DYING -> { - setHealth(0.0F); // looks like dying + setHealth(0.0F); // it looks like dying yield net.minecraft.world.entity.Pose.DYING; } default -> net.minecraft.world.entity.Pose.STANDING; }; } - public double getPoseOffset(Pose pose) { + private double getPoseOffset(Pose pose) { return switch (pose) { - case STANDING -> 0.63D; - case SITTING -> 0.65D; - case CROUCHING -> 0.45D; + case STANDING -> 0.20D; + case SITTING -> 0.20D; + case CROUCHING -> 0.175D; case SWIMMING -> 0.14D; case DYING -> 0.05D; case SLEEPING -> 0.10D; @@ -564,17 +590,7 @@ public double getPoseOffset(Pose pose) { @Override public InternalNpc clone() { - return new NPC_v1_20_R1(plugin, world, spawnLoc.clone(), equipment.clone(), settings.clone(), UUID.randomUUID(), target, new ArrayList<>(actions)); - } - - @Override - public float getYaw() { - return getYRot(); - } - - @Override - public float getPitch() { - return getXRot(); + return new NPC_v26_2_R1(plugin, world, spawnLoc.clone(), equipment.clone(), settings.clone(), + UUID.randomUUID(), target, new ArrayList<>(actions)); } } -