From 514834025eb0e98222c25a2476b1ee8d68d3ee61 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 01:49:07 -0500 Subject: [PATCH 01/24] Bump version to 3.7.2 --- api/pom.xml | 2 +- plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index cba5ccea..c38b3290 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.7.1 + 3.7.2 api diff --git a/plugin/pom.xml b/plugin/pom.xml index 9e57c9c9..10433487 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.7.1 + 3.7.2 plugin diff --git a/pom.xml b/pom.xml index bd44d60e..c0710f05 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.7.1 + 3.7.2 pom DisplayEntityUtils From 7ecacc848c2a2e3664233eab9f7fa1570fb8dfa8 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:26:56 -0500 Subject: [PATCH 02/24] Added DisplayStorage#isEnabled --- .../displayentityutils/database/DisplayStorage.java | 2 ++ .../displayentityutils/database/LocalDisplayStorage.java | 7 +++++++ .../displayentityutils/database/MYSQLDisplayStorage.java | 6 ++++++ .../displayentityutils/database/MongoDisplayStorage.java | 6 ++++++ 4 files changed, 21 insertions(+) diff --git a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java index 5fb9d079..a66810bc 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java +++ b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java @@ -10,6 +10,8 @@ public interface DisplayStorage { + boolean isEnabled(); + boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup group, @Nullable Player saver); void deleteDisplayEntityGroup(@NotNull String group, @Nullable Player deleter); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java index 550fdea2..8afd442d 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java @@ -3,6 +3,7 @@ import net.donnypz.displayentityutils.DisplayConfig; import net.donnypz.displayentityutils.managers.DisplayAnimationManager; import net.donnypz.displayentityutils.managers.DisplayGroupManager; +import net.donnypz.displayentityutils.managers.LoadMethod; import net.donnypz.displayentityutils.managers.PluginFolders; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayAnimation; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayEntityGroup; @@ -21,6 +22,12 @@ public final class LocalDisplayStorage implements DisplayStorage { + @Override + public boolean isEnabled() { + return LoadMethod.LOCAL.isEnabled(); + } + + @Override public boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup displayEntityGroup, @Nullable Player saver){ try{ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index 0de19e13..8531bf0d 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -5,6 +5,7 @@ import net.donnypz.displayentityutils.DisplayConfig; import net.donnypz.displayentityutils.managers.DisplayAnimationManager; import net.donnypz.displayentityutils.managers.DisplayGroupManager; +import net.donnypz.displayentityutils.managers.LoadMethod; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayAnimation; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayEntityGroup; import net.kyori.adventure.text.Component; @@ -132,6 +133,11 @@ private Connection getConnection(){ } } + @Override + public boolean isEnabled() { + return LoadMethod.MYSQL.isEnabled(); + } + @Override public boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup displayEntityGroup, @Nullable Player saver){ String tag = displayEntityGroup.getTag(); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index 30b9a1fd..a1a0e9c8 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -10,6 +10,7 @@ import net.donnypz.displayentityutils.DisplayConfig; import net.donnypz.displayentityutils.managers.DisplayAnimationManager; import net.donnypz.displayentityutils.managers.DisplayGroupManager; +import net.donnypz.displayentityutils.managers.LoadMethod; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayAnimation; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayEntityGroup; import net.kyori.adventure.text.Component; @@ -159,6 +160,11 @@ public void closeConnection(){ } } + @Override + public boolean isEnabled() { + return LoadMethod.MONGODB.isEnabled(); + } + @Override public boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup displayEntityGroup, @Nullable Player saver){ String tag = displayEntityGroup.getTag(); From 2660a6e6e9ddf8cb0ed7031a2f4c0ee0c201b13e Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:34:17 -0500 Subject: [PATCH 03/24] change default MongoDB `database` name to `displayentityutils` --- plugin/src/main/resources/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/resources/config.yml b/plugin/src/main/resources/config.yml index ef6c63c0..0d30e1f5 100644 --- a/plugin/src/main/resources/config.yml +++ b/plugin/src/main/resources/config.yml @@ -10,7 +10,7 @@ mongodb: enabled: false host: "localhost" port: 27017 - database: "DisplayEntityUtils" + database: "displayentityutils" username: "user" password: "password" groupCollection: "saved_displays" From 6ccc082725152af2be31aa0a7c0fcd4e8c44b68d Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:57:52 -0500 Subject: [PATCH 04/24] Add paginated `#getGroupTags` and `#getAnimationTags` to `DisplayStorage` --- .../database/DisplayStorage.java | 4 + .../DisplayEntities/DisplayAnimation.java | 2 +- .../DisplayEntities/DisplayEntityGroup.java | 2 +- .../database/CommonDisplayStorageUtils.java | 4 + .../database/LocalDisplayStorage.java | 92 ++++++++++++++----- .../database/MYSQLDisplayStorage.java | 73 +++++++++++---- .../database/MongoDisplayStorage.java | 56 ++++++++--- 7 files changed, 176 insertions(+), 57 deletions(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java index a66810bc..20870750 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java +++ b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java @@ -26,5 +26,9 @@ public interface DisplayStorage { @NotNull List getGroupTags(); + @NotNull List getGroupTags(int page, int size); + @NotNull List getAnimationTags(); + + @NotNull List getAnimationTags(int page, int size); } diff --git a/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayEntities/DisplayAnimation.java b/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayEntities/DisplayAnimation.java index 7007846b..f829ad7f 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayEntities/DisplayAnimation.java +++ b/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayEntities/DisplayAnimation.java @@ -20,7 +20,7 @@ public final class DisplayAnimation implements Serializable { @Serial private static final long serialVersionUID = 99L; - public static final String fileExtension = ".deanim"; + public static final String FILE_EXTENSION = ".deanim"; DisplayAnimation(){ diff --git a/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayEntities/DisplayEntityGroup.java b/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayEntities/DisplayEntityGroup.java index 2962944e..a9c11d6f 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayEntities/DisplayEntityGroup.java +++ b/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayEntities/DisplayEntityGroup.java @@ -26,7 +26,7 @@ public final class DisplayEntityGroup implements Serializable{ @Serial private static final long serialVersionUID = 99L; - public static final String fileExtension = ".deg"; + public static final String FILE_EXTENSION = ".deg"; DisplayEntityGroup(SpawnedDisplayEntityGroup spawnedGroup){ this.tag = spawnedGroup.getTag(); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/CommonDisplayStorageUtils.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/CommonDisplayStorageUtils.java index 4eb94e9b..67106ff7 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/CommonDisplayStorageUtils.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/CommonDisplayStorageUtils.java @@ -33,4 +33,8 @@ static byte[] toByteArray(Object entityObject) throws IOException { byteOut.close(); return data; } + + static int getPageOffset(int page, int size){ + return (page - 1) * size; + } } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java index 8afd442d..caa21dea 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java @@ -16,6 +16,9 @@ import org.jetbrains.annotations.Nullable; import java.io.*; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.zip.GZIPOutputStream; @@ -40,7 +43,7 @@ public boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup displayEntityG byte[] data = byteOut.toByteArray(); - File saveFile = new File(PluginFolders.groupSaveFolder, "/"+displayEntityGroup.getTag()+DisplayEntityGroup.fileExtension); + File saveFile = new File(PluginFolders.groupSaveFolder, "/"+displayEntityGroup.getTag()+DisplayEntityGroup.FILE_EXTENSION); if (saveFile.exists()){ if (!DisplayConfig.overwritexistingSaves()){ if (saver != null){ @@ -69,8 +72,9 @@ public boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup displayEntityG } } + @Override public void deleteDisplayEntityGroup(@NotNull String tag, @Nullable Player deleter){ - File saveFile = new File(PluginFolders.groupSaveFolder, "/"+tag+DisplayEntityGroup.fileExtension); + File saveFile = new File(PluginFolders.groupSaveFolder, "/"+tag+DisplayEntityGroup.FILE_EXTENSION); if (saveFile.exists()){ saveFile.delete(); if (deleter != null){ @@ -83,14 +87,16 @@ public void deleteDisplayEntityGroup(@NotNull String tag, @Nullable Player delet } } + @Override public @Nullable DisplayEntityGroup getDisplayEntityGroup(@NotNull String tag){ - File saveFile = new File(PluginFolders.groupSaveFolder, "/"+tag+DisplayEntityGroup.fileExtension); + File saveFile = new File(PluginFolders.groupSaveFolder, "/"+tag+DisplayEntityGroup.FILE_EXTENSION); if (!saveFile.exists()){ return null; } return DisplayGroupManager.getGroup(saveFile); } + @Override public boolean saveDisplayAnimation(@NotNull DisplayAnimation displayAnimation, @Nullable Player saver){ try{ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); @@ -103,7 +109,7 @@ public boolean saveDisplayAnimation(@NotNull DisplayAnimation displayAnimation, byte[] data = byteOut.toByteArray(); byteOut.close(); - File saveFile = new File(PluginFolders.animSaveFolder, "/"+displayAnimation.getAnimationTag()+DisplayAnimation.fileExtension); + File saveFile = new File(PluginFolders.animSaveFolder, "/"+displayAnimation.getAnimationTag()+DisplayAnimation.FILE_EXTENSION); if (saveFile.exists()){ if (DisplayConfig.overwritexistingSaves()){ saveFile.delete(); @@ -138,8 +144,9 @@ public boolean saveDisplayAnimation(@NotNull DisplayAnimation displayAnimation, + @Override public void deleteDisplayAnimation(@NotNull String tag, @Nullable Player deleter){ - File saveFile = new File(PluginFolders.animSaveFolder, "/"+tag+DisplayAnimation.fileExtension); + File saveFile = new File(PluginFolders.animSaveFolder, "/"+tag+DisplayAnimation.FILE_EXTENSION); if (saveFile.exists()){ saveFile.delete(); if (deleter != null){ @@ -152,41 +159,78 @@ public void deleteDisplayAnimation(@NotNull String tag, @Nullable Player deleter } } + @Override public @Nullable DisplayAnimation getDisplayAnimation(@NotNull String tag){ - File saveFile = new File(PluginFolders.animSaveFolder, "/"+tag+DisplayAnimation.fileExtension); + File saveFile = new File(PluginFolders.animSaveFolder, "/"+tag+DisplayAnimation.FILE_EXTENSION); if (!saveFile.exists()){ return null; } return DisplayAnimationManager.getAnimation(saveFile); } + @Override public @NotNull List getGroupTags(){ + return getTags(PluginFolders.groupSaveFolder, DisplayEntityGroup.FILE_EXTENSION); + } + + @Override + public @NotNull List getGroupTags(int page, int size) { + return getTags(PluginFolders.groupSaveFolder, DisplayEntityGroup.FILE_EXTENSION, page, size); + } + + @Override + public @NotNull List getAnimationTags(){ + return getTags(PluginFolders.animSaveFolder, DisplayAnimation.FILE_EXTENSION); + } + + @Override + public @NotNull List getAnimationTags(int page, int size) { + return getTags(PluginFolders.animSaveFolder, DisplayAnimation.FILE_EXTENSION, page, size); + } + + private List getTags(File saveFolder, String fileExtension){ List tags = new ArrayList<>(); - File groupFolder = new File(PluginFolders.groupSaveFolder, "/"); - if (!groupFolder.exists() || groupFolder.listFiles() == null){ - return tags; - } - for (File file : groupFolder.listFiles()){ - if (file.getName().contains(DisplayEntityGroup.fileExtension)){ - tags.add(file.getName().replace(DisplayEntityGroup.fileExtension, "")); - } + File animFolder = new File(saveFolder, "/"); + if (!animFolder.exists()) return tags; + + File[] files = animFolder.listFiles(); + if (files == null) return tags; + for (File file : files){ + String fileName = file.getName(); + if (fileName.endsWith(fileExtension)){ + tags.add(fileName.replace(fileExtension, "")); + } } return tags; } - public @NotNull List getAnimationTags(){ + private List getTags(File saveFolder, String fileExtension, int page, int size){ List tags = new ArrayList<>(); - File animFolder = new File(PluginFolders.animSaveFolder, "/"); - if (!animFolder.exists() || animFolder.listFiles() == null){ - return tags; - } - for (File file : animFolder.listFiles()){ - if (file.getName().contains(DisplayAnimation.fileExtension)){ - tags.add(file.getName().replace(DisplayAnimation.fileExtension, "")); - } + int offset = CommonDisplayStorageUtils.getPageOffset(page, size); + int skipped = 0; + int count = 0; + + try (DirectoryStream stream = Files.newDirectoryStream(saveFolder.toPath())) { + for (Path path : stream) { + if (!Files.isRegularFile(path)) continue; + + String fileName = path.getFileName().toString(); + if (!fileName.endsWith(fileExtension)) continue; + + if (skipped++ < offset) continue; + + String tag = fileName.substring(0, fileName.length() - fileExtension.length()); + tags.add(tag); + + if (++count >= size) { + break; + } + } + } catch (IOException e) { + return tags; } - return tags; + return tags; } } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index 8531bf0d..4d8247fc 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -197,11 +197,67 @@ public void deleteDisplayAnimation(@NotNull String tag, @Nullable Player deleter return getTags(GROUP_TABLE); } + @Override + public @NotNull List getGroupTags(int page, int size) { + return getTags(GROUP_TABLE, page, size); + } + @Override public @NotNull List getAnimationTags(){ return getTags(ANIMATION_TABLE); } + @Override + public @NotNull List getAnimationTags(int page, int size) { + return getTags(ANIMATION_TABLE, page, size); + } + + private List getTags(String tableName){ //internally set table name + if (!isConnected()) return Collections.emptyList(); + List tags = new ArrayList<>(); + String retrieve = "SELECT "+TAG_COLUMN+" FROM "+tableName+";"; + + try(Connection connection = getConnection(); + Statement statement = connection.createStatement(); + ResultSet results = statement.executeQuery(retrieve)){ + + while(results.next()){ + tags.add(results.getString(TAG_COLUMN)); + } + } + catch(SQLException e){ + e.printStackTrace(); + } + return tags; + } + + private List getTags(String tableName, int page, int size){ //internally set table name + if (!isConnected()) return new ArrayList<>(); + List tags = new ArrayList<>(); + + int offset = CommonDisplayStorageUtils.getPageOffset(page, size); + String retrieve = + """ + SELECT %s FROM %s + ORDER BY %s + LIMIT %d + OFFSET %d + """.formatted(TAG_COLUMN, tableName, TAG_COLUMN, size, offset); + + try(Connection connection = getConnection(); + Statement statement = connection.createStatement(); + ResultSet results = statement.executeQuery(retrieve)){ + + while(results.next()){ + tags.add(results.getString(TAG_COLUMN)); + } + } + catch(SQLException e){ + e.printStackTrace(); + } + return tags; + } + private boolean saveEntity(String tag, Object entity, String tableName, String displayName, Player saver){ if (!isConnected()) return false; String save = "INSERT INTO "+tableName+" VALUES(?, ?);"; @@ -318,22 +374,5 @@ private boolean hasEntity(String tag, String tableName, Connection connection){ } } - private List getTags(String tableName){ //internally set table name - if (!isConnected()) return Collections.emptyList(); - List tags = new ArrayList<>(); - String retrieve = "SELECT "+TAG_COLUMN+" FROM "+tableName+";"; - try(Connection connection = getConnection(); - Statement statement = connection.createStatement(); - ResultSet results = statement.executeQuery(retrieve)){ - - while(results.next()){ - tags.add(results.getString(TAG_COLUMN)); - } - } - catch(SQLException e){ - e.printStackTrace(); - } - return tags; - } } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index a1a0e9c8..6809c4e5 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -243,11 +243,53 @@ public void deleteDisplayAnimation(@NotNull String tag, @Nullable Player deleter return getTags(GROUP_COLLECTION); } + @Override + public @NotNull List getGroupTags(int page, int size) { + return getTags(GROUP_COLLECTION, page, size); + } + @Override public @NotNull List getAnimationTags(){ return getTags(ANIMATION_COLLECTION); } + @Override + public @NotNull List getAnimationTags(int page, int size) { + return getTags(ANIMATION_COLLECTION, page, size); + } + + private List getTags(MongoCollection collection){ + if (!isConnected()) return new ArrayList<>(); + + return collection.find() + .projection( + Projections.fields( + Projections.include(TAG_FIELD), + Projections.excludeId() + ) + ) + .map(doc -> doc.getString(TAG_FIELD)) + .into(new ArrayList<>()); + } + + private List getTags(MongoCollection collection, int page, int size){ + if (!isConnected()) return new ArrayList<>(); + int offset = CommonDisplayStorageUtils.getPageOffset(page, size); + + return collection.find() + .skip(offset) + .limit(size) + .sort(Sorts.ascending(TAG_FIELD)) + .projection( + Projections.fields( + Projections.include(TAG_FIELD), + Projections.excludeId() + ) + ) + .map(doc -> doc.getString(TAG_FIELD)) + .into(new ArrayList<>()); + } + private boolean saveEntity( String tag, @@ -318,20 +360,6 @@ private void deleteEntity( } } - private List getTags(MongoCollection collection){ - if (!isConnected()) return new ArrayList<>(); - - return collection.find() - .projection( - Projections.fields( - Projections.include(TAG_FIELD), - Projections.excludeId() - ) - ) - .map(doc -> doc.getString(TAG_FIELD)) - .into(new ArrayList<>()); - } - private Document getGroupDocument(String tag){ return GROUP_COLLECTION.find(new Document(TAG_FIELD, tag)) .first(); From 96fcf52916d7d8419cc0fd05ced6ed389a5bdb98 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:58:54 -0500 Subject: [PATCH 05/24] don't use `databaseName` parameter in generated connection string --- .../displayentityutils/database/MongoDisplayStorage.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index 6809c4e5..251fd70f 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -6,6 +6,7 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.Projections; +import com.mongodb.client.model.Sorts; import net.donnypz.displayentityutils.DisplayAPI; import net.donnypz.displayentityutils.DisplayConfig; import net.donnypz.displayentityutils.managers.DisplayAnimationManager; @@ -55,12 +56,11 @@ public void createConnection( String animationCollection ){ String connectionString = String.format( - "mongodb://%s:%s@%s:%d/%s", + "mongodb://%s:%s@%s:%d", username, password, host, - port, - databaseName + port ); this.createConnection( From 6435802e7c5fc664c5cca2c1e3aac6e05510d561 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:59:35 -0500 Subject: [PATCH 06/24] Use paginated `#getGroupTags` and `#getAnimationTags` from `DisplayStorage` when listing groups in list commands --- .../displayentityutils/command/ListCMD.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/ListCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/ListCMD.java index 6c14e027..b2c63f53 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/command/ListCMD.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/ListCMD.java @@ -1,8 +1,7 @@ package net.donnypz.displayentityutils.command; import net.donnypz.displayentityutils.DisplayAPI; -import net.donnypz.displayentityutils.managers.DisplayAnimationManager; -import net.donnypz.displayentityutils.managers.DisplayGroupManager; +import net.donnypz.displayentityutils.database.DisplayStorage; import net.donnypz.displayentityutils.managers.LoadMethod; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -19,6 +18,7 @@ public class ListCMD extends ConsoleUsableSubCommand { int minLength; private final int STORAGE_INDEX; private final int PAGE_NUMBER_INDEX; + private final int PAGE_SIZE = 6; boolean listsGroups; public ListCMD(Component incorrectUsageMessage, int minLength, boolean listsGroups) { @@ -54,15 +54,29 @@ public void list(CommandSender sender, String[] args) { return; } - List tags = listsGroups ? - DisplayGroupManager.getSavedDisplayEntityGroups(loadMethod) - : - DisplayAnimationManager.getSavedDisplayAnimations(loadMethod); + int pageNumber = 1; + if (args.length > minLength) { + try { + pageNumber = Math.max(1, Integer.parseInt(args[PAGE_NUMBER_INDEX])); + } catch (NumberFormatException ignored) { + } + } + + DisplayStorage storage = DisplayAPI.getStorage(loadMethod); + + if (!storage.isEnabled()){ + sender.sendMessage(DisplayAPI.pluginPrefix + .append(Component.text("Storage location is not enabled!", NamedTextColor.RED))); + return; + } + List tags = listsGroups + ? storage.getGroupTags(pageNumber, PAGE_SIZE) + : storage.getAnimationTags(pageNumber, PAGE_SIZE); sender.sendMessage(DisplayAPI.pluginPrefixLong); sender.sendMessage(MiniMessage.miniMessage().deserialize("Storage Location: " + loadMethod.getDisplayName())); if (tags.isEmpty()) { - sender.sendMessage(Component.text("That storage location is empty!", NamedTextColor.RED)); + sender.sendMessage(Component.text("| Nothing to list on this page.", NamedTextColor.GRAY)); return; } @@ -70,22 +84,7 @@ public void list(CommandSender sender, String[] args) { sender.sendMessage(Component.text("Click to a listed item to " + (listsGroups ? "spawn" : "select") + " it", NamedTextColor.GRAY)); } - int pageNumber = 1; - if (args.length > minLength) { - try { - pageNumber = Math.max(1, Integer.parseInt(args[PAGE_NUMBER_INDEX])); - } catch (NumberFormatException ignored) { - } - } - - int end = pageNumber * 6; - int start = end - 6; - - for (int i = start; i < end; i++) { - if (i >= tags.size()) { - break; - } - String tag = tags.get(i); + for (String tag : tags) { sender.sendMessage(getMessage(tag, loadMethod)); } sender.sendMessage(Component.text("----------" + "Page " + pageNumber + "----------", NamedTextColor.GRAY)); From 95d17a4f8e93ed6fbee84d7980eef21fdfd7c3b4 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:59:59 -0500 Subject: [PATCH 07/24] `Remove DisplayAnimationManager#getSavedDisplayAnimations` --- .../managers/DisplayAnimationManager.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/managers/DisplayAnimationManager.java b/api/src/main/java/net/donnypz/displayentityutils/managers/DisplayAnimationManager.java index c823e2c8..3ca6d6ec 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/managers/DisplayAnimationManager.java +++ b/api/src/main/java/net/donnypz/displayentityutils/managers/DisplayAnimationManager.java @@ -324,17 +324,6 @@ private static void attemptCacheAnimation(String tag, SpawnedDisplayAnimation an } } - - /** - * Get the animation tags of all saved {@link DisplayAnimation}s in a storage location. - * @param loadMethod of the search location - * @return a list of all animations by their group tag - */ - public static @NotNull List getSavedDisplayAnimations(@NotNull LoadMethod loadMethod){ - if (!loadMethod.isEnabled()) return Collections.emptyList(); - return DisplayAPI.getStorage(loadMethod).getAnimationTags(); - } - /** * Get a {@link DisplayAnimation} from a JSON string * @param json JSON of a saved {@link DisplayAnimation} From aafe8c3a69fbad0a1c309561bf73585c190bbb0a Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:00:17 -0500 Subject: [PATCH 08/24] Remove `DisplayGroupManager#getSavedDisplayEntityGroups` --- .../managers/DisplayGroupManager.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/managers/DisplayGroupManager.java b/api/src/main/java/net/donnypz/displayentityutils/managers/DisplayGroupManager.java index 4cc55575..133386dc 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/managers/DisplayGroupManager.java +++ b/api/src/main/java/net/donnypz/displayentityutils/managers/DisplayGroupManager.java @@ -738,19 +738,6 @@ private static DisplayEntityGroup getGroupFromJson(JsonObject jsonObject){ .fromJson(jsonObject, DisplayEntityGroup.class); } - - - /** - * Get the group tags of all saved {@link DisplayEntityGroup}s in a storage location. - * @param loadMethod of the search location - * @return a list of all groups by their group tag - */ - @ApiStatus.Internal - public static @NotNull List getSavedDisplayEntityGroups(@NotNull LoadMethod loadMethod) { - if (!loadMethod.isEnabled()) return Collections.emptyList(); - return DisplayAPI.getStorage(loadMethod).getGroupTags(); - } - @ApiStatus.Internal public static void addPersistentPacketGroup(@NotNull PacketDisplayEntityGroup group, @NotNull Location location){ if (group.isPersistent()) return; From a16d7884402f01ee2eb4401638b827f8b0ea3d9e Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:01:31 -0500 Subject: [PATCH 09/24] Add `LoadMethod#getDisplayStorage` --- .../donnypz/displayentityutils/managers/LoadMethod.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/src/main/java/net/donnypz/displayentityutils/managers/LoadMethod.java b/api/src/main/java/net/donnypz/displayentityutils/managers/LoadMethod.java index fbeb8536..215ddabe 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/managers/LoadMethod.java +++ b/api/src/main/java/net/donnypz/displayentityutils/managers/LoadMethod.java @@ -1,8 +1,11 @@ package net.donnypz.displayentityutils.managers; +import net.donnypz.displayentityutils.DisplayAPI; import net.donnypz.displayentityutils.DisplayConfig; +import net.donnypz.displayentityutils.database.DisplayStorage; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayAnimation; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayEntityGroup; +import org.jetbrains.annotations.NotNull; /** * Storage locations for saving, deleting, and retrieving a saved {@link DisplayEntityGroup} or {@link DisplayAnimation} @@ -44,6 +47,10 @@ public boolean isEnabled(){ } } + public @NotNull DisplayStorage getDisplayStorage(){ + return DisplayAPI.getStorage(this); + } + public String getDisplayName() { return displayName; } From f87b4a9b3c264425eb9dc730d8f8f6c505adf98e Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:08:22 -0500 Subject: [PATCH 10/24] Register all plugin permissions for permission manager discovery (LP) --- .../displayentityutils/command/Permission.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/Permission.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/Permission.java index c6d2a760..83770f2d 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/command/Permission.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/Permission.java @@ -1,5 +1,8 @@ package net.donnypz.displayentityutils.command; +import net.donnypz.displayentityutils.DisplayAPI; +import org.bukkit.plugin.PluginManager; + public enum Permission { HELP("deu.help"), RELOAD("deu.reload"), @@ -136,6 +139,20 @@ public enum Permission { BDENGINE_SPAWN_MODEL("deu.bdengine.spawnmodel"), BDENGINE_IMPORT("deu.bdengine.import"); + static{ + for (Permission permission : Permission.values()){ + registerPermission(permission); + } + } + + private static void registerPermission(Permission permission){ + PluginManager pm = DisplayAPI.getPlugin().getServer().getPluginManager(); + if (pm.getPermission(permission.getPermission()) == null){ + pm.addPermission(new org.bukkit.permissions.Permission(permission.getPermission())); + } + } + + private final String permission; Permission(String permission){ From cbd656d86b8db0b47366788260d5c819ecf10dfd Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:40:32 -0500 Subject: [PATCH 11/24] Added `Control#getControlType` --- .../displayentityutils/utils/gizmo/controls/Control.java | 8 +++++++- .../utils/gizmo/controls/ControlType.java | 7 +++++++ .../utils/gizmo/controls/drag/Drag.java | 5 +++-- .../utils/gizmo/controls/drag/RotationDrag.java | 3 ++- .../utils/gizmo/controls/drag/ScaleDrag.java | 3 ++- .../utils/gizmo/controls/drag/TranslationDrag.java | 3 ++- .../utils/gizmo/controls/selector/AxisSelector.java | 5 +++-- .../utils/gizmo/controls/selector/RotationSelector.java | 3 ++- .../utils/gizmo/controls/selector/ScaleSelector.java | 3 ++- .../utils/gizmo/controls/selector/Selector.java | 5 +++-- .../gizmo/controls/selector/TranslationAxisSelector.java | 3 ++- .../gizmo/controls/selector/TranslationPlaneSelector.java | 3 ++- 12 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/ControlType.java diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/Control.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/Control.java index be262d76..c67878c3 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/Control.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/Control.java @@ -6,11 +6,13 @@ public abstract class Control { protected final GizmoAxis axis; + private final ControlType controlType; public static final float MAX_DISTANCE = 15.0f; public static final float MAX_DISTANCE_SQUARED = MAX_DISTANCE*MAX_DISTANCE; - protected Control(GizmoAxis axis){ + protected Control(GizmoAxis axis, ControlType controlType) { this.axis = axis; + this.controlType = controlType;; } public void glow(ActiveGroup gizmo) { @@ -33,5 +35,9 @@ public GizmoAxis getAxis() { return axis; } + public ControlType getControlType() { + return controlType; + } + public abstract String getTag(); } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/ControlType.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/ControlType.java new file mode 100644 index 00000000..d9e2cdeb --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/ControlType.java @@ -0,0 +1,7 @@ +package net.donnypz.displayentityutils.utils.gizmo.controls; + +public enum ControlType { + TRANSLATION, + ROTATION, + SCALE; +} diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/Drag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/Drag.java index a37cd652..9d4462fc 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/Drag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/Drag.java @@ -1,13 +1,14 @@ package net.donnypz.displayentityutils.utils.gizmo.controls.drag; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.controls.Control; import org.bukkit.entity.Player; public abstract class Drag extends Control { - public Drag(GizmoAxis axis) { - super(axis); + public Drag(GizmoAxis axis, ControlType type) { + super(axis, type); } public abstract void updatePosition(Player player); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/RotationDrag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/RotationDrag.java index d32d608d..f51a98d4 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/RotationDrag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/RotationDrag.java @@ -5,6 +5,7 @@ import net.donnypz.displayentityutils.utils.gizmo.GizmoSelectionMode; import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.util.GizmoMathUtil; import org.bukkit.Location; @@ -31,7 +32,7 @@ public class RotationDrag extends Drag { private final Vector3f lastDirection = new Vector3f(); public RotationDrag(Player player, GizmoSessionImpl gizmo, GizmoAxis axis) { - super(axis); + super(axis, ControlType.ROTATION); this.gizmo = gizmo; this.originalAxis = axis.getDirections()[0]; this.pivotPoint = gizmo.getGizmoModel().getLocation().toVector().toVector3f(); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/ScaleDrag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/ScaleDrag.java index 9fef8e65..6ff43b55 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/ScaleDrag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/ScaleDrag.java @@ -6,6 +6,7 @@ import net.donnypz.displayentityutils.utils.gizmo.GizmoSelectionMode; import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.util.GizmoTitleUtil; import net.kyori.adventure.text.Component; @@ -31,7 +32,7 @@ public class ScaleDrag extends Drag { private final Vector3f lastHitPoint; public ScaleDrag(Player player, GizmoSessionImpl gizmo, GizmoAxis axis) { - super(axis); + super(axis, ControlType.SCALE); this.gizmo = gizmo; this.originalAxis = axis.getDirections()[0]; diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationDrag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationDrag.java index bdee48b2..4eb3e5cf 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationDrag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationDrag.java @@ -7,6 +7,7 @@ import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; import net.donnypz.displayentityutils.utils.gizmo.TranslationMode; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.util.GizmoTitleUtil; import net.kyori.adventure.text.Component; @@ -34,7 +35,7 @@ public abstract class TranslationDrag extends Drag { protected final Vector3f[] currentAxesDir; public TranslationDrag(Player player, GizmoSessionImpl gizmo, GizmoAxis axis) { - super(axis); + super(axis, ControlType.TRANSLATION); this.gizmo = gizmo; this.originalAxes = axis.getDirections(); this.currentAxesDir = IntStream.range(0, 2) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/AxisSelector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/AxisSelector.java index b8a35e6e..83cc569d 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/AxisSelector.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/AxisSelector.java @@ -1,11 +1,12 @@ package net.donnypz.displayentityutils.utils.gizmo.controls.selector; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; public abstract class AxisSelector extends Selector{ - AxisSelector(GizmoAxis axis) { - super(axis); + AxisSelector(GizmoAxis axis, ControlType controlType) { + super(axis, controlType); } public static TranslationAxisSelector x() { diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/RotationSelector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/RotationSelector.java index c0c84162..219ea59b 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/RotationSelector.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/RotationSelector.java @@ -2,6 +2,7 @@ import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.Drag; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.RotationDrag; @@ -16,7 +17,7 @@ public class RotationSelector extends Selector { private float ringThickness = 0.075f; RotationSelector(GizmoAxis axis) { - super(axis); + super(axis, ControlType.ROTATION); } public static RotationSelector x() { diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/ScaleSelector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/ScaleSelector.java index 71b7218b..a298ec83 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/ScaleSelector.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/ScaleSelector.java @@ -1,6 +1,7 @@ package net.donnypz.displayentityutils.utils.gizmo.controls.selector; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.Drag; @@ -17,7 +18,7 @@ public class ScaleSelector extends AxisSelector { private float radius = 0.125f; public ScaleSelector(GizmoAxis axis) { - super(axis); + super(axis, ControlType.SCALE); this.localStart = axis.getDirections()[0] .mul(1.2f); this.localEnd = axis.getDirections()[0] diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/Selector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/Selector.java index 64d29c18..65d81f61 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/Selector.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/Selector.java @@ -1,6 +1,7 @@ package net.donnypz.displayentityutils.utils.gizmo.controls.selector; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.controls.Control; @@ -10,8 +11,8 @@ public abstract class Selector extends Control { - public Selector(GizmoAxis axis) { - super(axis); + public Selector(GizmoAxis axis, ControlType controlType) { + super(axis, controlType); } public abstract float intersect(GizmoSpace gizmoSpace, Player player, Location gizmoLocation); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/TranslationAxisSelector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/TranslationAxisSelector.java index cc55c365..e874e8ae 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/TranslationAxisSelector.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/TranslationAxisSelector.java @@ -1,6 +1,7 @@ package net.donnypz.displayentityutils.utils.gizmo.controls.selector; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.Drag; @@ -17,7 +18,7 @@ public class TranslationAxisSelector extends AxisSelector { private float radius = 0.075f; TranslationAxisSelector(GizmoAxis axis) { - super(axis); + super(axis, ControlType.TRANSLATION); this.localStart = axis.getDirections()[0] .mul(0.1125f); this.localEnd = axis.getDirections()[0]; diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/TranslationPlaneSelector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/TranslationPlaneSelector.java index 345cb85c..bc0fb345 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/TranslationPlaneSelector.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/TranslationPlaneSelector.java @@ -2,6 +2,7 @@ import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.Drag; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.TranslationPlaneDrag; @@ -17,7 +18,7 @@ public class TranslationPlaneSelector extends Selector { private float size = 0.25f; TranslationPlaneSelector(GizmoAxis axis) { - super(axis); + super(axis, ControlType.TRANSLATION); Vector3f[] axes = axis.getDirections(); Vector3f axis1 = axes[0]; Vector3f axis2 = axes[1]; From 1d706c880e52aed8bac1f29bc18bdf1213f5d55d Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 00:32:21 -0500 Subject: [PATCH 12/24] Stop player movement when selecting a Gizmo control --- .../donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java index 94ec82af..c7650bc8 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java @@ -357,6 +357,7 @@ public Drag selectHovered() { ActivePartSelection sel = DEUUser.getOrCreateUser(player).getSelectedPartSelection(); if (sel == null) return null; + player.setVelocity(new Vector()); activeDrag = hoveredSelector.getDrag(player, this); } return activeDrag; From a373a554bcc6ee3403d1c2cd4ddd1c990b79a46e Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 04:59:12 -0500 Subject: [PATCH 13/24] Correctly make adding a tag to a cloned part optional --- .../command/parts/PartsCloneCMD.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/parts/PartsCloneCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/parts/PartsCloneCMD.java index eb2b4175..4d598f61 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/command/parts/PartsCloneCMD.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/parts/PartsCloneCMD.java @@ -63,12 +63,14 @@ protected boolean executeSinglePartAction(@NotNull Player player, @Nullable Acti if (!oArgs.isValidOptions()) return false; String tag = oArgs.getOption(ADD_TAG); - if (!DisplayUtils.isValidTag(tag) || tag.isBlank()) { - DisplayEntityPluginCommand.invalidTag(player, tag); - return false; - } - else{ - clonedPart.addTag(tag); + if (!tag.isBlank()){ + if (!DisplayUtils.isValidTag(tag)){ + DisplayEntityPluginCommand.invalidTag(player, tag); + return false; + } + else{ + clonedPart.addTag(tag); + } } player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Part cloned!", NamedTextColor.GREEN))); From 4e8bd7935636e6345568c1142e7d84cfac194069 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 05:12:22 -0500 Subject: [PATCH 14/24] Register permissions on enable, rather than in static block --- .../net/donnypz/displayentityutils/DisplayEntityPlugin.java | 3 +++ .../net/donnypz/displayentityutils/command/Permission.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java index 8ddd3113..7086cc09 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java @@ -5,6 +5,7 @@ import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.event.PacketListenerPriority; import net.donnypz.displayentityutils.command.DisplayEntityPluginCommand; +import net.donnypz.displayentityutils.command.Permission; import net.donnypz.displayentityutils.database.*; import net.donnypz.displayentityutils.listeners.autogroup.DEULoadingListeners; import net.donnypz.displayentityutils.listeners.bdengine.BDEngineConversionListener; @@ -91,6 +92,8 @@ public void onEnable() { else{ getComponentLogger().warn(Component.text("bStats disabled on server using dev version.")); } + + Permission.registerPermissions(); getServer().getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Plugin Enabled!", NamedTextColor.GREEN))); } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/Permission.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/Permission.java index 83770f2d..7a4b98f0 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/command/Permission.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/Permission.java @@ -139,7 +139,8 @@ public enum Permission { BDENGINE_SPAWN_MODEL("deu.bdengine.spawnmodel"), BDENGINE_IMPORT("deu.bdengine.import"); - static{ + + public static void registerPermissions(){ for (Permission permission : Permission.values()){ registerPermission(permission); } From b13ea71c4fa8bd4de65adee4a93ba8f620a56131 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 05:18:23 -0500 Subject: [PATCH 15/24] update documentation --- .../net/donnypz/displayentityutils/utils/DisplayUtils.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayUtils.java b/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayUtils.java index 04f2acaa..737b2839 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayUtils.java +++ b/api/src/main/java/net/donnypz/displayentityutils/utils/DisplayUtils.java @@ -371,6 +371,7 @@ public static boolean isInLoadedChunk(@NotNull Entity entity){ /** * Get the {@link SpawnedDisplayEntityGroup} passengers, with a specific group tag, riding an entity * @param vehicleEntity the entity + * @param groupTag the group tag * @return a list */ public static List getGroupPassengers(@NotNull Entity vehicleEntity, @NotNull String groupTag){ @@ -839,7 +840,8 @@ public static boolean isValidTag(@NotNull String tag){ /** * Remove a part tag from an entity - * @param tag the tag to remove from this part + * @param entity the entity to remove the tag from + * @param tag the tag to remove */ public static void removeTag(@NotNull Entity entity, @NotNull String tag){ removeFromPDCList(entity, tag, DisplayKeys.Part.PART_TAGS); @@ -847,6 +849,7 @@ public static void removeTag(@NotNull Entity entity, @NotNull String tag){ /** * Remove part tags from an entity + * @param entity the entity to remove the tag from * @param tags the tags to remove from this part */ public static void removeTags(@NotNull Entity entity, @NotNull List tags){ @@ -885,6 +888,7 @@ static void removeManyFromPDCList(@NotNull Entity entity, List elements, /** * Gets the part tags belonging to an entity + * @param entity the entity * @return The part's part tags. */ public static @NotNull List getTags(@NotNull Entity entity){ @@ -902,6 +906,7 @@ static void removeManyFromPDCList(@NotNull Entity entity, List elements, /** * Determine whether a part entity has a part tag + * @param entity the entity * @param tag the tag to check for * @return true if this part has the tag */ From 64a9148cc3420176257d974be2ab4c2ef04b6e1f Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:31:24 -0500 Subject: [PATCH 16/24] inform player that selection mode cannot change with single entity selection --- .../listeners/gizmo/DEUGizmoListener.java | 2 ++ .../utils/gizmo/util/GizmoTitleUtil.java | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/listeners/gizmo/DEUGizmoListener.java b/plugin/src/main/java/net/donnypz/displayentityutils/listeners/gizmo/DEUGizmoListener.java index 1cb45cc7..ac25d4da 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/listeners/gizmo/DEUGizmoListener.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/listeners/gizmo/DEUGizmoListener.java @@ -180,6 +180,8 @@ private void switchSpace(Player player){ private void switchSelectionMode(Player player){ DEUUser user = DEUUser.getUser(player); if (user != null && user.getSelectedPartSelection() instanceof SinglePartSelection){ + GizmoTitleUtil.showCannotChangeSelectionMode(player); + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_COW_BELL, 1, 0.8f); return; } GizmoSessionImpl gizmo = (GizmoSessionImpl) getGizmoSession(player); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoTitleUtil.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoTitleUtil.java index 55a7bc14..8e2b4d1e 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoTitleUtil.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoTitleUtil.java @@ -2,8 +2,11 @@ import net.donnypz.displayentityutils.utils.gizmo.GizmoSelectionMode; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.Snap; import net.donnypz.displayentityutils.utils.gizmo.TranslationMode; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.title.Title; import org.bukkit.entity.Player; @@ -41,4 +44,10 @@ public static void showNewSelectionMode(Player player, GizmoSelectionMode select showSubtitle(player, MiniMessage.miniMessage().deserialize("Selection Mode: " + selectionMode.name())); } + + public static void showCannotChangeSelectionMode(Player player){ + showSubtitle(player, + MiniMessage.miniMessage().deserialize("Cannot change selection mode for single entity")); + } + } From cb75a4c2f9611cdf36873ff17c154a5e8fe6fea5 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:13:30 -0500 Subject: [PATCH 17/24] Add GizmoSession#teleport --- .../displayentityutils/utils/gizmo/GizmoSession.java | 2 ++ .../utils/gizmo/GizmoSessionImpl.java | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSession.java b/api/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSession.java index 288719b4..70ad6573 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSession.java +++ b/api/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSession.java @@ -36,6 +36,8 @@ public interface GizmoSession { void teleport(Vector direction); + void teleport(Location location); + void deselectHide(); void selectShow(Location spawnLocation); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java index c7650bc8..1a2bee31 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java @@ -115,10 +115,7 @@ public PacketDisplayEntityGroup getGizmoModel() { return gizmoModel; } - public void teleport(Location location) { - if (!valid) return; - gizmoModel.teleport(location, true); - } + public void setPitch(float pitch) { gizmoModel.setPitch(pitch, false); @@ -193,6 +190,12 @@ public void teleport(Vector direction) { gizmoModel.teleport(direction, direction.length()); } + @Override + public void teleport(Location location) { + if (!valid) return; + gizmoModel.teleport(location, true); + } + @Override public float getScale() { return scale; From d5364b4a35ddde8a26d896bff5b77ef96b507cb6 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 21:12:41 -0500 Subject: [PATCH 18/24] Add `/deu display roundtranslation` --- .../command/display/DisplayCMD.java | 1 + .../display/DisplayRoundTranslationCMD.java | 106 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/command/display/DisplayRoundTranslationCMD.java diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/display/DisplayCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/display/DisplayCMD.java index 96671614..0a000fb8 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/command/display/DisplayCMD.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/display/DisplayCMD.java @@ -12,6 +12,7 @@ public DisplayCMD(){ new DisplayViewRangeCMD(this); new DisplayBillboardCMD(this); new DisplayTranslateCMD(this); + new DisplayRoundTranslationCMD(this); new DisplayResetCMD(this); new DisplayRotateCMD(this); new DisplaySetRotationCMD(this); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/display/DisplayRoundTranslationCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/display/DisplayRoundTranslationCMD.java new file mode 100644 index 00000000..4f910756 --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/display/DisplayRoundTranslationCMD.java @@ -0,0 +1,106 @@ +package net.donnypz.displayentityutils.command.display; + +import net.donnypz.displayentityutils.DisplayAPI; +import net.donnypz.displayentityutils.command.DEUSubCommand; +import net.donnypz.displayentityutils.command.PartsSubCommand; +import net.donnypz.displayentityutils.command.Permission; +import net.donnypz.displayentityutils.managers.DEUUser; +import net.donnypz.displayentityutils.utils.DisplayEntities.ActiveGroup; +import net.donnypz.displayentityutils.utils.DisplayEntities.ActivePart; +import net.donnypz.displayentityutils.utils.DisplayEntities.ActivePartSelection; +import net.donnypz.displayentityutils.utils.DisplayEntities.MultiPartSelection; +import net.donnypz.displayentityutils.utils.gizmo.GizmoSession; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.List; + +class DisplayRoundTranslationCMD extends PartsSubCommand { + DisplayRoundTranslationCMD(@NotNull DEUSubCommand parentSubCommand) { + super("roundtranslation", parentSubCommand, Permission.DISPLAY_TRANSFORM, true); + super.cancelIfDraggingGizmo(); + setTabComplete(2, List.of("x", "y", "z", "all")); + addFlag("-resetpivot"); + } + + @Override + protected void sendIncorrectUsage(@NotNull Player player) { + super.incorrectUsage(player); + } + + @Override + protected boolean executeAllPartsAction(@NotNull Player player, @Nullable ActiveGroup group, @NotNull MultiPartSelection selection, @NotNull String[] args) { + String arg = args[2].toLowerCase(); + if (!isValidArg(player, arg)) return false; + + selection.getParts().forEach(part -> roundTranslation(part, arg)); + player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Translation rounded down for all selected displays!", NamedTextColor.GREEN))); + if (getOptionalArguments(player, args).hasFlag("-resetpivot")) resetGizmoPivot(player, selection); + return true; + } + + @Override + protected boolean executeSinglePartAction(@NotNull Player player, @Nullable ActiveGroup group, @NotNull ActivePartSelection selection, @NotNull ActivePart selectedPart, @NotNull String[] args) { + if (isNotDisplay(player, selectedPart)) return false; + + String arg = args[2].toLowerCase(); + if (!isValidArg(player, arg)) return false; + + roundTranslation(selectedPart, arg); + player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Rounded down selected display's translation!", NamedTextColor.GREEN))); + if (getOptionalArguments(player, args).hasFlag("-resetpivot")) resetGizmoPivot(player, selection); + return true; + } + + boolean isValidArg(Player player, String arg){ + switch (arg) { + case "x", "y", "z", "all" -> { + return true; + } + default -> { + sendIncorrectUsage(player); + return false; + } + } + } + + void roundTranslation(ActivePart part, String arg) { + if (!part.isDisplay()) return; + + Vector3f translation = part.getTransformation().getTranslation(); + Vector roundRemove = new Vector( + arg.equals("x") || arg.equals("all") + ? (int) translation.x - translation.x + : 0, + arg.equals("y") || arg.equals("all") + ? (int) translation.y - translation.y + : 0, + arg.equals("z") || arg.equals("all") + ? (int) translation.z - translation.z + : 0); + if (roundRemove.isZero()) { + return; + } + part.translate(roundRemove, 0, 0); + } + + void resetGizmoPivot(Player player, ActivePartSelection selection) { + GizmoSession gizmo = DEUUser.getOrCreateUser(player).getGizmo(); + + if (gizmo != null) { + gizmo.teleport(selection.getLocation()); + player.sendMessage(Component.text("| Gizmo pivot reset", NamedTextColor.GRAY)); + } + } + + + @Override + protected String getDescription() { + return "Remove the decimal portion of your selected display's translation. (Ex: 12.25 would become 12.0). Optionally reset gizmo pivot with \"-resetpivot\""; + } +} From 361324c2f82ef95a8d96cdbaee1b981cedb01a5c Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 21:15:02 -0500 Subject: [PATCH 19/24] Added `/deu gizmo snaptoblock` --- .../command/gizmo/GizmoCMD.java | 1 + .../command/gizmo/GizmoSnapToBlockCMD.java | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapToBlockCMD.java diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoCMD.java index 3188bf2e..7df508f4 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoCMD.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoCMD.java @@ -22,6 +22,7 @@ public GizmoCMD() { new GizmoToggleCMD(this); new GizmoMoveHereCMD(this); new GizmoResetPivotCMD(this); + new GizmoSnapToBlockCMD(this); } public static GizmoSessionImpl getOrCreateGizmo(Player player, Location spawnLocation) { diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapToBlockCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapToBlockCMD.java new file mode 100644 index 00000000..93333f48 --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapToBlockCMD.java @@ -0,0 +1,72 @@ +package net.donnypz.displayentityutils.command.gizmo; + +import net.donnypz.displayentityutils.DisplayAPI; +import net.donnypz.displayentityutils.command.DEUSubCommand; +import net.donnypz.displayentityutils.command.Permission; +import net.donnypz.displayentityutils.command.PlayerSubCommand; +import net.donnypz.displayentityutils.managers.DEUUser; +import net.donnypz.displayentityutils.utils.DisplayEntities.ActivePartSelection; +import net.donnypz.displayentityutils.utils.DisplayEntities.MultiPartSelection; +import net.donnypz.displayentityutils.utils.DisplayEntities.SinglePartSelection; +import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Location; +import org.bukkit.entity.Display; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.util.Transformation; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3f; + +public class GizmoSnapToBlockCMD extends PlayerSubCommand { + public GizmoSnapToBlockCMD(@NotNull DEUSubCommand parentSubCommand) { + super("snaptoblock", parentSubCommand, Permission.GIZMO_USE); + addFlag("-translation"); + } + + @Override + public void execute(Player player, String[] args) { + if (!hasMinimumArguments(player, args)) return; + GizmoSessionImpl gizmo = GizmoCMD.getOrCreateGizmo(player, null); + if (GizmoCMD.isDraggingCancel(player, gizmo)) return; + + Location loc = gizmo.getGizmoModel().getLocation(); + + gizmo.teleport(zeroLocation(loc)); + + DEUUser user = DEUUser.getOrCreateUser(player); + ActivePartSelection sel = user.getSelectedPartSelection(); + if (sel instanceof SinglePartSelection s){ + s.getSelectedPart().teleport(zeroLocation(s.getSelectedPart().getLocation())); + Entity entity = s.getSelectedPart().getEntity(); + if (entity instanceof Display d && getOptionalArguments(player, args).hasFlag("-translation")){ + Transformation t = d.getTransformation(); + s.getSelectedPart().setTransformation(new Transformation( + new Vector3f(), + t.getLeftRotation(), + t.getScale(), + t.getRightRotation() + )); + } + } + else if (sel instanceof MultiPartSelection m){ + m.getGroup().teleport(zeroLocation(m.getGroup().getLocation()), true); + } + + player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Snapping enabled", NamedTextColor.GREEN))); + + } + + private Location zeroLocation(Location location){ + location.setX(location.getBlockX()); + location.setY(location.getBlockY()); + location.setZ(location.getBlockZ()); + return location; + } + + @Override + protected String getDescription() { + return "Teleport your selection and gizmo to their location's block coordinate. Use \"-translation\" to optionally reset translation of an ungrouped entity"; + } +} From 9828b774d71cc9525b994c54331f93e76b2ecaa2 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 22:30:44 -0500 Subject: [PATCH 20/24] Remove `/deu gizmo snaptoblock` --- .../command/gizmo/GizmoSnapToBlockCMD.java | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapToBlockCMD.java diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapToBlockCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapToBlockCMD.java deleted file mode 100644 index 93333f48..00000000 --- a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapToBlockCMD.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.donnypz.displayentityutils.command.gizmo; - -import net.donnypz.displayentityutils.DisplayAPI; -import net.donnypz.displayentityutils.command.DEUSubCommand; -import net.donnypz.displayentityutils.command.Permission; -import net.donnypz.displayentityutils.command.PlayerSubCommand; -import net.donnypz.displayentityutils.managers.DEUUser; -import net.donnypz.displayentityutils.utils.DisplayEntities.ActivePartSelection; -import net.donnypz.displayentityutils.utils.DisplayEntities.MultiPartSelection; -import net.donnypz.displayentityutils.utils.DisplayEntities.SinglePartSelection; -import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.Location; -import org.bukkit.entity.Display; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.util.Transformation; -import org.jetbrains.annotations.NotNull; -import org.joml.Vector3f; - -public class GizmoSnapToBlockCMD extends PlayerSubCommand { - public GizmoSnapToBlockCMD(@NotNull DEUSubCommand parentSubCommand) { - super("snaptoblock", parentSubCommand, Permission.GIZMO_USE); - addFlag("-translation"); - } - - @Override - public void execute(Player player, String[] args) { - if (!hasMinimumArguments(player, args)) return; - GizmoSessionImpl gizmo = GizmoCMD.getOrCreateGizmo(player, null); - if (GizmoCMD.isDraggingCancel(player, gizmo)) return; - - Location loc = gizmo.getGizmoModel().getLocation(); - - gizmo.teleport(zeroLocation(loc)); - - DEUUser user = DEUUser.getOrCreateUser(player); - ActivePartSelection sel = user.getSelectedPartSelection(); - if (sel instanceof SinglePartSelection s){ - s.getSelectedPart().teleport(zeroLocation(s.getSelectedPart().getLocation())); - Entity entity = s.getSelectedPart().getEntity(); - if (entity instanceof Display d && getOptionalArguments(player, args).hasFlag("-translation")){ - Transformation t = d.getTransformation(); - s.getSelectedPart().setTransformation(new Transformation( - new Vector3f(), - t.getLeftRotation(), - t.getScale(), - t.getRightRotation() - )); - } - } - else if (sel instanceof MultiPartSelection m){ - m.getGroup().teleport(zeroLocation(m.getGroup().getLocation()), true); - } - - player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Snapping enabled", NamedTextColor.GREEN))); - - } - - private Location zeroLocation(Location location){ - location.setX(location.getBlockX()); - location.setY(location.getBlockY()); - location.setZ(location.getBlockZ()); - return location; - } - - @Override - protected String getDescription() { - return "Teleport your selection and gizmo to their location's block coordinate. Use \"-translation\" to optionally reset translation of an ungrouped entity"; - } -} From 6b3c56c91677e454588f88378ed5b938467fdd94 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:36:59 -0500 Subject: [PATCH 21/24] Clone selection when clicking gizmo pivot point (white cube) --- .../utils/gizmo/GizmoSessionImpl.java | 29 +++- .../utils/gizmo/controls/GizmoAxis.java | 2 + .../controls/selector/CloneSelector.java | 22 +++ .../gizmo/controls/selector/CubeSelector.java | 137 ++++++++++++++++++ .../controls/selector/ScaleSelector.java | 113 +-------------- 5 files changed, 190 insertions(+), 113 deletions(-) create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/CloneSelector.java create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/CubeSelector.java diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java index 1a2bee31..532a83eb 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java @@ -9,10 +9,7 @@ import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.controls.Control; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.Drag; -import net.donnypz.displayentityutils.utils.gizmo.controls.selector.RotationSelector; -import net.donnypz.displayentityutils.utils.gizmo.controls.selector.ScaleSelector; -import net.donnypz.displayentityutils.utils.gizmo.controls.selector.Selector; -import net.donnypz.displayentityutils.utils.gizmo.controls.selector.AxisSelector; +import net.donnypz.displayentityutils.utils.gizmo.controls.selector.*; import net.donnypz.displayentityutils.utils.gizmo.util.GizmoTitleUtil; import net.donnypz.displayentityutils.utils.relativepoints.RelativePointUtils; import net.donnypz.displayentityutils.utils.version.folia.Scheduler; @@ -362,6 +359,30 @@ public Drag selectHovered() { player.setVelocity(new Vector()); activeDrag = hoveredSelector.getDrag(player, this); + if (activeDrag == null && hoveredSelector instanceof CloneSelector) { + boolean result; + if (sel instanceof MultiPartSelection m){ + ActiveGroup clonedGroup = m.getGroup().clone(m.getLocation()); + result = GroupCMD.selectGroupSilentSuccess(player, clonedGroup, false, true); + } + else{ + ActivePart clonedPart = sel.getSelectedPart().clone(); + SinglePartSelection newSelection = new SinglePartSelection((SpawnedDisplayEntityPart) clonedPart); + + DEUUser user = DEUUser.getUser(player); + user.setSelectedPartSelection(newSelection, false); + result = true; + } + + if (result) { + player.sendMessage(DisplayAPI.pluginPrefix + .append(Component.text("Selection cloned!", NamedTextColor.GREEN))); + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_CHIME, 1f, 1.25f); + + Location gizmoLoc = gizmoModel.getLocation(); + gizmoLoc.getWorld().spawnParticle(Particle.END_ROD, gizmoLoc, 10, 0.1,0.1,0.1,0.1f); + } + } } return activeDrag; } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/GizmoAxis.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/GizmoAxis.java index 5e7ee174..2d656380 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/GizmoAxis.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/GizmoAxis.java @@ -6,6 +6,8 @@ import java.util.Arrays; public enum GizmoAxis { + CENTER(Color.WHITE, "pivot", new Vector3f(0,0,0)), + //axes X(Color.RED, "move_x", new Vector3f(1, 0, 0)), Y(Color.LIME, "move_y", new Vector3f(0, 1, 0)), diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/CloneSelector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/CloneSelector.java new file mode 100644 index 00000000..77154102 --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/CloneSelector.java @@ -0,0 +1,22 @@ +package net.donnypz.displayentityutils.utils.gizmo.controls.selector; + +import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; +import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; +import net.donnypz.displayentityutils.utils.gizmo.controls.drag.Drag; +import org.bukkit.entity.Player; + +public class CloneSelector extends CubeSelector { + public CloneSelector() { + super(GizmoAxis.CENTER); + } + + @Override + public String getTag() { + return axis.getTag(); + } + + @Override + public Drag getDrag(Player player, GizmoSessionImpl gizmo) { + return null; + } +} diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/CubeSelector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/CubeSelector.java new file mode 100644 index 00000000..c80ed0da --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/CubeSelector.java @@ -0,0 +1,137 @@ +package net.donnypz.displayentityutils.utils.gizmo.controls.selector; + +import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; +import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; +import net.donnypz.displayentityutils.utils.gizmo.util.GizmoMathUtil; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.joml.Vector3f; + +public abstract class CubeSelector extends AxisSelector { + + protected final Vector3f localStart; + protected final Vector3f localEnd; + protected float radius = 0.125f; + + public CubeSelector(GizmoAxis axis) { + super(axis, ControlType.SCALE); + this.localStart = axis.getDirections()[0] + .mul(1.2f); + this.localEnd = axis.getDirections()[0] + .mul(1.40f); + } + + @Override + public String getTag() { + return axis.getScaleTag(); + } + + + @Override + public float intersect(GizmoSpace gizmoSpace, Player player, Location gizmoLocation) { + Location eyeLoc = player.getEyeLocation(); + + + Vector3f rayOrigin = eyeLoc.toVector() + .toVector3f() + .sub(gizmoLocation.toVector().toVector3f()); + + Vector3f ray = eyeLoc.getDirection() + .toVector3f() + .normalize(); + + + Vector3f axisStart = GizmoMathUtil.rotate( + new Vector3f(localStart), + gizmoSpace, + gizmoLocation + ); + + Vector3f axisEnd = GizmoMathUtil.rotate( + new Vector3f(localEnd), + gizmoSpace, + gizmoLocation + ); + + + Vector3f axisDir = new Vector3f(axisEnd) + .sub(axisStart); + + float axisLength = axisDir.length(); + + //Point selector + if (axisLength < 1e-6f) { // ~ 0 + // Point selector + Vector3f toPoint = new Vector3f(axisStart) + .sub(rayOrigin); + + float distanceAlongRay = toPoint.dot(ray); + + if (distanceAlongRay < 0) { + return -1; + } + + Vector3f closestPointOnRay = new Vector3f(rayOrigin) + .fma(distanceAlongRay, ray); + + if (closestPointOnRay.distance(axisStart) > radius) { + return -1; + } + + return distanceAlongRay; + } + + axisDir.normalize(); + + Vector3f axisStartToRayOrigin = new Vector3f(rayOrigin) + .sub(axisStart); + + + //see how much player's ray and axis dir point in same dir + float rayDotAxis = ray.dot(axisDir); + + //denominator for closest-point calculation. + float denom = 1 - rayDotAxis * rayDotAxis; + + + //parallel or opposite, ray & axis + if (denom < 1e-6f) return -1; + + float startDotAxis = axisStartToRayOrigin.dot(axisDir); + + //distance from player eye to closest point on ray + float distanceAlongRay = + (rayDotAxis * startDotAxis - axisStartToRayOrigin.dot(ray)) + / denom; + + + if (distanceAlongRay < 0) return -1; + + Vector3f closestPointOnRay = new Vector3f(rayOrigin) + .fma(distanceAlongRay, ray); + + + float distanceAlongAxis = axisStartToRayOrigin + .add(new Vector3f(ray).mul(distanceAlongRay)) + .dot(axisDir); + + + if (distanceAlongAxis < 0 || distanceAlongAxis > axisLength) return -1; + + + Vector3f axisPoint = new Vector3f(axisStart) + .fma(distanceAlongAxis, axisDir); + + if (closestPointOnRay.distance(axisPoint) > radius) return -1; + + return distanceAlongRay; + } + + @Override + public void scale(float oldScale, float scaleMultiplier) { + GizmoMathUtil.scale(localStart, oldScale, scaleMultiplier); + GizmoMathUtil.scale(localEnd, oldScale, scaleMultiplier); + radius = (radius / oldScale) * scaleMultiplier; + } +} diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/ScaleSelector.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/ScaleSelector.java index a298ec83..719bc495 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/ScaleSelector.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/selector/ScaleSelector.java @@ -1,123 +1,18 @@ package net.donnypz.displayentityutils.utils.gizmo.controls.selector; -import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; -import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; -import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; +import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.Drag; import net.donnypz.displayentityutils.utils.gizmo.controls.drag.ScaleDrag; -import net.donnypz.displayentityutils.utils.gizmo.util.GizmoMathUtil; -import org.bukkit.Location; import org.bukkit.entity.Player; -import org.joml.Vector3f; - -public class ScaleSelector extends AxisSelector { - - private final Vector3f localStart; - private final Vector3f localEnd; - private float radius = 0.125f; +public class ScaleSelector extends CubeSelector{ public ScaleSelector(GizmoAxis axis) { - super(axis, ControlType.SCALE); - this.localStart = axis.getDirections()[0] - .mul(1.2f); - this.localEnd = axis.getDirections()[0] - .mul(1.40f); - } - - @Override - public String getTag() { - return axis.getScaleTag(); - } - - - @Override - public float intersect(GizmoSpace gizmoSpace, Player player, Location gizmoLocation) { - Location eyeLoc = player.getEyeLocation(); - - - Vector3f rayOrigin = eyeLoc.toVector() - .toVector3f() - .sub(gizmoLocation.toVector().toVector3f()); - - Vector3f ray = eyeLoc.getDirection() - .toVector3f() - .normalize(); - - - Vector3f axisStart = GizmoMathUtil.rotate( - new Vector3f(localStart), - gizmoSpace, - gizmoLocation - ); - - Vector3f axisEnd = GizmoMathUtil.rotate( - new Vector3f(localEnd), - gizmoSpace, - gizmoLocation - ); - - - Vector3f axisDir = new Vector3f(axisEnd) - .sub(axisStart); - - float axisLength = axisDir.length(); - axisDir.normalize(); - - - Vector3f axisStartToRayOrigin = new Vector3f(rayOrigin) - .sub(axisStart); - - - //see how much player's ray and axis dir point in same dir - float rayDotAxis = ray.dot(axisDir); - - //denominator for closest-point calculation. - float denom = 1 - rayDotAxis * rayDotAxis; - - - //parallel or opposite, ray & axis - if (denom < 1e-6f) return -1; - - float startDotAxis = axisStartToRayOrigin.dot(axisDir); - - //distance from player eye to closest point on ray - float distanceAlongRay = - (rayDotAxis * startDotAxis - axisStartToRayOrigin.dot(ray)) - / denom; - - - if (distanceAlongRay < 0) return -1; - - Vector3f closestPointOnRay = new Vector3f(rayOrigin) - .fma(distanceAlongRay, ray); - - - float distanceAlongAxis = axisStartToRayOrigin - .add(new Vector3f(ray).mul(distanceAlongRay)) - .dot(axisDir); - - - if (distanceAlongAxis < 0 || distanceAlongAxis > axisLength) return -1; - - - Vector3f axisPoint = new Vector3f(axisStart) - .fma(distanceAlongAxis, axisDir); - - if (closestPointOnRay.distance(axisPoint) > radius) return -1; - - return distanceAlongRay; - } - - @Override - public void scale(float oldScale, float scaleMultiplier) { - GizmoMathUtil.scale(localStart, oldScale, scaleMultiplier); - GizmoMathUtil.scale(localEnd, oldScale, scaleMultiplier); - radius = (radius / oldScale) * scaleMultiplier; + super(axis); } @Override public Drag getDrag(Player player, GizmoSessionImpl gizmo) { return new ScaleDrag(player, gizmo, axis); } -} +} \ No newline at end of file From 155e7586e849d957295a5f6477e287509ac5e3c6 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:36:01 -0500 Subject: [PATCH 22/24] Add snapping to gizmo controls --- .../command/gizmo/GizmoCMD.java | 3 +- .../command/gizmo/GizmoSnapValueCMD.java | 59 +++++++ .../command/gizmo/GizmoToggleSnapCMD.java | 40 +++++ .../listeners/gizmo/DEUGizmoListener.java | 74 +++++++-- .../utils/gizmo/GizmoSessionImpl.java | 12 ++ .../utils/gizmo/GizmoWand.java | 45 ++++-- .../displayentityutils/utils/gizmo/Snap.java | 108 +++++++++++++ .../gizmo/controls/drag/RotationDrag.java | 32 +++- .../utils/gizmo/controls/drag/ScaleDrag.java | 66 ++++++-- .../controls/drag/TranslationAxisDrag.java | 26 ++-- .../gizmo/controls/drag/TranslationDrag.java | 146 ++++++++++++++---- .../controls/drag/TranslationPlaneDrag.java | 50 ++++-- .../utils/gizmo/util/GizmoMathUtil.java | 4 + .../utils/gizmo/util/GizmoTitleUtil.java | 30 ++++ 14 files changed, 596 insertions(+), 99 deletions(-) create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapValueCMD.java create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoToggleSnapCMD.java create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/Snap.java diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoCMD.java index 7df508f4..14f6cdad 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoCMD.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoCMD.java @@ -22,7 +22,8 @@ public GizmoCMD() { new GizmoToggleCMD(this); new GizmoMoveHereCMD(this); new GizmoResetPivotCMD(this); - new GizmoSnapToBlockCMD(this); + new GizmoSnapValueCMD(this); + new GizmoToggleSnapCMD(this); } public static GizmoSessionImpl getOrCreateGizmo(Player player, Location spawnLocation) { diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapValueCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapValueCMD.java new file mode 100644 index 00000000..41ec863b --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoSnapValueCMD.java @@ -0,0 +1,59 @@ +package net.donnypz.displayentityutils.command.gizmo; + +import net.donnypz.displayentityutils.DisplayAPI; +import net.donnypz.displayentityutils.command.DEUSubCommand; +import net.donnypz.displayentityutils.command.Permission; +import net.donnypz.displayentityutils.command.PlayerSubCommand; +import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; +import net.donnypz.displayentityutils.utils.gizmo.Snap; +import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class GizmoSnapValueCMD extends PlayerSubCommand { + + public GizmoSnapValueCMD(@NotNull DEUSubCommand parentSubCommand) { + super("snapvalue", parentSubCommand, Permission.GIZMO_USE); + setTabComplete(2, ""); + } + + @Override + public void execute(Player player, String[] args) { + GizmoSessionImpl gizmo = GizmoCMD.getOrCreateGizmo(player, null); + + + if (!gizmo.isScanning() || !gizmo.isDragging()) { + player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("You must have a Gizmo control selected to do this!", NamedTextColor.RED))); + return; + } + + + try { + Snap snap = gizmo.getSnap(); + ControlType controlType = gizmo.getDragControl().getControlType(); + float snapValue = Float.parseFloat(args[2]); + if (snapValue <= 0) throw new NumberFormatException(); + snap.setSnapValue(snapValue); + final float trueSnapValue = snap.getSnapValue(); + player.sendMessage(DisplayAPI.pluginPrefix + .append(MiniMessage + .miniMessage() + .deserialize("Snapping set to " + trueSnapValue + " for " + controlType.name() + " controls" + ) + ) + ); + } catch (NumberFormatException e) { + player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Enter a number greater than 0 for the snap value!", NamedTextColor.RED))); + } + + + } + + @Override + protected String getDescription() { + return "Set the snapping value for your selected Gizmo control"; + } +} diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoToggleSnapCMD.java b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoToggleSnapCMD.java new file mode 100644 index 00000000..d9ca7b6c --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/command/gizmo/GizmoToggleSnapCMD.java @@ -0,0 +1,40 @@ +package net.donnypz.displayentityutils.command.gizmo; + +import net.donnypz.displayentityutils.DisplayAPI; +import net.donnypz.displayentityutils.command.DEUSubCommand; +import net.donnypz.displayentityutils.command.Permission; +import net.donnypz.displayentityutils.command.PlayerSubCommand; +import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; +import net.donnypz.displayentityutils.utils.gizmo.Snap; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class GizmoToggleSnapCMD extends PlayerSubCommand { + public GizmoToggleSnapCMD(@NotNull DEUSubCommand parentSubCommand) { + super("togglesnap", parentSubCommand, Permission.GIZMO_USE); + } + + @Override + public void execute(Player player, String[] args) { + if (!hasMinimumArguments(player, args)) return; + GizmoSessionImpl gizmo = GizmoCMD.getOrCreateGizmo(player, null); + + Snap snap = gizmo.getSnap(); + snap.setEnabled(!snap.isEnabled()); + + if (snap.isEnabled()){ + player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Snapping enabled", NamedTextColor.GREEN))); + } + else{ + player.sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Snapping disabled", NamedTextColor.RED))); + } + + } + + @Override + protected String getDescription() { + return "Toggle snapping of gizmo control movements"; + } +} diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/listeners/gizmo/DEUGizmoListener.java b/plugin/src/main/java/net/donnypz/displayentityutils/listeners/gizmo/DEUGizmoListener.java index ac25d4da..5625859d 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/listeners/gizmo/DEUGizmoListener.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/listeners/gizmo/DEUGizmoListener.java @@ -63,7 +63,7 @@ public void onSelectControl(PlayerInteractAtEntityEvent e) { if (!GizmoManager.isGizmoWand(item)) return; e.setCancelled(true); - GizmoSessionImpl gizmo = (GizmoSessionImpl) getGizmoSession(player); + GizmoSessionImpl gizmo = getGizmoSession(player); if (gizmo == null) return; if (gizmo.hasActiveControl()){ @@ -81,7 +81,7 @@ public void onSelectControl(PlayerInteractEvent e) { if (!player.hasPermission(Permission.GIZMO_USE.getPermission())) return; if (e.getHand() == EquipmentSlot.OFF_HAND) return; - GizmoSessionImpl gizmo = (GizmoSessionImpl) getGizmoSession(player); + GizmoSessionImpl gizmo = getGizmoSession(player); if (gizmo == null) return; if (gizmo.isLastInteractionItemDrop()){ gizmo.setLastInteractionItemDrop(false); @@ -112,7 +112,15 @@ public void onHeldItem(PlayerItemHeldEvent e) { int oldSlot = e.getPreviousSlot(); ItemStack newItem = player.getInventory().getItem(newSlot); ItemStack oldItem = player.getInventory().getItem(oldSlot); - setGizmoScanning(player, newItem, oldItem); + + boolean cancelEvent = changeGizmoSnapValue(player, newSlot, oldSlot); + //Snapping + if (cancelEvent){ + e.setCancelled(true); + } + else{ + setGizmoScanning(player, newItem, oldItem); + } } @EventHandler(priority = EventPriority.LOW) @@ -121,6 +129,7 @@ public void onSlotChange(PlayerInventorySlotChangeEvent e) { int slot = e.getSlot(); ItemStack newItem = e.getNewItemStack(); ItemStack oldItem = e.getOldItemStack(); + if (slot != player.getInventory().getHeldItemSlot()) return; setGizmoScanning(player, newItem, oldItem); } @@ -132,7 +141,13 @@ public void onSwitchItem(PlayerSwapHandItemsEvent e){ if (!GizmoManager.isGizmoWand(toOffhand)) return; e.setCancelled(true); - switchSpace(player); + + if (player.isSneaking()){ + toggleSnapping(player); + } + else{ + switchSpace(player); + } } @EventHandler(priority = EventPriority.LOW) @@ -144,13 +159,13 @@ public void onDropItem(PlayerDropItemEvent e){ e.setCancelled(true); switchTranslationMode(player); - GizmoSessionImpl gizmo = (GizmoSessionImpl) getGizmoSession(player); + GizmoSessionImpl gizmo = getGizmoSession(player); if (gizmo == null) return; gizmo.setLastInteractionItemDrop(true); } private void switchTranslationMode(Player player){ - GizmoSessionImpl gizmo = (GizmoSessionImpl) getGizmoSession(player); + GizmoSessionImpl gizmo = getGizmoSession(player); if (gizmo == null) return; TranslationMode mode = gizmo.getTranslationMode(); @@ -165,7 +180,7 @@ private void switchTranslationMode(Player player){ } private void switchSpace(Player player){ - GizmoSessionImpl gizmo = (GizmoSessionImpl) getGizmoSession(player); + GizmoSessionImpl gizmo = getGizmoSession(player); if (gizmo == null) return; GizmoSpace gizmoSpace = gizmo.getGizmoSpace(); @@ -177,6 +192,16 @@ private void switchSpace(Player player){ player.playSound(player, Sound.BLOCK_NOTE_BLOCK_XYLOPHONE, 1, 1.2f); } + private void toggleSnapping(Player player){ + GizmoSessionImpl gizmo = getGizmoSession(player); + if (gizmo == null) return; + Snap snap = gizmo.getSnap(); + + snap.setEnabled(!snap.isEnabled()); + GizmoTitleUtil.showSnapStatus(player, snap.isEnabled()); + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1.2f); + } + private void switchSelectionMode(Player player){ DEUUser user = DEUUser.getUser(player); if (user != null && user.getSelectedPartSelection() instanceof SinglePartSelection){ @@ -184,7 +209,7 @@ private void switchSelectionMode(Player player){ player.playSound(player, Sound.BLOCK_NOTE_BLOCK_COW_BELL, 1, 0.8f); return; } - GizmoSessionImpl gizmo = (GizmoSessionImpl) getGizmoSession(player); + GizmoSessionImpl gizmo = getGizmoSession(player); if (gizmo == null) return; GizmoSelectionMode mode = gizmo.getSelectionMode(); @@ -197,7 +222,7 @@ private void switchSelectionMode(Player player){ } private void setGizmoStatus(Player player, boolean leftClick) { - GizmoSessionImpl gizmo = (GizmoSessionImpl) getGizmoSession(player); + GizmoSessionImpl gizmo = getGizmoSession(player); if (gizmo == null) return; if (leftClick) { @@ -240,9 +265,36 @@ private void setGizmoScanning(Player player, gizmo.setScanning(true); } - private GizmoSession getGizmoSession(Player player) { + private boolean changeGizmoSnapValue(Player player, int newSlot, int oldSlot){ //boolean = cancel event? + GizmoSessionImpl gizmo = getGizmoSession(player); + if (gizmo == null) return false; + + if (!gizmo.isScanning() || !gizmo.isDragging()) return false; + + int difference = newSlot - oldSlot; + + boolean isScrollDown; + if (difference == 1 || difference == -8) { + isScrollDown = true; + } else if (difference == -1 || difference == 8) { + isScrollDown = false; + } + else{ //used number keys instead of scroll-wheel + return true; + } + + Snap snap = gizmo.getSnap(); + snap.update(isScrollDown); + + Drag drag = gizmo.getDragControl(); + GizmoTitleUtil.showSnapValue(player, snap, drag.getControlType()); + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1.2f); + return true; + } + + private GizmoSessionImpl getGizmoSession(Player player) { DEUUser user = DEUUser.getUser(player); if (user == null) return null; - return user.getGizmo(); + return (GizmoSessionImpl) user.getGizmo(); } } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java index 532a83eb..52277bb1 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoSessionImpl.java @@ -1,6 +1,7 @@ package net.donnypz.displayentityutils.utils.gizmo; import net.donnypz.displayentityutils.DisplayAPI; +import net.donnypz.displayentityutils.command.group.GroupCMD; import net.donnypz.displayentityutils.events.GroupSpawnedEvent; import net.donnypz.displayentityutils.managers.DEUUser; import net.donnypz.displayentityutils.managers.DisplayGroupManager; @@ -18,6 +19,8 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; @@ -33,6 +36,7 @@ public class GizmoSessionImpl implements GizmoSession { public static final int SCAN_FREQUENCY = 1; private final PacketDisplayEntityGroup gizmoModel; + private final Snap snap; private TranslationMode translationMode = TranslationMode.TELEPORT; private GizmoSpace gizmoSpace = GizmoSpace.LOCAL; private GizmoSelectionMode selectionMode = GizmoSelectionMode.GROUP; @@ -60,6 +64,10 @@ public class GizmoSessionImpl implements GizmoSession { public GizmoSessionImpl(Player player, Location spawnLocation) { this.playerUUID = player.getUniqueId(); this.deuUser = DEUUser.getOrCreateUser(player); + this.snap = new Snap(this); + + //Clone Selector + this.selectors.add(new CloneSelector()); //Translate Axis this.selectors.add(AxisSelector.x()); @@ -130,6 +138,10 @@ public void setLastInteractionItemDrop(boolean lastInteractionItemDrop){ this.lastInteractionItemDrop = lastInteractionItemDrop; } + public Snap getSnap() { + return snap; + } + @Override public TranslationMode getTranslationMode() { return translationMode; diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoWand.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoWand.java index 63d2984a..a470593b 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoWand.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/GizmoWand.java @@ -30,6 +30,7 @@ public static void give(Player player){ player.sendMessage(DisplayAPI.pluginPrefix .append(Component.text("Gizmo wand added to your inventory!", NamedTextColor.GREEN))); + player.sendMessage(Component.text("| Controls in wand's item lore", NamedTextColor.GRAY, TextDecoration.ITALIC)); player.playSound(player, Sound.ENTITY_ITEM_PICKUP, 1, 1); } @@ -59,31 +60,57 @@ public static void toggleLink(Player player, GizmoSessionImpl gizmo, boolean eff } private static void buildItem() { - if (gizmoItemStack == null) { + //if (gizmoItemStack == null) { gizmoItemStack = new ItemStack(Material.STICK); gizmoItemStack.editMeta(meta -> { - meta.displayName(MiniMessage.miniMessage().deserialize("DEU Gizmo Wand")); + meta.displayName(MiniMessage.miniMessage() + .deserialize("DEU Gizmo Wand") + .decoration(TextDecoration.ITALIC, false)); meta.lore(List.of( - MiniMessage.miniMessage().deserialize("L-CLICK: Select Axis") + MiniMessage.miniMessage().deserialize("<#87e09c>L-CLICK: Select Axis") .decoration(TextDecoration.ITALIC, false), - MiniMessage.miniMessage().deserialize("<#b5deb4>L-CLICK (With selected Axis): Unlink Gizmo/Move pivot") + MiniMessage.miniMessage().deserialize("<#f07787>R-CLICK (With selected Axis): Deselect Axis") .decoration(TextDecoration.ITALIC, false), - MiniMessage.miniMessage().deserialize("R-CLICK : Switch Selection Mode (Group/Filter/Part)") + MiniMessage.miniMessage().deserialize("<#dce8e0>L-CLICK (With selected Axis): Unlink Gizmo/Move pivot") .decoration(TextDecoration.ITALIC, false), - MiniMessage.miniMessage().deserialize("R-CLICK (With selected Axis): Deselect Axis") + + + Component.empty(), + + Component.text("Sneak + Offhand (", TextColor.fromHexString("#a3bdd9")) + .append(Component.keybind("key.sneak")) + .append(Component.text(" + ")) + .append(Component.keybind("key.swapOffhand")) + .append(Component.text(")")) + .append(Component.text(": Toggle Snapping", NamedTextColor.GRAY)) + .decoration(TextDecoration.ITALIC, false), + MiniMessage.miniMessage().deserialize("<#966496>Scroll (With selected Axis): Change Snapping Value") + .decoration(TextDecoration.ITALIC, false), + + Component.empty(), + + MiniMessage.miniMessage().deserialize("R-CLICK : Switch Selection Mode (Group/Filter/Part)") .decoration(TextDecoration.ITALIC, false), Component.text("Offhand (", NamedTextColor.YELLOW) .append(Component.keybind("key.swapOffhand")) .append(Component.text(")")) .append(Component.text(": Toggle Space (Local/World)", NamedTextColor.GRAY)) .decoration(TextDecoration.ITALIC, false), - Component.text("Drop Item (", TextColor.color(150, 100, 150)) + Component.text("Drop Item (", TextColor.fromHexString("#c4f054")) .append(Component.keybind("key.drop")) .append(Component.text(")")) .append(Component.text(": Toggle Translation Mode (Translate/Teleport)", NamedTextColor.GRAY)) .decoration(TextDecoration.ITALIC, false), + + + Component.empty(), + + MiniMessage.miniMessage().deserialize("<#bcf7dd>L-CLICK (Pivot / White Cube<#bcf7dd>): Clone Selection") + .decoration(TextDecoration.ITALIC, false), + Component.empty(), - MiniMessage.miniMessage().deserialize("/deu gizmo help - for additional tools") + + MiniMessage.miniMessage().deserialize("/deu gizmo help - for additional tools") .decoration(TextDecoration.ITALIC, false), Component.text("Gizmo Model by: illystray", NamedTextColor.DARK_GRAY) .decoration(TextDecoration.ITALIC, false) @@ -91,6 +118,6 @@ private static void buildItem() { PersistentDataContainer pdc = meta.getPersistentDataContainer(); pdc.set(DisplayKeys.Gizmo.WAND, PersistentDataType.BOOLEAN, true); }); - } + // } } } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/Snap.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/Snap.java new file mode 100644 index 00000000..7188b15a --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/Snap.java @@ -0,0 +1,108 @@ +package net.donnypz.displayentityutils.utils.gizmo; + +import net.donnypz.displayentityutils.utils.gizmo.controls.drag.*; + +public class Snap { + + private float translationSnap = 0.5f; //in blocks + private float rotationSnap = 15.0f; //in deg/rad + private float scaleSnap = 0.25f; //in blocks + + public final float TRANSLATION_STEP = 0.05f; + public final float ROTATION_STEP = 1f; + public final float SCALE_STEP = 0.05f; + + private final float MAX_TRANSLATION_SNAP = 5.0f; + private final float MAX_ROTATION_SNAP = 90.0f; + private final float MAX_SCALE_SNAP = 1.0f; + + + private boolean isEnabled; + + private final GizmoSessionImpl gizmo; + + public Snap(GizmoSessionImpl gizmo) { + this.gizmo = gizmo; + } + + public boolean isEnabled() { + return isEnabled; + } + + public void setEnabled(boolean enabled) { + isEnabled = enabled; + } + + /** + * Set the snap vale of the gizmo's translation, rotation, or scale, based on the gizmo's current selection + * @param snapValue + */ + public void setSnapValue(float snapValue){ + Drag drag = gizmo.getDragControl(); + if (drag == null) return; + + switch (drag.getControlType()){ + case TRANSLATION -> { + this.translationSnap = Math.clamp(snapValue, TRANSLATION_STEP, MAX_TRANSLATION_SNAP); + } + case ROTATION -> { + this.rotationSnap = Math.clamp(snapValue, ROTATION_STEP, MAX_ROTATION_SNAP); + } + case SCALE -> { + this.scaleSnap = Math.clamp(snapValue, SCALE_STEP, MAX_SCALE_SNAP); + } + } + } + + public float getSnapValue(){ + Drag drag = gizmo.getDragControl(); + if (drag == null) return 0; + + switch (drag.getControlType()){ + case TRANSLATION -> { + return translationSnap; + } + case ROTATION -> { + return rotationSnap; + } + case SCALE -> { + return scaleSnap; + } + default -> { + return 0; + } + } + } + + private float getSnapStep(){ + Drag drag = gizmo.getDragControl(); + if (drag == null) return 0; + + switch (drag.getControlType()){ + case TRANSLATION -> { + return TRANSLATION_STEP; + } + case ROTATION -> { + return ROTATION_STEP; + } + case SCALE -> { + return SCALE_STEP; + } + default -> { + return 0; + } + } + } + + public void update(boolean isScrollDown){ + float snapValue; + if (isScrollDown){ + snapValue = getSnapValue() - getSnapStep(); + } + else{ + snapValue = getSnapValue() + getSnapStep(); + } + + setSnapValue(snapValue); + } +} diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/RotationDrag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/RotationDrag.java index f51a98d4..1dc69643 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/RotationDrag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/RotationDrag.java @@ -5,10 +5,12 @@ import net.donnypz.displayentityutils.utils.gizmo.GizmoSelectionMode; import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.Snap; import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.util.GizmoMathUtil; import org.bukkit.Location; +import org.bukkit.Sound; import org.bukkit.entity.Player; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -31,6 +33,9 @@ public class RotationDrag extends Drag { //prev dir from gizmo to player's looking dir private final Vector3f lastDirection = new Vector3f(); + private float totalAngleChange; + private float appliedSnapAngle; + public RotationDrag(Player player, GizmoSessionImpl gizmo, GizmoAxis axis) { super(axis, ControlType.ROTATION); this.gizmo = gizmo; @@ -68,7 +73,9 @@ public RotationDrag(Player player, GizmoSessionImpl gizmo, GizmoAxis axis) { @Override public void updatePosition(Player player) { + if (player.isSneaking()) return; if (!gizmo.isLinked()) return; + Vector3f hit = playerRayAndPlaneCollision(player); if (hit == null) { return; @@ -85,11 +92,32 @@ public void updatePosition(Player player) { lastDirection.set(currentDirection); - if (Math.abs(angle) < 1e-6f) { + if (Math.abs(angle) < 1e-6f) return; + + totalAngleChange += angle; + + Snap snap = gizmo.getSnap(); + float snapValueDeg = snap.getSnapValue(); + + //No snapping + if (!snap.isEnabled() || snapValueDeg <= 0){ + applyToPlayerSelection(angle); return; } - applyToPlayerSelection(angle); + float snapValueRad = (float) Math.toRadians(snapValueDeg); + + float snappedAngle = GizmoMathUtil.getSnappedValue(totalAngleChange, snapValueRad); + + float delta = snappedAngle - appliedSnapAngle; + + if (Math.abs(delta) < 1e-6f) return; + + appliedSnapAngle = snappedAngle; + + applyToPlayerSelection(delta); + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1); + } private void applyToPlayerSelection(float angle) { diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/ScaleDrag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/ScaleDrag.java index 6ff43b55..b223aa53 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/ScaleDrag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/ScaleDrag.java @@ -6,14 +6,17 @@ import net.donnypz.displayentityutils.utils.gizmo.GizmoSelectionMode; import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; +import net.donnypz.displayentityutils.utils.gizmo.Snap; import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; +import net.donnypz.displayentityutils.utils.gizmo.util.GizmoMathUtil; import net.donnypz.displayentityutils.utils.gizmo.util.GizmoTitleUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.util.Transformation; import org.joml.Quaternionf; @@ -31,6 +34,9 @@ public class ScaleDrag extends Drag { private final Vector3f lastHitPoint; + private float totalScaleChange; + private float appliedSnapScale; + public ScaleDrag(Player player, GizmoSessionImpl gizmo, GizmoAxis axis) { super(axis, ControlType.SCALE); @@ -42,21 +48,45 @@ public ScaleDrag(Player player, GizmoSessionImpl gizmo, GizmoAxis axis) { @Override public void updatePosition(Player player) { - if (lastHitPoint == null) - return; + if (player.isSneaking()) return; + if (lastHitPoint == null) return; Vector3f hit = playerRayAndPlaneCollision(player); - Vector3f delta = hit.sub(lastHitPoint, new Vector3f()); lastHitPoint.set(hit); float movementAmount = delta.dot(currentAxis); - if (Math.abs(movementAmount) < 1e-6f) + if (Math.abs(movementAmount) < 1e-6f) return; + + float scaleDelta = movementAmount * 2f; + Snap snap = gizmo.getSnap(); + float scaleSnapValue = snap.getSnapValue(); + + //No snapping + if (!snap.isEnabled() || scaleSnapValue <= 0){ + applyScale(scaleDelta); return; + } + + totalScaleChange += scaleDelta; + + float snappedScale = GizmoMathUtil.getSnappedValue(totalScaleChange, scaleSnapValue); + + float deltaScale = snappedScale - appliedSnapScale; + + if (Math.abs(deltaScale) < 1e-6f) return; + + float lastAppliedSnapScale = appliedSnapScale; + appliedSnapScale = snappedScale; + + if (!applyScale(deltaScale)){ + this.appliedSnapScale = lastAppliedSnapScale; + this.totalScaleChange -= scaleDelta; + } + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1); - applyScale(movementAmount); } private void computeCurrentAxisDirection() { @@ -120,48 +150,50 @@ private Vector3f createPlaneNormal(Vector3f lookDir) { .normalize(); } - private void applyScale(float movementAmount) { - - float scaleDelta = movementAmount * 2f; - + private boolean applyScale(float scaleDelta) { ActivePartSelection selection = DEUUser.getOrCreateUser(gizmo.getPlayerUUID()) .getSelectedPartSelection(); if (gizmo.getSelectionMode() == GizmoSelectionMode.PART || selection instanceof SinglePartSelection) { ActivePart part = selection.getSelectedPart(); - if (part == null) return; + if (part == null) return false; if (part.isDisplay()) { scaleDisplay(part, scaleDelta); - } else if (part.getType() == SpawnedDisplayEntityPart.PartType.INTERACTION) { + } + else if (part.getType() == SpawnedDisplayEntityPart.PartType.INTERACTION) { if (axis == GizmoAxis.Y) { part.setInteractionHeight(part.getInteractionHeight() + scaleDelta); - } else { + } + else { part.setInteractionWidth(part.getInteractionWidth() + scaleDelta); } - } else if (part.getType() == SpawnedDisplayEntityPart.PartType.MANNEQUIN) { + } + else if (part.getType() == SpawnedDisplayEntityPart.PartType.MANNEQUIN) { part.setMannequinScale(part.getMannequinScale() + scaleDelta); } - } else if (selection instanceof MultiPartSelection multi) { + } + else if (selection instanceof MultiPartSelection multi) { ActiveGroup group = multi.getGroup(); float scale = group.getScaleMultiplier() + scaleDelta; if (scale < 0.1f) { - return; + return false; } if (gizmo.getSelectionMode() == GizmoSelectionMode.FILTER && group.getSize() != multi.getSize()) { GizmoTitleUtil.show(Bukkit.getPlayer(gizmo.getPlayerUUID()), Component.text("Scale Failed", NamedTextColor.RED), MiniMessage.miniMessage().deserialize("⚠ Filtered selection cannot be scaled separately ⚠")); - - } else { + } + else { GroupTeleportCompletableFuture future = group.scale(scale, GizmoSessionImpl.SCAN_FREQUENCY + 1, true); //block thread until non-display teleports complete if (future != null) future.block(); } } + return true; } private void scaleDisplay(ActivePart part, float scaleDelta) { diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationAxisDrag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationAxisDrag.java index 3cde0038..060ea191 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationAxisDrag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationAxisDrag.java @@ -25,7 +25,6 @@ private void computeCurrentAxisDirection() { rotation.transform(super.currentAxesDir[0]).normalize(); } - } @Override @@ -55,12 +54,25 @@ protected Vector3f playerRayAndPlaneCollision(Player player) { : new Vector3f(super.lastHitPoint); //behind player camera - distance = Math.min(distance, MAX_LOOK_DISTANCE*gizmo.getScale()); + distance = Math.min(distance, MAX_LOOK_DISTANCE * gizmo.getScale()); return new Vector3f(playerEyePos) .fma(distance, ray); } + @Override + protected Vector3f getMovementAmounts(Player player, Vector3f delta) { + return new Vector3f(delta.dot(currentAxesDir[0]), 0, 0); + } + + @Override + protected Vector3f[] getMovementVectors(Vector3f movementAmounts) { + float movementAmount = movementAmounts.x; + Vector3f movement = super.currentAxesDir[0].mul(movementAmount, new Vector3f()); + Vector3f movementTranslate = super.originalAxes[0].mul(movementAmount, new Vector3f()); + return new Vector3f[]{movement, movementTranslate}; + } + //plane's normal, faces the player, but still perpendicular to axis. private Vector3f createPlaneNormal(Vector3f playerLookDir) { float dotProduct = playerLookDir.dot(super.currentAxesDir[0]); @@ -69,14 +81,4 @@ private Vector3f createPlaneNormal(Vector3f playerLookDir) { .sub(new Vector3f(super.currentAxesDir[0]).mul(dotProduct)) .normalize(); } - - @Override - public Vector3f[] getMovementVectors(Player player, Vector3f delta){ - Vector3f currentAxis = super.currentAxesDir[0]; - float movementAmount = delta.dot(currentAxis); - - Vector3f movement = new Vector3f(currentAxis).mul(movementAmount); - Vector3f movementTranslate = new Vector3f(super.originalAxes[0]).mul(movementAmount); - return new Vector3f[]{movement, movementTranslate}; - } } \ No newline at end of file diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationDrag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationDrag.java index 4eb3e5cf..e74265aa 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationDrag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationDrag.java @@ -3,10 +3,7 @@ import net.donnypz.displayentityutils.managers.DEUUser; import net.donnypz.displayentityutils.utils.DisplayEntities.*; import net.donnypz.displayentityutils.utils.DisplayEntities.concurrent.GroupTeleportCompletableFuture; -import net.donnypz.displayentityutils.utils.gizmo.GizmoSelectionMode; -import net.donnypz.displayentityutils.utils.gizmo.GizmoSessionImpl; -import net.donnypz.displayentityutils.utils.gizmo.GizmoSpace; -import net.donnypz.displayentityutils.utils.gizmo.TranslationMode; +import net.donnypz.displayentityutils.utils.gizmo.*; import net.donnypz.displayentityutils.utils.gizmo.controls.ControlType; import net.donnypz.displayentityutils.utils.gizmo.controls.GizmoAxis; import net.donnypz.displayentityutils.utils.gizmo.util.GizmoTitleUtil; @@ -15,6 +12,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import org.joml.Quaternionf; @@ -31,6 +29,9 @@ public abstract class TranslationDrag extends Drag { protected final GizmoSessionImpl gizmo; protected final Quaternionf rotation = new Quaternionf(); protected final Vector3f lastHitPoint; + protected final Vector3f initialHitPoint; + private final Vector3f totalMovementAmounts = new Vector3f(); + private final Vector3f appliedSnappedAmounts = new Vector3f(); protected final Vector3f[] originalAxes; protected final Vector3f[] currentAxesDir; @@ -41,7 +42,8 @@ public TranslationDrag(Player player, GizmoSessionImpl gizmo, GizmoAxis axis) { this.currentAxesDir = IntStream.range(0, 2) .mapToObj(i -> new Vector3f()) .toArray(Vector3f[]::new); - this.lastHitPoint = playerRayAndPlaneCollision(player); + this.initialHitPoint = playerRayAndPlaneCollision(player); + this.lastHitPoint = new Vector3f(initialHitPoint); } @@ -58,21 +60,73 @@ public String getTag() { return axis.getTag(); } - public abstract Vector3f[] getMovementVectors(Player player, Vector3f delta); + protected abstract Vector3f getMovementAmounts(Player player, Vector3f delta); + + protected abstract Vector3f[] getMovementVectors(Vector3f movementAmounts); private void updateTranslationMovement(Player player) { + if (player.isSneaking()) return; + Vector3f hit = playerRayAndPlaneCollision(player); - Vector3f delta = hit.sub(this.lastHitPoint, new Vector3f()); + Vector3f deltaFromLast = hit.sub(this.lastHitPoint, new Vector3f()); - this.lastHitPoint.set(hit); + Vector3f movementAmounts = getMovementAmounts(player, deltaFromLast); + + movementAmounts = applySnapping(player, movementAmounts); + + Vector3f[] movementVectors = getMovementVectors(movementAmounts); - Vector3f[] movementVectors = getMovementVectors(player, delta); Vector3f movement = movementVectors[0]; Vector3f movementTranslate = movementVectors[1]; - if (movement.lengthSquared() > 1e-6f) { - this.applyMovement(movement, movementTranslate); + if (movement.lengthSquared() <= 1e-6f) { + this.lastHitPoint.set(hit); + return; + } + + this.lastHitPoint.set(hit); + this.applyMovement(movement, movementTranslate); + } + + private Vector3f applySnapping(Player player, Vector3f movementAmounts) { + Snap snap = gizmo.getSnap(); + + if (!snap.isEnabled()) { + return movementAmounts; + } + + totalMovementAmounts.add(movementAmounts); + + float snapValue = snap.getSnapValue(); + + Vector3f snappedAmounts = new Vector3f( + snapAmount(totalMovementAmounts.x, snapValue), + snapAmount(totalMovementAmounts.y, snapValue), + snapAmount(totalMovementAmounts.z, snapValue) + ); + + Vector3f snappedDelta = snappedAmounts + .sub(appliedSnappedAmounts, new Vector3f()); + + appliedSnappedAmounts.set(snappedAmounts); + + if (snappedDelta.lengthSquared() > 1e-6f) { + if (player != null) { + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_HAT, 1, 1); + } } + + return snappedDelta; + } + + private float snapAmount(float amount, float snapValue) { + float sign = Math.signum(amount); + + return (float) ( + Math.floor(Math.abs(amount) / snapValue) + * snapValue + * sign + ); } protected void applyMovement(Vector3f delta, Vector3f translateDelta) { @@ -82,8 +136,7 @@ protected void applyMovement(Vector3f delta, Vector3f translateDelta) { protected void applyToGizmo(Vector3f delta) { Vector bukkitDelta = Vector.fromJOML(delta); - Location l = gizmo.getGizmoModel() - .getLocation(); + Location l = gizmo.getGizmoModel().getLocation(); l.add(bukkitDelta); gizmo.teleport(l); } @@ -98,9 +151,13 @@ protected void applyToPlayerSelection(Vector3f delta, Vector3f translateDelta) { if (gizmo.getTranslationMode() == TranslationMode.TRANSLATE) { this.translate(delta, translateDelta, sel); } else { - Vector bukkitDelta = Vector.fromJOML(delta); - Location tpLoc = sel.getLocation(); - tpLoc.add(bukkitDelta); + Location tpLoc = sel.getLocation().clone(); + tpLoc.add(Vector.fromJOML(delta)); + + Snap snap = gizmo.getSnap(); + + if (snap.isEnabled()) adjustTeleportForSnap(tpLoc, snap); + this.teleport(tpLoc, sel); } @@ -109,6 +166,34 @@ protected void applyToPlayerSelection(Vector3f delta, Vector3f translateDelta) { } } + private void adjustTeleportForSnap(Location tpLoc, Snap snap) { + float snapValue = snap.getSnapValue(); + + switch (axis) { + case X -> { + tpLoc.setX(Math.round(tpLoc.getX() / snapValue) * snapValue); + } + case Y -> { + tpLoc.setY(Math.round(tpLoc.getY() / snapValue) * snapValue); + } + case Z -> { + tpLoc.setZ(Math.round(tpLoc.getZ() / snapValue) * snapValue); + } + case XY -> { + tpLoc.setX(Math.round(tpLoc.getX() / snapValue) * snapValue); + tpLoc.setY(Math.round(tpLoc.getY() / snapValue) * snapValue); + } + case YZ -> { + tpLoc.setY(Math.round(tpLoc.getY() / snapValue) * snapValue); + tpLoc.setZ(Math.round(tpLoc.getZ() / snapValue) * snapValue); + } + case ZX -> { + tpLoc.setZ(Math.round(tpLoc.getZ() / snapValue) * snapValue); + tpLoc.setX(Math.round(tpLoc.getX() / snapValue) * snapValue); + } + } + } + private boolean canTeleport() { ActivePartSelection sel = DEUUser .getOrCreateUser(gizmo.getPlayerUUID()) @@ -121,15 +206,14 @@ private boolean canTeleport() { Player player = Bukkit.getPlayer(gizmo.getPlayerUUID()); if (sel instanceof MultiPartSelection mp && gizmo.isLinked()) { ActiveGroup group = mp.getGroup(); - if (group != null){ - if (group.isRiding()){ + if (group != null) { + if (group.isRiding()) { GizmoTitleUtil.show(player, Component.text("Teleport Failed", NamedTextColor.RED), MiniMessage.miniMessage().deserialize("⚠ Group cannot be riding an entity ⚠")); return false; - } - else if (group instanceof PacketDisplayEntityGroup pdeg - && pdeg.isPlaced()){ + } else if (group instanceof PacketDisplayEntityGroup pdeg + && pdeg.isPlaced()) { GizmoTitleUtil.show(player, Component.text("Teleport Failed", NamedTextColor.RED), MiniMessage.miniMessage().deserialize("⚠ Cannot teleport group placed by player w/ item ⚠")); @@ -140,13 +224,13 @@ else if (group instanceof PacketDisplayEntityGroup pdeg return true; } - private void undoTranslateDeltaRotation(Vector3f translateDelta, ActivePartSelection sel){ + private void undoTranslateDeltaRotation(Vector3f translateDelta, ActivePartSelection sel) { Location loc = sel.getLocation(); float pitch = loc.getPitch(); float yaw = loc.getYaw(); Quaternionf undoRotation = new Quaternionf() - .rotateY((float)Math.toRadians(-yaw)) - .rotateX((float)Math.toRadians(pitch)) + .rotateY((float) Math.toRadians(-yaw)) + .rotateX((float) Math.toRadians(pitch)) .invert(); //normalize and conjugates (negate imaginary, keep scalar same) undoRotation.transform(translateDelta); @@ -154,21 +238,20 @@ private void undoTranslateDeltaRotation(Vector3f translateDelta, ActivePartSelec private void translate(Vector3f delta, Vector3f translateDelta, ActivePartSelection sel) { Vector bukkitTranslateDelta; - if (gizmo.getGizmoSpace() == GizmoSpace.WORLD){ + if (gizmo.getGizmoSpace() == GizmoSpace.WORLD) { undoTranslateDeltaRotation(translateDelta, sel); } bukkitTranslateDelta = Vector.fromJOML(translateDelta); Vector bukkitDelta = Vector.fromJOML(delta); - if (gizmo.getSelectionMode() == GizmoSelectionMode.PART || sel instanceof SinglePartSelection){ + if (gizmo.getSelectionMode() == GizmoSelectionMode.PART || sel instanceof SinglePartSelection) { ActivePart part = sel.getSelectedPart(); if (part == null) return; part.translate(part.isDisplay() ? bukkitTranslateDelta : bukkitDelta, GizmoSessionImpl.SCAN_FREQUENCY, 0); - } - else if (sel instanceof MultiPartSelection mps){ + } else if (sel instanceof MultiPartSelection mps) { for (ActivePart p : getParts(mps)) { p.translate(p.isDisplay() ? bukkitTranslateDelta @@ -179,7 +262,7 @@ else if (sel instanceof MultiPartSelection mps){ } private void teleport(Location tpLoc, ActivePartSelection sel) { - if (sel instanceof SinglePartSelection){ + if (sel instanceof SinglePartSelection) { ActivePart part = sel.getSelectedPart(); if (part == null) return; part.setTeleportDuration(GizmoSessionImpl.SCAN_FREQUENCY); @@ -191,8 +274,7 @@ private void teleport(Location tpLoc, ActivePartSelection sel) { } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } - } - else if (sel instanceof MultiPartSelection mps){ + } else if (sel instanceof MultiPartSelection mps) { ActiveGroup group = mps.getGroup(); if (group != null) { group.setTeleportDuration(GizmoSessionImpl.SCAN_FREQUENCY); @@ -206,7 +288,7 @@ else if (sel instanceof MultiPartSelection mps){ } } - private List getParts(MultiPartSelection mps){ + private List getParts(MultiPartSelection mps) { return gizmo.getSelectionMode() == GizmoSelectionMode.GROUP ? mps.getGroup().getParts() : mps.getParts(); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationPlaneDrag.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationPlaneDrag.java index 67342488..5f617214 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationPlaneDrag.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/controls/drag/TranslationPlaneDrag.java @@ -57,30 +57,50 @@ protected Vector3f playerRayAndPlaneCollision(Player player) { : new Vector3f(super.lastHitPoint); //behind player camera - distance = Math.min(distance, MAX_LOOK_DISTANCE* gizmo.getScale()); + distance = Math.min(distance, MAX_LOOK_DISTANCE * gizmo.getScale()); return new Vector3f(playerEyePos) .fma(distance, ray); } - private Vector3f createPlaneNormal(){ - return new Vector3f(super.currentAxesDir[0]) - .cross(super.currentAxesDir[1]) - .normalize(); + @Override + protected Vector3f getMovementAmounts(Player player, Vector3f delta) { + return new Vector3f( + delta.dot(currentAxesDir[0]), //axis1 + delta.dot(currentAxesDir[1]), //axis2 + 0 + ); } @Override - public Vector3f[] getMovementVectors(Player player, Vector3f delta){ - float movementAmountAxis1 = delta.dot(super.currentAxesDir[0]); - float movementAmountAxis2 = delta.dot(super.currentAxesDir[1]); - - Vector3f movement = new Vector3f(super.currentAxesDir[0]) - .mul(movementAmountAxis1) - .add(new Vector3f(super.currentAxesDir[1]).mul(movementAmountAxis2)); + protected Vector3f[] getMovementVectors(Vector3f movementAmounts) { + float movementAmountAxis1 = movementAmounts.x; + float movementAmountAxis2 = movementAmounts.y; + + Vector3f movement = + new Vector3f( + super.currentAxesDir[0]) + .mul(movementAmountAxis1) + .add( + new Vector3f(super.currentAxesDir[1]) + .mul(movementAmountAxis2) + ); Vector3f movementTranslate = - new Vector3f(super.originalAxes[0]).mul(movementAmountAxis1) - .add(new Vector3f(super.originalAxes[1]).mul(movementAmountAxis2)); - return new Vector3f[]{movement, new Vector3f(movementTranslate)}; + new Vector3f( + super.originalAxes[0]) + .mul(movementAmountAxis1) + .add( + new Vector3f(super.originalAxes[1]) + .mul(movementAmountAxis2) + ); + + return new Vector3f[]{movement, movementTranslate}; + } + + private Vector3f createPlaneNormal() { + return new Vector3f(super.currentAxesDir[0]) + .cross(super.currentAxesDir[1]) + .normalize(); } } \ No newline at end of file diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoMathUtil.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoMathUtil.java index 95153af7..9ea6b945 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoMathUtil.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoMathUtil.java @@ -23,4 +23,8 @@ public static void scale(Vector3f vector3f, float oldScale, float newScale) { vector3f.y = (vector3f.y / oldScale) * newScale; vector3f.z = (vector3f.z / oldScale) * newScale; } + + public static float getSnappedValue(float totalChange, float snapValue){ + return Math.round(totalChange / snapValue) * snapValue; + } } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoTitleUtil.java b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoTitleUtil.java index 8e2b4d1e..bf414806 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoTitleUtil.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/utils/gizmo/util/GizmoTitleUtil.java @@ -50,4 +50,34 @@ public static void showCannotChangeSelectionMode(Player player){ MiniMessage.miniMessage().deserialize("Cannot change selection mode for single entity")); } + public static void showSnapStatus(Player player, boolean status){ + showSubtitle(player, MiniMessage.miniMessage().deserialize( + "\uD83E\uDDF2 Snapping: " + + + ( + status + ? "ENABLED" + : "DISABLED" + ) + + + " \uD83E\uDDF2" + )); + } + + public static void showSnapValue(Player player, Snap snap, ControlType controlType) { + show( + player, + Component.text(controlType.name(), NamedTextColor.LIGHT_PURPLE), + MiniMessage.miniMessage().deserialize( + "\uD83E\uDDF2 Snap Value: " + snap.getSnapValue() + + + ( + controlType == ControlType.ROTATION + ? " degrees " + : " blocks " + ) + + + "\uD83E\uDDF2" + )); + } } From abec5c2a4509b3a8a53add4a2a67d0cc48fa3a80 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:41:40 -0500 Subject: [PATCH 23/24] Bump version to 3.8.0 --- api/pom.xml | 2 +- plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index c38b3290..0010a501 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.7.2 + 3.8.0 api diff --git a/plugin/pom.xml b/plugin/pom.xml index 10433487..83853a76 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.7.2 + 3.8.0 plugin diff --git a/pom.xml b/pom.xml index c0710f05..b00eeb87 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.7.2 + 3.8.0 pom DisplayEntityUtils From 824b2f45133f522cf1ff76865b98b69c7ea241f0 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Thu, 1 Oct 2026 03:01:57 -0500 Subject: [PATCH 24/24] add release notes --- docs/release-notes/3.8.0.md | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/release-notes/3.8.0.md diff --git a/docs/release-notes/3.8.0.md b/docs/release-notes/3.8.0.md new file mode 100644 index 00000000..ef47941b --- /dev/null +++ b/docs/release-notes/3.8.0.md @@ -0,0 +1,47 @@ +**Release Highlights** +- Gizmo Changes 📐 +- New Commands 💻 +- New and Breaking API Changes 🔨 +- Bug Fixes 🐛 + +## Gizmo Changes +- Snapped movements can be toggled by using `SNEAK + OFFHAND` +- The snap value can be changed by `SCROLLING (With selected axis)` +- A selected group / entity can be cloned by `LEFT CLICKING` the pivot / white cube at the center of the Gizmo +- Translating in teleport mode snaps to world grid when snapping is enabled + +## Command Changes + +### New Commands +- Added `/deu display roundtranslation` + - Remove the decimal portion of your selected display's translation. + - (Ex: X translation of `12.25` would become `12.0`). + - Permission: `deu.display.transform` +- Added `/deu gizmo snapvalue` + - Set the snapping value for a selected Gizmo control + - Permission: `deu.gizmo.use` +- Added `/deu gizmo toggglesnap` + - Toggle the snapping of gizmo control movements + - Permission: `deu.gizmo.use` + +## API Changes +> [!NOTE] +>**New Version:** `3.8.0` + +### Additions/Changes +- Added `DisplayStorage#isEnabled` to see if a storage location is enabled +- Add paginated `#getGroupTags` and `#getAnimationTags` to `DisplayStorage` +- Added `LoadMethod#getDisplayStorage` + +### Breaking Changes +- Removed `DisplayAnimationManager#getSavedDisplayAnimations` in favor of `DisplayStorage#getAnimationTags` +- Removed `DisplayGroupManager#getSavedDisplayEntityGroups` in favor of `DisplayStorage#getGroupTags` + + +## Bug Fixes / QOL +- Fixed an issue where using a `MongoDB` database without passing a `connectionString` in the config +generated an incorrect connection string +- Added minor optimization to listing group and animation tags from a database +- All plugin permissions are now correctly registered +- Player movement is now stopped after selecting a Gizmo control +- Fixed `/deu parts clone` requiring a tag to be added to a cloned part, rather than being optional