diff --git a/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/FancyNpcsConfig.java b/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/FancyNpcsConfig.java index 930b1518..b3409cda 100644 --- a/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/FancyNpcsConfig.java +++ b/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/FancyNpcsConfig.java @@ -20,6 +20,14 @@ public interface FancyNpcsConfig { int getNpcUpdateVisibilityInterval(); int getTurnToPlayerDistance(); + + /** + * Sets the distance at which NPCs turn to the player. + * + * @param distance The new distance value + * @return true if the distance was updated successfully, false otherwise + */ + boolean setTurnToPlayerDistance(int distance); boolean isTurnToPlayerResetToInitialDirection(); diff --git a/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/NpcData.java b/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/NpcData.java index 735c850e..4ef19171 100644 --- a/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/NpcData.java +++ b/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/NpcData.java @@ -36,6 +36,7 @@ public class NpcData { private Consumer onClick; private Map> actions; private boolean turnToPlayer; + private int turnToPlayerDistance = -1; // -1 means use the default from config private float interactionCooldown; private float scale; private int visibilityDistance; @@ -57,6 +58,7 @@ public class NpcData { EntityType type, Map equipment, boolean turnToPlayer, + int turnToPlayerDistance, Consumer onClick, Map> actions, float interactionCooldown, @@ -81,6 +83,7 @@ public class NpcData { this.onClick = onClick; this.actions = actions; this.turnToPlayer = turnToPlayer; + this.turnToPlayerDistance = turnToPlayerDistance; this.interactionCooldown = interactionCooldown; this.scale = scale; this.visibilityDistance = visibilityDistance; @@ -108,6 +111,7 @@ public class NpcData { }; this.actions = new ConcurrentHashMap<>(); this.turnToPlayer = false; + this.turnToPlayerDistance = -1; // Use default from config this.interactionCooldown = 0; this.scale = 1; this.visibilityDistance = -1; @@ -321,6 +325,27 @@ public class NpcData { return this; } + /** + * Gets the turn-to-player distance for this NPC. + * + * @return the custom distance value, or -1 if using the default from config + */ + public int getTurnToPlayerDistance() { + return turnToPlayerDistance; + } + + /** + * Sets the turn-to-player distance for this NPC. + * + * @param distance the custom distance value, or -1 to use the default from config + * @return this NpcData instance for method chaining + */ + public NpcData setTurnToPlayerDistance(int distance) { + this.turnToPlayerDistance = distance; + isDirty = true; + return this; + } + public float getInteractionCooldown() { return interactionCooldown; } diff --git a/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/events/NpcModifyEvent.java b/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/events/NpcModifyEvent.java index c5a29f5e..202916d3 100644 --- a/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/events/NpcModifyEvent.java +++ b/plugins/fancynpcs/api/src/main/java/de/oliver/fancynpcs/api/events/NpcModifyEvent.java @@ -94,6 +94,7 @@ public class NpcModifyEvent extends Event implements Cancellable { SHOW_IN_TAB, SKIN, TURN_TO_PLAYER, + TURN_TO_PLAYER_DISTANCE, TYPE, // Messages. MESSAGE_ADD, diff --git a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/FancyNpcsConfigImpl.java b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/FancyNpcsConfigImpl.java index 7e2cf672..6ef968a7 100644 --- a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/FancyNpcsConfigImpl.java +++ b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/FancyNpcsConfigImpl.java @@ -205,6 +205,24 @@ public class FancyNpcsConfigImpl implements FancyNpcsConfig { return turnToPlayerDistance; } + @Override + public boolean setTurnToPlayerDistance(int distance) { + // Validate the input - ensure the distance is positive or -1 for default + if (distance <= 0 && distance != -1) { + return false; + } + + // Update the config value in memory + this.turnToPlayerDistance = distance; + + // Persist to config file + FileConfiguration config = FancyNpcs.getInstance().getConfig(); + config.set("turn_to_player_distance", distance); + FancyNpcs.getInstance().saveConfig(); + + return true; + } + public int getVisibilityDistance() { return visibilityDistance; } diff --git a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/NpcManagerImpl.java b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/NpcManagerImpl.java index 78cc0fdd..9b6c7642 100644 --- a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/NpcManagerImpl.java +++ b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/NpcManagerImpl.java @@ -393,6 +393,8 @@ public class NpcManagerImpl implements NpcManager { } } + int turnToPlayerDistance = (int) npcConfig.getDouble("npcs." + id + ".turnToPlayerDistance", 0); + NpcData data = new NpcData( id, name, @@ -408,6 +410,7 @@ public class NpcManagerImpl implements NpcManager { type, new HashMap<>(), turnToPlayer, + turnToPlayerDistance, null, actions, interactionCooldown, @@ -472,4 +475,4 @@ public class NpcManagerImpl implements NpcManager { logger.error("Could not save backup file for NPCs"); } } -} +} \ No newline at end of file diff --git a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/CloudCommandManager.java b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/CloudCommandManager.java index 533e43d9..afc96abe 100644 --- a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/CloudCommandManager.java +++ b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/CloudCommandManager.java @@ -167,6 +167,7 @@ public final class CloudCommandManager { */ public @NotNull CloudCommandManager registerCommands() { annotationParser.parse(AttributeCMD.INSTANCE); + annotationParser.parse(CenterCMD.INSTANCE); annotationParser.parse(CollidableCMD.INSTANCE); annotationParser.parse(CopyCMD.INSTANCE); annotationParser.parse(CreateCMD.INSTANCE); @@ -187,6 +188,7 @@ public final class CloudCommandManager { annotationParser.parse(SkinCMD.INSTANCE); annotationParser.parse(TeleportCMD.INSTANCE); annotationParser.parse(TurnToPlayerCMD.INSTANCE); + annotationParser.parse(TurnToPlayerDistanceCMD.INSTANCE); annotationParser.parse(TypeCMD.INSTANCE); annotationParser.parse(ActionCMD.INSTANCE); annotationParser.parse(VisibilityDistanceCMD.INSTANCE); diff --git a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/CopyCMD.java b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/CopyCMD.java index 8d3aad9e..5ef3ae9d 100644 --- a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/CopyCMD.java +++ b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/CopyCMD.java @@ -50,6 +50,7 @@ public enum CopyCMD { npc.getData().getType(), new ConcurrentHashMap<>(npc.getData().getEquipment()), npc.getData().isTurnToPlayer(), + npc.getData().getTurnToPlayerDistance(), npc.getData().getOnClick(), new ConcurrentHashMap<>(npc.getData().getActions()), npc.getData().getInteractionCooldown(), diff --git a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/TurnToPlayerCMD.java b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/TurnToPlayerCMD.java index 4d939494..43470669 100644 --- a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/TurnToPlayerCMD.java +++ b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/commands/npc/TurnToPlayerCMD.java @@ -7,7 +7,6 @@ import de.oliver.fancynpcs.api.events.NpcModifyEvent; import org.bukkit.command.CommandSender; import org.incendo.cloud.annotations.Command; import org.incendo.cloud.annotations.Permission; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,14 +22,21 @@ public enum TurnToPlayerCMD { final @NotNull Npc npc, final @Nullable Boolean state ) { - final boolean finalState = (state == null) ? !npc.getData().isTurnToPlayer() : state; - // Calling the event and updating the state if not cancelled. - if (new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.TURN_TO_PLAYER, finalState, sender).callEvent()) { - npc.getData().setTurnToPlayer(finalState); - translator.translate(finalState ? "npc_turn_to_player_set_true" : "npc_turn_to_player_set_false").replace("npc", npc.getData().getName()).send(sender); - return; + if (state != null && npc.getData().isTurnToPlayer() != state) { + if (new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.TURN_TO_PLAYER, state, sender).callEvent()) { + npc.getData().setTurnToPlayer(state); + translator.translate(state ? "npc_turn_to_player_set_true" : "npc_turn_to_player_set_false") + .replace("npc", npc.getData().getName()) + .send(sender); + } else { + translator.translate("command_npc_modification_cancelled").send(sender); + } + } else if (state == null) { + // If no state provided, just display current state + boolean currentState = npc.getData().isTurnToPlayer(); + translator.translate(currentState ? "npc_turn_to_player_status_true" : "npc_turn_to_player_status_false") + .replace("npc", npc.getData().getName()) + .send(sender); } - translator.translate("command_npc_modification_cancelled").send(sender); } - } diff --git a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/tracker/TurnToPlayerTracker.java b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/tracker/TurnToPlayerTracker.java index e2bc1479..8a691e96 100644 --- a/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/tracker/TurnToPlayerTracker.java +++ b/plugins/fancynpcs/src/main/java/de/oliver/fancynpcs/tracker/TurnToPlayerTracker.java @@ -16,7 +16,7 @@ public class TurnToPlayerTracker implements Runnable { @Override public void run() { Collection npcs = FancyNpcs.getInstance().getNpcManagerImpl().getAllNpcs(); - int turnToPlayerDistance = FancyNpcs.getInstance().getFancyNpcConfig().getTurnToPlayerDistance(); + int defaultTurnToPlayerDistance = FancyNpcs.getInstance().getFancyNpcConfig().getTurnToPlayerDistance(); for (Player player : Bukkit.getOnlinePlayers()) { Location playerLocation = player.getLocation(); @@ -33,8 +33,12 @@ public class TurnToPlayerTracker implements Runnable { if (Double.isNaN(distance)) { continue; } + + // Get NPC-specific turn distance or fall back to default + int npcTurnDistance = npcData.getTurnToPlayerDistance(); + int effectiveTurnDistance = (npcTurnDistance == -1) ? defaultTurnToPlayerDistance : npcTurnDistance; - if (npcData.isTurnToPlayer() && distance < turnToPlayerDistance) { + if (npcData.isTurnToPlayer() && distance < effectiveTurnDistance) { Location newLoc = playerLocation.clone(); newLoc.setDirection(newLoc.subtract(npcLocation).toVector()); npc.lookAt(player, newLoc); diff --git a/plugins/fancynpcs/src/main/resources/languages/default.yml b/plugins/fancynpcs/src/main/resources/languages/default.yml index 21a7e738..1dc4ac2d 100644 --- a/plugins/fancynpcs/src/main/resources/languages/default.yml +++ b/plugins/fancynpcs/src/main/resources/languages/default.yml @@ -107,6 +107,7 @@ messages: npc_action_set: "Syntax: {primaryColor}/npc action {secondaryColor}(npc) (trigger) {primaryColor}set {secondaryColor}(index) (type) [value]" npc_attribute: "Syntax: {primaryColor}/npc attribute {secondaryColor}(npc) {primaryColor}(set | list)" npc_attribute_set: "Syntax: {primaryColor}/npc attribute {secondaryColor}(npc) {primaryColor}set {secondaryColor}(attribute) (value)" + npc_center: "Syntax: {primaryColor}/npc center {secondaryColor}(npc)" npc_collidable: "Syntax: {primaryColor}/npc collidable {secondaryColor}(npc) (state)" npc_copy: "Syntax: {primaryColor}/npc copy {secondaryColor}(npc) (new_name)" npc_create: "Syntax: {primaryColor}/npc create {secondaryColor}(npc) [--type] [--position] [--world]" @@ -125,7 +126,7 @@ messages: npc_show_in_tab: "Syntax: {primaryColor}/npc show_in_tab {secondaryColor}(npc) (state)" npc_skin: "Syntax: {primaryColor}/npc skin {secondaryColor}(npc) (@none | @mirror | name | uuid | placeholder | url | file name) [--slim]" npc_teleport: "Syntax: {primaryColor}/npc teleport {secondaryColor}(npc)" - npc_turn_to_player: "Syntax: {primaryColor}/npc turn_to_player {secondaryColor}(npc) (state)" + npc_turn_to_player: "Syntax: {primaryColor}/npc turn_to_player {secondaryColor}(npc) (state) [distance]" npc_type: "Syntax: {primaryColor}/npc type {secondaryColor}(npc) (type)" npc_visibility_distance: "Syntax: {primaryColor}/npc visibility_distance {secondaryColor}(npc) (always_visible | default | not_visible | distance)" @@ -158,6 +159,7 @@ messages: - "Lists all modified attributes of the NPC.'>{primaryColor}/npc attribute {secondaryColor}(npc) {primaryColor}list" - "Changes whether the NPC can collide with other entities.'>{primaryColor}/npc collidable {secondaryColor}(npc) [state]" - "Copies (duplicates) specified NPC.'>{primaryColor}/npc copy {secondaryColor}(npc) (new_name)" + - "Centers the NPC on its current block location.'>{primaryColor}/npc center {secondaryColor}(npc)" - "Creates a new NPC. Can be customized with flags.'>{primaryColor}/npc create {secondaryColor}(npc) [--type] [--location] [--world]" - "Changes displayname of the NPC. Supports MiniMessage, PlaceholderAPI and MiniPlaceholders.'>{primaryColor}/npc displayname {secondaryColor}(npc) (name)" - "Sets equipment slot of the NPC to item currently held in main hand, none or a specific item type.'>{primaryColor}/npc equipment {secondaryColor}(npc) {primaryColor}set {secondaryColor}(slot) (@hand | @none | item)" @@ -175,7 +177,7 @@ messages: - "Changes whether the NPC is shown in the player-list. This works only on NPCs of PLAYER type.{errorColor}Re-connecting to the server might be required for changes to take effect.'>{primaryColor}/npc show_in_tab {secondaryColor}(npc) (state)" - "Changes skin of the NPC.Supports PlaceholderAPI and MiniPlaceholders.{warningColor}@none - removes the skin{warningColor}@mirror - mirrors player skin{warningColor}(name) - name of any player{warningColor}(url) - url of the skin texture'>{primaryColor}/npc skin {secondaryColor}(npc) (@none | @mirror | name | url) [--slim]" - "Teleports you to the specified NPC.'>{primaryColor}/npc teleport {secondaryColor}(npc)" - - "Changes whether the NPC should turn to the player when in range.'>{primaryColor}/npc turn_to_player {secondaryColor}(npc) (state)" + - "Changes whether the NPC should turn to the player when in range. Optionally specify a custom turn distance.'>{primaryColor}/npc turn_to_player {secondaryColor}(npc) (state) [distance]" - "Changes the type of the NPC.'>{primaryColor}/npc type {secondaryColor}(npc) (type)" - "Changes the visibility distance of the NPC.'>{primaryColor}/npc visibility_distance {secondaryColor}(npc) (default | distance | ...)" @@ -322,6 +324,18 @@ messages: # Commands (npc turn_to_player) npc_turn_to_player_set_true: "NPC {warningColor}{npc} is now turning to player." npc_turn_to_player_set_false: "NPC {warningColor}{npc} is no longer turning to player." + npc_turn_to_player_distance_set: "NPC {warningColor}{npc} will now turn to players within {warningColor}{distance} blocks." + npc_turn_to_player_distance_default: "NPC {warningColor}{npc} will now use the default distance of {warningColor}{distance} blocks." + npc_turn_to_player_distance_invalid: "› {errorColor}Invalid distance value. Distance must be -1 (for default) or a positive number." # Commands (npc type) npc_type_success: "NPC {warningColor}{npc} type has been changed to {warningColor}{type}." + + # Commands (npc turn_to_player_distance) + turn_to_player_distance_current: "Current turn-to-player distance is: {warningColor}{distance} blocks" + turn_to_player_distance_updated: "› {successColor}Turn-to-player distance has been updated to {warningColor}{distance} blocks" + turn_to_player_distance_failed: "› {errorColor}Failed to update turn-to-player distance." + + # Commands (npc center) + npc_center_success: "NPC {warningColor}{npc} has been centered to {warningColor}{x}, {warningColor}{y}, {warningColor}{z}." + npc_center_failure_no_location: "› {errorColor}NPC {warningColor}{npc}{errorColor} has no valid location." diff --git a/src/main/java/de/oliver/fancynpcs/commands/npc/CenterCMD.java b/src/main/java/de/oliver/fancynpcs/commands/npc/CenterCMD.java new file mode 100644 index 00000000..a3916002 --- /dev/null +++ b/src/main/java/de/oliver/fancynpcs/commands/npc/CenterCMD.java @@ -0,0 +1,54 @@ +package de.oliver.fancynpcs.commands.npc; + +import de.oliver.fancylib.translations.Translator; +import de.oliver.fancynpcs.FancyNpcs; +import de.oliver.fancynpcs.api.Npc; +import de.oliver.fancynpcs.api.NpcData; +import de.oliver.fancynpcs.api.events.NpcModifyEvent; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.annotations.Command; +import org.incendo.cloud.annotations.Permission; +import org.jetbrains.annotations.NotNull; + +public enum CenterCMD { + INSTANCE; // SINGLETON + + private final Translator translator = FancyNpcs.getInstance().getTranslator(); + + @Command("npc center ") + @Permission("fancynpcs.command.npc.center") + public void onCenter( + final @NotNull CommandSender sender, + final @NotNull Npc npc + ) { + NpcData npcData = npc.getData(); + Location location = npcData.getLocation(); + + if (location == null) { + translator.translate("npc_center_failure_no_location").replace("npc", npcData.getName()).send(sender); + return; + } + + // Center the NPC on the block + Location centeredLocation = location.clone(); + centeredLocation.setX(centeredLocation.getBlockX() + 0.5); + centeredLocation.setY(centeredLocation.getY()); + centeredLocation.setZ(centeredLocation.getBlockZ() + 0.5); + + // Trigger the modify event + if (new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.LOCATION, centeredLocation, sender).callEvent()) { + npcData.setLocation(centeredLocation); + npc.updateForAll(); + + translator.translate("npc_center_success") + .replace("npc", npcData.getName()) + .replace("x", String.format("%.2f", centeredLocation.getX())) + .replace("y", String.format("%.2f", centeredLocation.getY())) + .replace("z", String.format("%.2f", centeredLocation.getZ())) + .send(sender); + } else { + translator.translate("command_npc_modification_cancelled").send(sender); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/oliver/fancynpcs/commands/npc/TurnToPlayerDistanceCMD.java b/src/main/java/de/oliver/fancynpcs/commands/npc/TurnToPlayerDistanceCMD.java new file mode 100644 index 00000000..c9e84c53 --- /dev/null +++ b/src/main/java/de/oliver/fancynpcs/commands/npc/TurnToPlayerDistanceCMD.java @@ -0,0 +1,78 @@ +package de.oliver.fancynpcs.commands.npc; + +import de.oliver.fancylib.translations.Translator; +import de.oliver.fancynpcs.FancyNpcs; +import de.oliver.fancynpcs.api.Npc; +import de.oliver.fancynpcs.api.events.NpcModifyEvent; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.annotations.Argument; +import org.incendo.cloud.annotations.Command; +import org.incendo.cloud.annotations.Permission; +import org.incendo.cloud.annotations.parser.Parser; +import org.incendo.cloud.annotations.suggestion.Suggestions; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public enum TurnToPlayerDistanceCMD { + INSTANCE; + + private final Translator translator = FancyNpcs.getInstance().getTranslator(); + + // Storing in a static variable to avoid re-creating the array each time suggestion is requested. + private final List DISTANCE_SUGGESTIONS = List.of("default"); + + @Command("npc turn_to_player_distance ") + @Permission("fancynpcs.command.npc.turn_to_player_distance") + public void onTurnToPlayerDistance( + final @NotNull CommandSender sender, + final @NotNull Npc npc, + final @Argument(parserName = "TurnToPlayerDistanceCMD/distance") int distance + ) { + if (distance < -1) { + translator.translate("npc_turn_to_player_distance_invalid").send(sender); + return; + } + + if (new NpcModifyEvent(npc, NpcModifyEvent.NpcModification.TURN_TO_PLAYER_DISTANCE, distance, sender).callEvent()) { + npc.getData().setTurnToPlayerDistance(distance); + + if (distance == -1) { + // Using default distance + int defaultDistance = FancyNpcs.getInstance().getFancyNpcConfig().getTurnToPlayerDistance(); + translator.translate("npc_turn_to_player_distance_default") + .replace("npc", npc.getData().getName()) + .replace("distance", String.valueOf(defaultDistance)) + .send(sender); + } else { + // Using custom distance + translator.translate("npc_turn_to_player_distance_set") + .replace("npc", npc.getData().getName()) + .replace("distance", String.valueOf(distance)) + .send(sender); + } + } else { + translator.translate("command_npc_modification_cancelled").send(sender); + } + } + + /* PARSERS AND SUGGESTIONS */ + + @Parser(name = "TurnToPlayerDistanceCMD/distance", suggestions = "TurnToPlayerDistanceCMD/distance") + public @NotNull Integer parse(final CommandContext context, final CommandInput input) { + // If 'default' string is provided, it is being handled as -1. + if (input.peekString().equalsIgnoreCase("default")) { + input.readString(); + return -1; + } + // Otherwise, reading next argument as int. + return input.readInteger(); + } + + @Suggestions("TurnToPlayerDistanceCMD/distance") + public @NotNull List suggest(final CommandContext context, final CommandInput input) { + return DISTANCE_SUGGESTIONS; + } +} \ No newline at end of file