From 624adbfc4f88492378586c2c6c001e72a1c850c1 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Mon, 16 Jun 2025 20:52:29 +0200 Subject: [PATCH 01/22] Add basic implementation for world folder creation --- .../settings/NavigatorInventoryType.java | 4 +- .../api/storage/FolderStorage.java | 51 +++-- .../buildsystem/api/world/BuildWorld.java | 42 +++- .../api/world/display/Displayable.java | 36 ++-- .../buildsystem/api/world/display/Folder.java | 47 ++++- .../de/eintosti/buildsystem/Messages.java | 16 ++ .../buildsystem/command/WorldsCommand.java | 5 + .../subcommand/worlds/FolderSubCommand.java | 108 ++++++++++ .../inventory/FilteredWorldsInventory.java | 188 ++++++++++++++---- .../inventory/PublicWorldsInventory.java | 12 +- .../storage/FolderStorageImpl.java | 75 ++++--- .../storage/yaml/YamlFolderStorage.java | 8 +- .../storage/yaml/YamlWorldStorage.java | 5 + .../tabcomplete/WorldsTabComplete.java | 1 + .../buildsystem/world/BuildWorldImpl.java | 46 ++--- .../buildsystem/world/WorldServiceImpl.java | 50 ----- .../buildsystem/world/display/FolderImpl.java | 63 +++--- 17 files changed, 522 insertions(+), 235 deletions(-) create mode 100644 buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorInventoryType.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorInventoryType.java index fbbdc7ec..629cf510 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorInventoryType.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorInventoryType.java @@ -19,8 +19,8 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.builder.Builder; -import de.eintosti.buildsystem.api.world.data.WorldData; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; +import de.eintosti.buildsystem.api.world.data.WorldData; import org.bukkit.entity.Player; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.Nullable; @@ -45,7 +45,7 @@ public enum NavigatorInventoryType { ARCHIVE("§6World Archive"), /** - * The navigator inventory which contains all private {@link }s that can only be modified by the world's creator and all players who have been added as a {@link Builder}. + * The navigator inventory which contains all private {@link BuildWorld}s that can only be modified by the world's creator and all players who have been added as a {@link Builder}. * * @see WorldData#privateWorld() */ diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java index 87bfd72b..1a417c45 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java @@ -20,6 +20,7 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; import java.util.Collection; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; public interface FolderStorage extends Storage { @@ -33,11 +34,30 @@ public interface FolderStorage extends Storage { Collection getFolders(); /** - * Adds a {@link Folder} and updates the world-to-folder mapping for all worlds contained in the folder. + * Gets a {@link Folder} by its name (case-insensitive). * - * @param folder the folder to add + * @param folderName The name of the folder to retrieve + * @return The folder if it exists, or {@code null} if it does not */ - void addFolder(Folder folder); + @Nullable + Folder getFolder(String folderName); + + /** + * Gets a {@link Folder} by its name. + * + * @param folderName The name of the folder to retrieve + * @param caseSensitive Whether to check the name case-sensitive or not + * @return The folder if it exists, or {@code null} if it does not + */ + @Nullable + Folder getFolder(String folderName, boolean caseSensitive); + + /** + * Creates a new {@link Folder} with the given name and adds it to the folder storage. + * + * @param folderName The name folder to create + */ + Folder createFolder(String folderName); /** * Removes a folder and all of its worlds from the world-to-folder mapping. @@ -64,26 +84,19 @@ public interface FolderStorage extends Storage { boolean folderExists(String folderName, boolean caseSensitive); /** - * Adds a {@link BuildWorld} to the specified {@link Folder} and updates the folder and world-to-folder mappings. - * - * @param worldName the name of the world to add - * @param folderName the name of the folder to add the world to - */ - void addWorldToFolder(String worldName, String folderName); - - /** - * Removes a world from the specified {@link Folder} and updates the world-to-folder mapping. + * Checks whether the given {@link BuildWorld} is assigned to any {@link Folder}. * - * @param worldName the name of the world to remove - * @param folderName the name of the folder to remove the world from + * @param buildWorld The world to check + * @return {@code true} if the world is in any folder; {@code false} otherwise */ - void removeWorldFromFolder(String worldName, String folderName); + boolean isAssignedToAnyFolder(BuildWorld buildWorld); /** - * Checks whether the given {@link BuildWorld} is assigned to any {@link Folder}. + * Gets the {@link Folder} that contains the specified {@link BuildWorld}. * - * @param buildWorld the build world to check - * @return {@code true} if the world is in any folder; {@code false} otherwise + * @param buildWorld The world to check + * @return The folder containing the world, or {@code null} if the world is not in any folder */ - boolean isWorldInAnyFolder(BuildWorld buildWorld); + @Nullable + Folder getAssignedFolder(BuildWorld buildWorld); } diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java index 67876019..1ea69cc4 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java @@ -20,13 +20,14 @@ import com.cryptomorin.xseries.profiles.objects.Profileable; import de.eintosti.buildsystem.api.world.builder.Builders; import de.eintosti.buildsystem.api.world.creation.generator.CustomGenerator; -import de.eintosti.buildsystem.api.world.data.WorldData; import de.eintosti.buildsystem.api.world.data.BuildWorldType; +import de.eintosti.buildsystem.api.world.data.WorldData; import de.eintosti.buildsystem.api.world.display.Displayable; import de.eintosti.buildsystem.api.world.util.WorldLoader; import de.eintosti.buildsystem.api.world.util.WorldPermissions; import de.eintosti.buildsystem.api.world.util.WorldTeleporter; import de.eintosti.buildsystem.api.world.util.WorldUnloader; +import java.util.UUID; import org.bukkit.Difficulty; import org.bukkit.World; import org.jetbrains.annotations.Nullable; @@ -42,7 +43,16 @@ public interface BuildWorld extends Displayable { World getWorld(); /** - * Set the name of the world. + * Gets the unique identifier of the world. + *

+ * Not equivalent to {@link World#getUID()}. + * + * @return The uuid of the world + */ + UUID getUniqueId(); + + /** + * Sets the name of the world. * * @param name The name to set to */ @@ -51,7 +61,7 @@ public interface BuildWorld extends Displayable { Profileable asProfilable(); /** - * Get world's type. + * Gets the world's type. * * @return The {@link BuildWorldType} of the world */ @@ -65,14 +75,14 @@ public interface BuildWorld extends Displayable { WorldData getData(); /** - * Get the creation date of the world. + * Gets the creation date of the world. * * @return The number of milliseconds that have passed since {@code January 1, 1970 UTC}, until the world was created. */ long getCreationDate(); /** - * Get the custom chunk generator used to generate the world. + * Gets the custom chunk generator used to generate the world. * * @return The custom chunk generator used to generate the world. */ @@ -85,48 +95,58 @@ public interface BuildWorld extends Displayable { void cycleDifficulty(); /** - * Get a list of all builders who can modify the world. + * Gets a list of all builders who can modify the world. * * @return the list of all builders */ Builders getBuilders(); /** - * Get the time in the {@link World} linked to the build world. + * Gets the time in the {@link World} linked to the build world. * * @return The world time */ String getWorldTime(); /** - * Get whether the world is currently loaded, allowing a player to enter it. + * Gets whether the world is currently loaded, allowing a player to enter it. * * @return {@code true} if the world is loaded, otherwise {@code false} */ boolean isLoaded(); /** - * Set whether the world is currently loaded, allowing a player to enter it. + * Sets whether the world is currently loaded, allowing a player to enter it. * * @param loaded {@code true} if the world is loaded, otherwise {@code false} */ void setLoaded(boolean loaded); /** - * Get the {@link WorldLoader} used to load the world. + * Gets the {@link WorldLoader} used to load the world. * * @return The {@link WorldLoader} used to load the world */ WorldLoader getLoader(); /** - * Get the {@link WorldUnloader} used to unload the world. + * Gets the {@link WorldUnloader} used to unload the world. * * @return The {@link WorldUnloader} used to unload the world */ WorldUnloader getUnloader(); + /** + * Gets the {@link WorldTeleporter} used to teleport players to the world. + * + * @return The {@link WorldTeleporter} used to teleport players to the world + */ WorldTeleporter getTeleporter(); + /** + * Gets the {@link WorldPermissions} associated with the world. + * + * @return The {@link WorldPermissions} for the world + */ WorldPermissions getPermissions(); } \ No newline at end of file diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java index 5265801a..51fbfb54 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java @@ -21,10 +21,10 @@ import java.util.List; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; /** - * Represents an item that can be displayed in the navigator inventory. This interface follows functional programming principles by defining pure functions for data transformation - * and display. + * Represents an object that can be displayed in an inventory. */ public interface Displayable { @@ -35,34 +35,46 @@ public interface Displayable { */ String getName(); + /** + * Gets the name used to display this item in an inventory. + * + * @param player The player viewing the item + * @return The display name + */ + String getDisplayName(Player player); + /** * Gets the material to display this item with. * * @return The material */ - XMaterial getMaterial(); + XMaterial getIcon(); /** - * Gets the display name shown in the inventory. This should be a pure function that transforms the name based on the player's context. - * - * @param player The player viewing the inventory - * @return The display name + * Sets the icon for this displayable item. */ - String getDisplayName(Player player); + void setIcon(XMaterial material); /** - * Gets the lore shown in the inventory. This should be a pure function that generates lore based on the player's context. + * Gets the lore of this displayable item. * - * @param player The player viewing the inventory + * @param player The player viewing the item * @return The lore */ List getLore(Player player); /** - * Converts this displayable to an ItemStack for display. This should be a pure function that creates a new ItemStack instance. + * Converts this displayable to an {@link ItemStack} for display. * * @param player The player viewing the inventory * @return The ItemStack representation */ - ItemStack asItemStack(Player player); + default ItemStack asItemStack(Player player) { + ItemStack itemStack = getIcon().parseItem(); + ItemMeta itemMeta = itemStack.getItemMeta(); + itemMeta.setDisplayName(getDisplayName(player)); + itemMeta.setLore(getLore(player)); + itemStack.setItemMeta(itemMeta); + return itemStack; + } } \ No newline at end of file diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java index d717963e..9e34095f 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java @@ -17,22 +17,47 @@ */ package de.eintosti.buildsystem.api.world.display; -import com.cryptomorin.xseries.XMaterial; +import de.eintosti.buildsystem.api.world.BuildWorld; import java.util.List; +import java.util.UUID; import org.jetbrains.annotations.Unmodifiable; public interface Folder extends Displayable { + /** + * Gets a list of all world UUIDs in this folder. + * + * @return An unmodifiable list of world UUIDs + */ @Unmodifiable - List getWorlds(); - - boolean containsWorld(String worldName); - - void addWorld(String worldName); - - void removeWorld(String worldName); - + List getWorldUUIDs(); + + /** + * Gets whether this folder contains the specific {@link BuildWorld}. + * + * @param buildWorld The world to check + * @return {@code true} if the folder contains the world, {@code false} otherwise + */ + boolean containsWorld(BuildWorld buildWorld); + + /** + * Adds a {@link BuildWorld} to this folder. + * + * @param buildWorld The world to add + */ + void addWorld(BuildWorld buildWorld); + + /** + * Removes a {@link BuildWorld} from this folder. + * + * @param buildWorld The world to remove + */ + void removeWorld(BuildWorld buildWorld); + + /** + * Gets the number of worlds in this folder. + * + * @return The number of worlds in this folder + */ int getWorldCount(); - - void setMaterial(XMaterial material); } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index 20a0f1a6..2ba0cb8a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -78,6 +78,7 @@ public static void createMessageFile() { setMessage(sb, "loading_world", "&7Loading &b%world%&7..."); setMessage(sb, "world_not_loaded", "&cWorld is not loaded!"); setMessage(sb, "enter_world_name", "&7Enter &bWorld Name"); + setMessage(sb, "enter_folder_name", "&7Enter &bFolder Name"); setMessage(sb, "enter_generator_name", "&7Enter &bGenerator Name"); setMessage(sb, "enter_world_creator", "&7Enter &bWorld Creator"); setMessage(sb, "enter_world_permission", "&7Enter &bPermission"); @@ -262,6 +263,16 @@ public static void createMessageFile() { setMessage(sb, "worlds_edit_unknown_world", "%prefix% &cUnknown world."); setMessage(sb, "worlds_edit_error", "%prefix% &cError: Please try again."); addSpacer(sb, ""); + setMessage(sb, "worlds_folder_usage", "%prefix% &7Usage: &b/worlds folder "); + setMessage(sb, "worlds_folder_unknown_folder", "%prefix% &cUnknown folder."); + setMessage(sb, "worlds_folder_unknown_world", "%prefix% &cUnknown world."); + setMessage(sb, "worlds_folder_world_already_in_folder", "%prefix% &c%world% is already contained within %folder%."); + setMessage(sb, "worlds_folder_world_already_in_another_folder", "%prefix% &c%world% is already contained within another folder."); + setMessage(sb, "worlds_folder_world_not_in_folder", "%prefix% &c%world% is not contained within %folder%."); + setMessage(sb, "worlds_folder_world_added_to_folder", "%prefix% &b%world% &7was &aadded &7to &b%folder%&7."); + setMessage(sb, "worlds_folder_world_removed_from_folder", "%prefix% &b%world% &7was &cremoved &7from &b%folder%&7."); + setMessage(sb, "worlds_folder_created", "%prefix% &7The folder &b%folder% &7was &asuccessfully &7created."); + addSpacer(sb, ""); setMessage(sb, "worlds_help_usage", "%prefix% &7Usage: &b/worlds help [page]"); setMessage(sb, "worlds_help_invalid_page", "%prefix% &cInvalid page."); setMessage(sb, "worlds_help_title_with_page", "%prefix% &7&nWorlds Help:&8 (&7%page%/%max%&8)"); @@ -425,6 +436,7 @@ public static void createMessageFile() { setMessage(sb, "world_navigator_title", "&3» &8World Navigator"); setMessage(sb, "world_navigator_no_worlds", "&c&nNo worlds available"); setMessage(sb, "world_navigator_create_world", "&bCreate World"); + setMessage(sb, "world_navigator_create_folder", "&bCreate Folder"); setMessage(sb, "world_item_title", "&3&l%world%"); setMessage(sb, "world_item_lore_normal", Arrays.asList( "&7Status&8: %status%", @@ -449,6 +461,10 @@ public static void createMessageFile() { "&8- &7&oLeft click&8: &7Teleport", "&8- &7&oRight click&8: &7Edit" )); + setMessage(sb, "folder_item_title", "&3&l%folder%"); + setMessage(sb, "folder_item_lore", Arrays.asList( + "&7Worlds&8: &b%worlds%" + )); setMessage(sb, "world_item_builders_builder_template", "&b%builder%&7, "); setMessage(sb, "world_sort_title", "&bSort"); setMessage(sb, "world_sort_name_az", "&8» &7&oName (A-Z)"); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/WorldsCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/WorldsCommand.java index 8c34bcdf..d38bfd14 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/WorldsCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/WorldsCommand.java @@ -25,6 +25,7 @@ import de.eintosti.buildsystem.command.subcommand.worlds.BuildersSubCommand; import de.eintosti.buildsystem.command.subcommand.worlds.DeleteSubCommand; import de.eintosti.buildsystem.command.subcommand.worlds.EditSubCommand; +import de.eintosti.buildsystem.command.subcommand.worlds.FolderSubCommand; import de.eintosti.buildsystem.command.subcommand.worlds.HelpSubCommand; import de.eintosti.buildsystem.command.subcommand.worlds.ImportAllSubCommand; import de.eintosti.buildsystem.command.subcommand.worlds.ImportSubCommand; @@ -104,6 +105,10 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N subCommand = new EditSubCommand(plugin, worldName); break; } + case FOLDER: { + subCommand = new FolderSubCommand(plugin); + break; + } case HELP: { subCommand = new HelpSubCommand(plugin); break; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java new file mode 100644 index 00000000..a9a127ea --- /dev/null +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018-2025, Thomas Meaney + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.eintosti.buildsystem.command.subcommand.worlds; + +import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.storage.FolderStorage; +import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.command.subcommand.Argument; +import de.eintosti.buildsystem.command.subcommand.SubCommand; +import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete.WorldsArgument; +import de.eintosti.buildsystem.world.WorldServiceImpl; +import java.util.AbstractMap; +import java.util.Locale; +import java.util.Map.Entry; +import org.bukkit.entity.Player; + +public class FolderSubCommand implements SubCommand { + + private final WorldServiceImpl worldService; + + public FolderSubCommand(BuildSystemPlugin plugin) { + this.worldService = plugin.getWorldService(); + } + + @Override + public void execute(Player player, String[] args) { + if (args.length != 4) { + Messages.sendMessage(player, "worlds_folder_usage"); + return; + } + + String folderName = args[1]; + String operation = args[2].toLowerCase(Locale.ROOT); + String worldName = args[3]; + + FolderStorage folderStorage = worldService.getFolderStorage(); + Folder folder = folderStorage.getFolder(folderName); + if (folder == null) { + Messages.sendMessage(player, "worlds_folder_unknown_folder"); + return; + } + + BuildWorld buildWorld = worldService.getWorldStorage().getBuildWorld(worldName); + if (buildWorld == null) { + Messages.sendMessage(player, "worlds_folder_unknown_world"); + return; + } + + Entry folderPlaceholder = new AbstractMap.SimpleEntry<>("%folder%", folder.getName()); + Entry worldPlaceholder = new AbstractMap.SimpleEntry<>("%world%", buildWorld.getName()); + + switch (operation) { + case "add": { + if (folder.containsWorld(buildWorld)) { + Messages.sendMessage(player, "worlds_folder_world_already_in_folder", folderPlaceholder, worldPlaceholder); + return; + } + + if (folderStorage.isAssignedToAnyFolder(buildWorld)) { + Messages.sendMessage(player, "worlds_folder_world_already_in_another_folder", worldPlaceholder); + return; + } + + folder.addWorld(buildWorld); + Messages.sendMessage(player, "worlds_folder_world_added_to_folder", folderPlaceholder, worldPlaceholder); + break; + } + + case "remove": { + if (!folder.containsWorld(buildWorld)) { + Messages.sendMessage(player, "worlds_folder_world_not_in_folder", folderPlaceholder, worldPlaceholder); + return; + } + + folder.removeWorld(buildWorld); + Messages.sendMessage(player, "worlds_folder_world_removed_from_folder", folderPlaceholder, worldPlaceholder); + break; + } + + default: { + Messages.sendMessage(player, "worlds_folder_usage"); + break; + } + } + } + + @Override + public Argument getArgument() { + return WorldsArgument.FOLDER; + } +} \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FilteredWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FilteredWorldsInventory.java index f215a0d7..fc5309c0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FilteredWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FilteredWorldsInventory.java @@ -31,8 +31,12 @@ import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; import de.eintosti.buildsystem.api.world.data.WorldData; +import de.eintosti.buildsystem.api.world.display.Displayable; +import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.player.PlayerServiceImpl; import de.eintosti.buildsystem.player.settings.SettingsManager; +import de.eintosti.buildsystem.storage.FolderStorageImpl; +import de.eintosti.buildsystem.storage.WorldStorageImpl; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; import de.eintosti.buildsystem.util.InventoryUtils; import de.eintosti.buildsystem.util.PaginatedInventory; @@ -43,9 +47,13 @@ import de.eintosti.buildsystem.world.modification.EditInventory; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.Locale; +import java.util.Objects; import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -62,7 +70,8 @@ public abstract class FilteredWorldsInventory extends PaginatedInventory impleme private final BuildSystemPlugin plugin; private final PlayerServiceImpl playerService; private final SettingsManager settingsManager; - private final WorldServiceImpl worldService; + private final FolderStorageImpl folderStorage; + private final WorldStorageImpl worldStorage; private final String inventoryName; private final String noWorldsText; @@ -73,7 +82,9 @@ public FilteredWorldsInventory(BuildSystemPlugin plugin, String inventoryName, S this.plugin = plugin; this.playerService = plugin.getPlayerService(); this.settingsManager = plugin.getSettingsManager(); - this.worldService = plugin.getWorldService(); + WorldServiceImpl worldService = plugin.getWorldService(); + this.folderStorage = worldService.getFolderStorage(); + this.worldStorage = worldService.getWorldStorage(); this.inventoryName = inventoryName; this.noWorldsText = noWorldsText; @@ -97,15 +108,17 @@ protected Inventory createInventory(Player player) { } /** - * Gets the number of worlds that are to be displayed in the inventory. + * Gets the number of {@link Displayable} objects that are to be displayed in the inventory. * * @param player The player to show the inventory to - * @return The number of worlds + * @return The number of displayable objects */ - private int numOfWorlds(Player player) { - return (int) worldService.getWorldStorage().getBuildWorlds().stream() + private int calculateNumObjects(Player player) { + int numFolders = folderStorage.getFolders().size(); + int numWorlds = (int) worldStorage.getBuildWorlds().stream() .filter(buildWorld -> isValidWorld(player, buildWorld)) .count(); + return numFolders + numWorlds; } /** @@ -127,52 +140,86 @@ private Inventory getInventory(Player player) { } private void addWorlds(Player player) { - int numWorlds = numOfWorlds(player); - int numInventories = numWorlds % MAX_WORLDS == 0 - ? Math.max(numWorlds, 1) - : numWorlds + 1; + int numObjects = calculateNumObjects(player); + int numInventories = numObjects % MAX_WORLDS == 0 + ? Math.max(numObjects, 1) + : numObjects + 1; inventories = new Inventory[numInventories]; Inventory inventory = createInventory(player); int index = 0; inventories[index] = inventory; - if (numWorlds == 0) { + if (numObjects == 0) { inventory.setItem(22, InventoryUtils.createSkull(Messages.getString(noWorldsText, player), Profileable.detect("2e3f50ba62cbda3ecf5479b62fedebd61d76589771cc19286bf2745cd71e47c6"))); return; } - int columnWorld = 9, maxColumnWorld = 44; - for (BuildWorld buildWorld : worldService.getDisplayOrder(plugin.getSettingsManager().getSettings(player))) { - if (isValidWorld(player, buildWorld)) { - String displayName = Messages.getString("world_item_title", player, new AbstractMap.SimpleEntry<>("%world%", buildWorld.getName())); - List lore = buildWorld.getLore(player); - InventoryUtils.addWorldItem(inventory, columnWorld++, buildWorld, displayName, lore); - } + int columnObject = 9, maxColumnObject = 44; + for (Displayable displayable : collectDisplayables(player)) { + inventory.setItem(columnObject++, displayable.asItemStack(player)); - if (columnWorld > maxColumnWorld) { - columnWorld = 9; + if (columnObject > maxColumnObject) { + columnObject = 9; inventory = createInventory(player); inventories[++index] = inventory; } } } - private void addWorldSortItem(Inventory inventory, Player player) { - Settings settings = settingsManager.getSettings(player); - WorldSort worldSort = settings.getWorldDisplay().getWorldSort(); - inventory.setItem(45, InventoryUtils.createItem(XMaterial.BOOK, Messages.getString("world_sort_title", player), Messages.getString(worldSort.getMessageKey(), player))); - } - - private void addWorldFilterItem(Inventory inventory, Player player) { + /** + * Collects all {@link Displayable} items (i.e. {@link Folder}s and {@link BuildWorld}s) that should be shown to the player. + *

+ * The method applies the following logic: + *

    + *
  • Fetches all {@link BuildWorld}s visible to the player based on world access rights and filter settings.
  • + *
  • Sorts the filtered worlds according to the player's selected sort order.
  • + *
  • If a world is assigned to a {@link Folder}, it is excluded from display and the folder is shown instead.
  • + *
  • If a world is not assigned to any folder, it is displayed directly.
  • + *
  • All folders are listed first, followed by all standalone worlds.
  • + *
+ * This ensures that: + *
    + *
  • Worlds are never shown twice (both directly and via their folder).
  • + *
  • Folder grouping takes display priority over individual worlds.
  • + *
+ * + * @param player The player for whom to collect displayable items + * @return A list of {@link Displayable} items to be presented in the UI, sorted with folders first. + */ + private List collectDisplayables(Player player) { Settings settings = settingsManager.getSettings(player); - WorldFilter worldFilter = settings.getWorldDisplay().getWorldFilter(); + WorldDisplay worldDisplay = settings.getWorldDisplay(); - List lore = new ArrayList<>(); - lore.add(Messages.getString(worldFilter.getMode().getLoreKey(), player, new AbstractMap.SimpleEntry<>("%text%", worldFilter.getText()))); - lore.addAll(Messages.getStringList("world_filter_lore", player)); + List buildWorlds = worldStorage.getBuildWorlds().stream() + .filter(buildWorld -> isValidWorld(player, buildWorld)) + .filter(worldDisplay.getWorldFilter().apply()) + .sorted(createDisplayOrderComparator(worldDisplay.getWorldSort())) + .collect(Collectors.toList()); + + List folders = collectFolders(buildWorlds); + List standaloneWorlds = buildWorlds.stream() + .filter(world -> !this.folderStorage.isAssignedToAnyFolder(world)) + .collect(Collectors.toList()); + + List displayables = new ArrayList<>(); + displayables.addAll(folders); + displayables.addAll(standaloneWorlds); + return displayables; + } - inventory.setItem(46, InventoryUtils.createItem(XMaterial.HOPPER, Messages.getString("world_filter_title", player), lore)); + /** + * Collects all {@link Folder}s that contain a {@link BuildWorld} from the given list. + * + * @param buildWorlds The list of build worlds to check for folders + * @return A list of folders that contain a build world + */ + private List collectFolders(List buildWorlds) { + return buildWorlds.stream() + .map(this.folderStorage::getAssignedFolder) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); } /** @@ -192,7 +239,7 @@ private void addWorldFilterItem(Inventory inventory, Player player) { */ private boolean isValidWorld(Player player, BuildWorld buildWorld) { WorldData worldData = buildWorld.getData(); - if (!worldService.getWorldStorage().isCorrectVisibility(worldData.privateWorld().get(), visibility)) { + if (!worldStorage.isCorrectVisibility(worldData.privateWorld().get(), visibility)) { return false; } @@ -207,6 +254,60 @@ private boolean isValidWorld(Player player, BuildWorld buildWorld) { return Bukkit.getWorld(buildWorld.getName()) != null || !buildWorld.isLoaded(); } + /** + * Creates a comparator which sorts the {@link BuildWorld}s in the order they are to be displayed. + * + * @param worldSort The order in which the worlds are to be sorted + * @return The comparator + */ + private Comparator createDisplayOrderComparator(WorldSort worldSort) { + Comparator comparator; + switch (worldSort) { + case OLDEST_FIRST: + comparator = Comparator.comparingLong(BuildWorld::getCreationDate); + break; + case NEWEST_FIRST: + comparator = Comparator.comparingLong(BuildWorld::getCreationDate).reversed(); + break; + case PROJECT_A_TO_Z: + comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)); + break; + case PROJECT_Z_TO_A: + comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)).reversed(); + break; + case STATUS_NOT_STARTED: + comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()); + break; + case STATUS_FINISHED: + comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()).reversed(); + break; + case NAME_Z_TO_A: + comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)).reversed(); + break; + default: // NAME_A_TO_Z + comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)); + break; + } + return comparator; + } + + private void addWorldSortItem(Inventory inventory, Player player) { + Settings settings = settingsManager.getSettings(player); + WorldSort worldSort = settings.getWorldDisplay().getWorldSort(); + inventory.setItem(45, InventoryUtils.createItem(XMaterial.BOOK, Messages.getString("world_sort_title", player), Messages.getString(worldSort.getMessageKey(), player))); + } + + private void addWorldFilterItem(Inventory inventory, Player player) { + Settings settings = settingsManager.getSettings(player); + WorldFilter worldFilter = settings.getWorldDisplay().getWorldFilter(); + + List lore = new ArrayList<>(); + lore.add(Messages.getString(worldFilter.getMode().getLoreKey(), player, new AbstractMap.SimpleEntry<>("%text%", worldFilter.getText()))); + lore.addAll(Messages.getStringList("world_filter_lore", player)); + + inventory.setItem(46, InventoryUtils.createItem(XMaterial.HOPPER, Messages.getString("world_filter_title", player), lore)); + } + @EventHandler public void onInventoryClick(InventoryClickEvent event) { if (!InventoryUtils.isValidClick(event, inventoryName)) { @@ -256,13 +357,26 @@ public void onInventoryClick(InventoryClickEvent event) { return; } break; + case 50: + if (itemStack.getType() == XMaterial.PLAYER_HEAD.get()) { + XSound.ENTITY_CHICKEN_EGG.play(player); + player.closeInventory(); + new PlayerChatInput(plugin, player, "enter_folder_name", input -> { + Folder folder = folderStorage.createFolder(input.trim()); + Messages.sendMessage(player, "worlds_folder_created", + new AbstractMap.SimpleEntry<>("%folder%", folder.getName()) + ); + }); + return; + } + break; case 52: - if (decrementInv(player, numOfWorlds(player), MAX_WORLDS)) { + if (decrementInv(player, calculateNumObjects(player), MAX_WORLDS)) { openInventory(player); } return; case 53: - if (incrementInv(player, numOfWorlds(player), MAX_WORLDS)) { + if (incrementInv(player, calculateNumObjects(player), MAX_WORLDS)) { openInventory(player); } return; @@ -340,7 +454,7 @@ public void manageInventoryClick(InventoryClickEvent event, Player player, ItemS } if (slot >= 9 && slot <= 44) { - BuildWorld buildWorld = worldService.getWorldStorage().getBuildWorld(getWorldName(player, displayName)); + BuildWorld buildWorld = worldStorage.getBuildWorld(getWorldName(player, displayName)); manageWorldItemClick(event, player, itemMeta, buildWorld); return; } @@ -404,7 +518,7 @@ private void manageWorldItemClick(InventoryClickEvent event, Player player, Item private void performNonEditClick(Player player, ItemMeta itemMeta) { playerService.closeNavigator(player); String worldName = getWorldName(player, itemMeta.getDisplayName()); - BuildWorld buildWorld = worldService.getWorldStorage().getBuildWorld(worldName); + BuildWorld buildWorld = worldStorage.getBuildWorld(worldName); if (buildWorld == null) { plugin.getLogger().warning("Could not find world " + worldName); return; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java index 131ca1ad..4b6b15ce 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java @@ -36,13 +36,10 @@ public class PublicWorldsInventory extends FilteredWorldsInventory { BuildWorldStatus.NOT_STARTED, BuildWorldStatus.IN_PROGRESS, BuildWorldStatus.ALMOST_FINISHED, BuildWorldStatus.FINISHED ); - private final BuildSystemPlugin plugin; private final PlayerServiceImpl playerManager; public PublicWorldsInventory(BuildSystemPlugin plugin) { super(plugin, "world_navigator_title", "world_navigator_no_worlds", VISIBILITY, VALID_STATUS); - - this.plugin = plugin; this.playerManager = plugin.getPlayerService(); } @@ -52,6 +49,7 @@ protected Inventory createInventory(Player player) { if (playerManager.canCreateWorld(player, super.getVisibility())) { addWorldCreateItem(inventory, player); } + addFolderCreateItem(inventory, player); return inventory; } @@ -62,4 +60,12 @@ private void addWorldCreateItem(Inventory inventory, Player player) { InventoryUtils.addGlassPane(player, inventory, 49); } } + + private void addFolderCreateItem(Inventory inventory, Player player) { + if (player.hasPermission("buildsystem.create.folder")) { + inventory.setItem(50, InventoryUtils.createSkull(Messages.getString("world_navigator_create_folder", player), Profileable.detect("3edd20be93520949e6ce789dc4f43efaeb28c717ee6bfcbbe02780142f716"))); + } else { + InventoryUtils.addGlassPane(player, inventory, 50); + } + } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java index 65eb261d..c7d5ff29 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java @@ -18,14 +18,15 @@ package de.eintosti.buildsystem.storage; import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.api.storage.FolderStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; -import de.eintosti.buildsystem.api.storage.FolderStorage; import de.eintosti.buildsystem.world.display.FolderImpl; import java.util.AbstractMap; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.UUID; import java.util.function.Function; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -37,7 +38,7 @@ public abstract class FolderStorageImpl implements FolderStorage { protected final Logger logger; private final Map foldersByName; - private final Map worldToFolderMap; + private final Map worldToFolderMap; public FolderStorageImpl(BuildSystemPlugin plugin) { this.plugin = plugin; @@ -46,7 +47,7 @@ public FolderStorageImpl(BuildSystemPlugin plugin) { this.foldersByName = load().stream() .collect(Collectors.toMap(Folder::getName, Function.identity())); this.worldToFolderMap = this.foldersByName.values().stream() - .flatMap(folder -> folder.getWorlds().stream() + .flatMap(folder -> folder.getWorldUUIDs().stream() .map(world -> new AbstractMap.SimpleEntry<>(world, folder.getName()))) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @@ -58,11 +59,10 @@ public Collection getFolders() { } @Override - public void addFolder(Folder folder) { - foldersByName.put(folder.getName(), folder); - for (String worldName : folder.getWorlds()) { - worldToFolderMap.put(worldName, folder.getName()); - } + public Folder createFolder(String folderName) { + Folder folder = new FolderImpl(this, folderName); + foldersByName.put(folderName, folder); + return folder; } @Override @@ -72,8 +72,8 @@ public void removeFolder(String folderName) { return; } - for (String worldName : removed.getWorlds()) { - worldToFolderMap.remove(worldName); + for (UUID worldUuid : removed.getWorldUUIDs()) { + worldToFolderMap.remove(worldUuid); } } @@ -92,31 +92,50 @@ public boolean folderExists(String folderName, boolean caseSensitive) { } @Override - public void addWorldToFolder(String worldName, String folderName) { - Folder folder = foldersByName.get(folderName); - if (folder == null) { - plugin.getLogger().warning(String.format("Attempting to add world %s to unknown folder %s", worldName, folderName)); - return; - } - - folder.addWorld(worldName); - worldToFolderMap.put(worldName, folderName); + public Folder getFolder(String folderName) { + return getFolder(folderName, false); } @Override - public void removeWorldFromFolder(String worldName, String folderName) { - Folder folder = foldersByName.get(folderName); - if (folder == null) { - plugin.getLogger().warning(String.format("Attempting to remove world %s from unknown folder %s", worldName, folderName)); - return; + public Folder getFolder(String folderName, boolean caseSensitive) { + if (caseSensitive) { + return foldersByName.get(folderName); + } else { + return foldersByName.entrySet().stream() + .filter(entry -> entry.getKey().equalsIgnoreCase(folderName)) + .map(Map.Entry::getValue) + .findFirst() + .orElse(null); } + } + + /** + * Caches which {@link Folder} a {@link BuildWorld} is assigned to. + * + * @param buildWorld The world to assign + * @param folderName The name of the folder + */ + public void assignWorldToFolder(BuildWorld buildWorld, String folderName) { + this.worldToFolderMap.put(buildWorld.getUniqueId(), folderName); + } - folder.removeWorld(worldName); - worldToFolderMap.remove(worldName); + /** + * Removes the mapping of a {@link BuildWorld} to its assigned {@link Folder}. + * + * @param buildWorld The world to unassign + */ + public void unassignWorldToFolder(BuildWorld buildWorld) { + this.worldToFolderMap.remove(buildWorld.getUniqueId()); + } + + @Override + public boolean isAssignedToAnyFolder(BuildWorld buildWorld) { + return worldToFolderMap.containsKey(buildWorld.getUniqueId()); } @Override - public boolean isWorldInAnyFolder(BuildWorld buildWorld) { - return worldToFolderMap.containsKey(buildWorld.getName()); + public Folder getAssignedFolder(BuildWorld buildWorld) { + String folderName = worldToFolderMap.get(buildWorld.getUniqueId()); + return getFolder(folderName); } } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java index d24b6c30..dffcee7a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.logging.Level; import java.util.stream.Collectors; import org.bukkit.configuration.ConfigurationSection; @@ -89,8 +90,8 @@ public void save(Collection folders) { public @NotNull Map serializeFolder(Folder folder) { Map serializedFolder = new HashMap<>(); - serializedFolder.put("material", folder.getMaterial().name()); - serializedFolder.put("worlds", folder.getWorlds()); + serializedFolder.put("material", folder.getIcon().name()); + serializedFolder.put("worlds", folder.getWorldUUIDs().stream().map(UUID::toString).collect(Collectors.toList())); return serializedFolder; } @@ -119,9 +120,10 @@ private Folder loadFolder(String folderName) { XMaterial defaultMaterial = XMaterial.CHEST; XMaterial material = XMaterial.matchXMaterial(config.getString(path + ".material", defaultMaterial.name())).orElse(defaultMaterial); - List worlds = config.getStringList(path + ".worlds"); + List worlds = config.getStringList(path + ".worlds").stream().map(UUID::fromString).collect(Collectors.toList()); return new FolderImpl( + this, folderName, material, worlds diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java index 0cc3c741..3a896c82 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java @@ -105,6 +105,7 @@ public void save(Collection buildWorlds) { public @NotNull Map serializeWorld(BuildWorld buildWorld) { Map world = new HashMap<>(); + world.put("uuid", buildWorld.getUniqueId().toString()); Builders builders = buildWorld.getBuilders(); if (builders.getCreator() != null) { world.put("creator", builders.getCreator().toString()); @@ -154,6 +155,9 @@ public Collection load() { } private BuildWorldImpl loadWorld(String worldName) { + UUID uuid = config.isString("worlds." + worldName + ".uuid") + ? UUID.fromString(config.getString("worlds." + worldName + ".uuid")) + : UUID.randomUUID(); // Generate a new UUID if not present Builder creator = parseCreator(worldName); BuildWorldType worldType = config.isString("worlds." + worldName + ".type") ? BuildWorldType.valueOf(config.getString("worlds." + worldName + ".type")) @@ -167,6 +171,7 @@ private BuildWorldImpl loadWorld(String worldName) { CustomGeneratorImpl customGenerator = new CustomGeneratorImpl(generatorName, parseChunkGenerator(worldName, generatorName)); return new BuildWorldImpl( + uuid, worldName, worldType, worldData, diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java index 57bc8f5d..5687a0a5 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java @@ -197,6 +197,7 @@ public enum WorldsArgument implements Argument { BUILDERS("builders", "buildsystem.builders"), DELETE("delete", "buildsystem.delete"), EDIT("edit", "buildsystem.edit"), + FOLDER("folder", "buildsystem.folder"), HELP("help", "buildsystem.help.worlds"), IMPORT("import", "buildsystem.import"), IMPORT_ALL("importAll", "buildsystem.import.all"), diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java index 83b092dc..f43ec1ac 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java @@ -40,11 +40,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.Difficulty; import org.bukkit.World; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Nullable; @@ -52,6 +52,7 @@ public final class BuildWorldImpl implements BuildWorld { private static final BuildSystemPlugin PLUGIN = JavaPlugin.getPlugin(BuildSystemPlugin.class); + private final UUID uuid; private String name; private boolean loaded; @@ -64,29 +65,6 @@ public final class BuildWorldImpl implements BuildWorld { private final WorldLoaderImpl worldLoader; private final WorldUnloaderImpl worldUnloader; -// public BuildWorldImpl( -// String name, -// Builder creator, -// BuildWorldType worldType, -// long creationDate, -// boolean privateWorld, -// CustomGenerator customGenerator, -// List builders -// ) { -// BuildSystemPlugin plugin = JavaPlugin.getPlugin(BuildSystemPlugin.class); -// -// this.name = name; -// this.worldType = worldType; -// this.worldData = new WorldDataImpl(name, plugin.getConfigValues(), privateWorld); -// this.creationDate = creationDate; -// this.customGenerator = customGenerator; -// this.builders = new BuildersImpl(creator, builders); -// -// this.worldLoader = WorldLoaderImpl.of(this); -// this.worldUnloader = WorldUnloaderImpl.of(this); -// this.worldUnloader.manageUnload(); -// } - public BuildWorldImpl( String name, Builder creator, @@ -96,6 +74,7 @@ public BuildWorldImpl( CustomGenerator customGenerator ) { this( + UUID.randomUUID(), name, worldType, new WorldDataImpl( @@ -112,6 +91,7 @@ public BuildWorldImpl( } public BuildWorldImpl( + UUID uuid, String name, BuildWorldType worldType, WorldDataImpl worldData, @@ -120,6 +100,7 @@ public BuildWorldImpl( long creationDate, CustomGenerator customGenerator ) { + this.uuid = uuid; this.name = name; this.worldType = worldType; this.worldData = worldData; @@ -142,6 +123,11 @@ public World getWorld() { return Bukkit.getWorld(name); } + @Override + public UUID getUniqueId() { + return this.uuid; + } + @Override public String getName() { return this.name; @@ -154,10 +140,15 @@ public void setName(String name) { } @Override - public XMaterial getMaterial() { + public XMaterial getIcon() { return this.worldData.material().get(); } + @Override + public void setIcon(XMaterial material) { + this.worldData.material().set(material); + } + @Override public String getDisplayName(Player player) { return Messages.getString("world_item_title", player, @@ -212,11 +203,6 @@ public List getLore(Player player) { return lore; } - @Override - public ItemStack asItemStack(Player player) { - return getMaterial().parseItem(); - } - @Override public Profileable asProfilable() { return builders.hasCreator() diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java index 7e45ecce..d1fb13a8 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java @@ -20,10 +20,6 @@ import com.cryptomorin.xseries.XSound; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; -import de.eintosti.buildsystem.api.navigator.settings.WorldFilter; -import de.eintosti.buildsystem.api.navigator.settings.WorldSort; -import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.WorldService; import de.eintosti.buildsystem.api.world.builder.Builder; @@ -43,12 +39,9 @@ import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Location; @@ -87,49 +80,6 @@ public FolderStorageImpl getFolderStorage() { return folderStorage; } - /** - * Gets the worlds in the order they are to be displayed. First, the {@link WorldFilter} is applied. Then, the list of worlds is sorted using the {@link WorldSort}. - * - * @param settings The settings that provide the sorting method - * @return The list of sorted worlds - */ - public List getDisplayOrder(Settings settings) { - WorldDisplay worldDisplay = settings.getWorldDisplay(); - Comparator comparator; - - switch (worldDisplay.getWorldSort()) { - case OLDEST_FIRST: - comparator = Comparator.comparingLong(BuildWorld::getCreationDate); - break; - case NEWEST_FIRST: - comparator = Comparator.comparingLong(BuildWorld::getCreationDate).reversed(); - break; - case PROJECT_A_TO_Z: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)); - break; - case PROJECT_Z_TO_A: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)).reversed(); - break; - case STATUS_NOT_STARTED: - comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()); - break; - case STATUS_FINISHED: - comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()).reversed(); - break; - case NAME_Z_TO_A: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)).reversed(); - break; - default: // NAME_A_TO_Z - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)); - break; - } - - return worldStorage.getBuildWorlds().stream() - .filter(worldDisplay.getWorldFilter().apply()) - .sorted(comparator) - .collect(Collectors.toList()); - } - public void startWorldNameInput(Player player, BuildWorldType worldType, @Nullable String template, boolean privateWorld) { player.closeInventory(); new PlayerChatInput(plugin, player, "enter_world_name", input -> { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java index e1cf43c3..0febd891 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java @@ -19,67 +19,77 @@ import com.cryptomorin.xseries.XMaterial; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.world.display.Displayable; +import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.storage.FolderStorageImpl; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Unmodifiable; public class FolderImpl implements Folder { + private final FolderStorageImpl folderStorage; + private final String name; - private final List worldNames; + private final List worlds; private XMaterial material; - public FolderImpl(String name) { - this.name = name; - this.worldNames = new ArrayList<>(); - this.material = XMaterial.CHEST; + public FolderImpl(FolderStorageImpl folderStorage, String name) { + this(folderStorage, name, XMaterial.CHEST, new ArrayList<>()); } - public FolderImpl(String name, XMaterial material, List worldNames) { + public FolderImpl(FolderStorageImpl folderStorage, String name, XMaterial material, List worlds) { + this.folderStorage = folderStorage; this.name = name; - this.worldNames = worldNames; + this.worlds = worlds; this.material = material; } @Override public String getName() { - return name; + return this.name; } + @Override @Unmodifiable - public List getWorlds() { - return Collections.unmodifiableList(worldNames); + public List getWorldUUIDs() { + return Collections.unmodifiableList(this.worlds); } - public boolean containsWorld(String worldName) { - return worldNames.contains(worldName); + @Override + public boolean containsWorld(BuildWorld buildWorld) { + return this.worlds.contains(buildWorld.getUniqueId()); } - public void addWorld(String worldName) { - worldNames.add(worldName); + @Override + public void addWorld(BuildWorld buildWorld) { + this.worlds.add(buildWorld.getUniqueId()); + this.folderStorage.assignWorldToFolder(buildWorld, this.name); } - public void removeWorld(String worldName) { - worldNames.remove(worldName); + @Override + public void removeWorld(BuildWorld buildWorld) { + this.worlds.remove(buildWorld.getUniqueId()); + this.folderStorage.unassignWorldToFolder(buildWorld); } + @Override public int getWorldCount() { - return worldNames.size(); + return this.worlds.size(); } @Override - public XMaterial getMaterial() { - return material; + public XMaterial getIcon() { + return this.material; } - public void setMaterial(XMaterial material) { + @Override + public void setIcon(XMaterial material) { this.material = material; } @@ -93,17 +103,12 @@ public String getDisplayName(Player player) { @Override public List getLore(Player player) { List lore = new ArrayList<>(); - lore.add(Messages.getString("folder_item_contents", player, - new AbstractMap.SimpleEntry<>("%count%", String.valueOf(getWorldCount()))) + lore.add(Messages.getString("folder_item_lore", player, + new AbstractMap.SimpleEntry<>("%worlds%", String.valueOf(getWorldCount()))) ); return lore; } - @Override - public ItemStack asItemStack(Player player) { - return getMaterial().parseItem(); - } - @Override public boolean equals(Object o) { if (this == o) { From 388085862b7fee6275e8c17d85605b6a5c40d8c8 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 12:28:41 +0200 Subject: [PATCH 02/22] Continue work on world folders - Folder can now be entered and left - Refactored a bunch of inventory-specific code --- .../buildsystem/api/storage/WorldStorage.java | 10 + .../buildsystem/BuildSystemPlugin.java | 21 - .../de/eintosti/buildsystem/Messages.java | 7 +- .../buildsystem/command/BackCommand.java | 3 - .../buildsystem/command/SpawnCommand.java | 3 +- .../buildsystem/event/EventDispatcher.java | 2 +- .../world/BuildWorldManipulationEvent.java | 1 - .../luckperms/calculators/RoleCalculator.java | 3 +- .../listener/AsyncPlayerPreLoginListener.java | 7 +- .../listener/BlockPhysicsListener.java | 3 +- .../listener/BlockPlaceListener.java | 3 +- .../BuildWorldResetUnloadListener.java | 1 - .../listener/EntityDamageListener.java | 1 - .../listener/EntitySpawnListener.java | 3 +- .../listener/NavigatorListener.java | 9 +- .../inventory/ArchivedWorldsInventory.java | 23 +- .../inventory/CreatableWorldsInventory.java | 121 ++++ .../inventory/DisplayablesInventory.java | 597 ++++++++++++++++++ .../inventory/FilteredWorldsInventory.java | 528 ---------------- .../inventory/FolderContentInventory.java | 80 +++ .../inventory/NavigatorInventory.java | 11 +- .../inventory/PrivateWorldsInventory.java | 65 +- .../inventory/PublicWorldsInventory.java | 74 +-- .../buildsystem/player/BlocksInventory.java | 4 +- .../buildsystem/player/BuildPlayerImpl.java | 3 +- .../player/settings/DesignInventory.java | 4 +- .../player/settings/SettingsImpl.java | 4 +- .../player/settings/SettingsInventory.java | 4 +- .../player/settings/SpeedInventory.java | 4 +- .../buildsystem/storage/WorldStorageImpl.java | 10 + .../storage/factory/PlayerStorageFactory.java | 2 - .../buildsystem/util/InventoryUtils.java | 10 +- .../world/builder/BuilderInventory.java | 4 +- .../world/creation/BuildWorldCreatorImpl.java | 4 +- .../world/creation/CreateInventory.java | 4 +- .../world/modification/DeleteInventory.java | 4 +- .../world/modification/EditInventory.java | 4 +- .../world/modification/GameRuleInventory.java | 5 +- .../world/modification/SetupInventory.java | 4 +- .../world/util/WorldLoaderImpl.java | 1 - .../world/util/WorldPermissionsImpl.java | 2 +- .../world/util/WorldTeleporterImpl.java | 1 - 42 files changed, 955 insertions(+), 699 deletions(-) create mode 100644 buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java create mode 100644 buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java delete mode 100644 buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FilteredWorldsInventory.java create mode 100644 buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java index 48d8ade0..bc58e3d7 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java @@ -21,6 +21,7 @@ import de.eintosti.buildsystem.api.world.data.Visibility; import java.util.Collection; import java.util.List; +import java.util.UUID; import org.bukkit.World; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; @@ -46,6 +47,15 @@ public interface WorldStorage extends Storage { @Nullable BuildWorld getBuildWorld(World world); + /** + * Gets the {@link BuildWorld} by the given {@link UUID}. + * + * @param uuid The build world's unique identifier + * @return The world object if one was found, {@code null} otherwise + */ + @Nullable + BuildWorld getBuildWorld(UUID uuid); + /** * Gets a list of all {@link BuildWorld}s. * diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java index 53d1fbb2..3323cf07 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java @@ -70,10 +70,7 @@ import de.eintosti.buildsystem.listener.WorldManipulateByAxiomListener; import de.eintosti.buildsystem.listener.WorldManipulateListener; import de.eintosti.buildsystem.navigator.ArmorStandManager; -import de.eintosti.buildsystem.navigator.inventory.ArchivedWorldsInventory; import de.eintosti.buildsystem.navigator.inventory.NavigatorInventory; -import de.eintosti.buildsystem.navigator.inventory.PrivateWorldsInventory; -import de.eintosti.buildsystem.navigator.inventory.PublicWorldsInventory; import de.eintosti.buildsystem.player.BlocksInventory; import de.eintosti.buildsystem.player.LogoutLocationImpl; import de.eintosti.buildsystem.player.PlayerServiceImpl; @@ -136,7 +133,6 @@ public class BuildSystemPlugin extends JavaPlugin { private SpawnManager spawnManager; private WorldServiceImpl worldService; - private ArchivedWorldsInventory archivedWorldsInventory; private BlocksInventory blocksInventory; private BuilderInventory builderInventory; private CreateInventory createInventory; @@ -145,12 +141,10 @@ public class BuildSystemPlugin extends JavaPlugin { private EditInventory editInventory; private GameRuleInventory gameRuleInventory; private NavigatorInventory navigatorInventory; - private PrivateWorldsInventory privateWorldsInventory; private SettingsInventory settingsInventory; private SetupInventory setupInventory; private SpeedInventory speedInventory; private StatusInventory statusInventory; - private PublicWorldsInventory publicWorldsInventory; private ConfigValues configValues; private CustomBlocks customBlocks; @@ -263,7 +257,6 @@ private void initClasses() { this.settingsManager = new SettingsManager(this); this.spawnManager = new SpawnManager(this); - this.archivedWorldsInventory = new ArchivedWorldsInventory(this); this.blocksInventory = new BlocksInventory(this); this.builderInventory = new BuilderInventory(this); this.createInventory = new CreateInventory(this); @@ -272,8 +265,6 @@ private void initClasses() { this.editInventory = new EditInventory(this); this.gameRuleInventory = new GameRuleInventory(this); this.navigatorInventory = new NavigatorInventory(this); - this.privateWorldsInventory = new PrivateWorldsInventory(this); - this.publicWorldsInventory = new PublicWorldsInventory(this); this.settingsInventory = new SettingsInventory(this); this.setupInventory = new SetupInventory(this); this.speedInventory = new SpeedInventory(this); @@ -506,10 +497,6 @@ public WorldServiceImpl getWorldService() { return worldService; } - public ArchivedWorldsInventory getArchivedWorldsInventory() { - return archivedWorldsInventory; - } - public BlocksInventory getBlocksInventory() { return blocksInventory; } @@ -542,14 +529,6 @@ public NavigatorInventory getNavigatorInventory() { return navigatorInventory; } - public PrivateWorldsInventory getPrivateWorldsInventory() { - return privateWorldsInventory; - } - - public PublicWorldsInventory getPublicWorldsInventory() { - return publicWorldsInventory; - } - public SettingsInventory getSettingsInventory() { return settingsInventory; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index 2ba0cb8a..efc1834e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -435,8 +435,8 @@ public static void createMessageFile() { addSpacer(sb, "# World Navigator"); setMessage(sb, "world_navigator_title", "&3» &8World Navigator"); setMessage(sb, "world_navigator_no_worlds", "&c&nNo worlds available"); - setMessage(sb, "world_navigator_create_world", "&bCreate World"); - setMessage(sb, "world_navigator_create_folder", "&bCreate Folder"); + setMessage(sb, "world_navigator_create_world", "&bCreate a public World"); + setMessage(sb, "world_navigator_create_folder", "&bCreate a Folder"); setMessage(sb, "world_item_title", "&3&l%world%"); setMessage(sb, "world_item_lore_normal", Arrays.asList( "&7Status&8: %status%", @@ -461,6 +461,7 @@ public static void createMessageFile() { "&8- &7&oLeft click&8: &7Teleport", "&8- &7&oRight click&8: &7Edit" )); + setMessage(sb, "folder_title", "&3» &8%folder%"); setMessage(sb, "folder_item_title", "&3&l%folder%"); setMessage(sb, "folder_item_lore", Arrays.asList( "&7Worlds&8: &b%worlds%" @@ -494,7 +495,7 @@ public static void createMessageFile() { addSpacer(sb, "# Private Worlds"); setMessage(sb, "private_title", "&3» &8Private Worlds"); setMessage(sb, "private_no_worlds", "&c&nNo worlds available"); - setMessage(sb, "private_create_world", "&bCreate a private world"); + setMessage(sb, "private_create_world", "&bCreate a private World"); addSpacer(sb, ""); addSpacer(sb, "# Setup"); setMessage(sb, "setup_title", "&3» &8Setup"); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BackCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BackCommand.java index 915853b6..9d652cdf 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BackCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BackCommand.java @@ -22,9 +22,6 @@ import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.player.BuildPlayer; import de.eintosti.buildsystem.api.storage.PlayerStorage; -import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.player.BuildPlayerImpl; -import de.eintosti.buildsystem.player.PlayerServiceImpl; import io.papermc.lib.PaperLib; import java.util.UUID; import org.bukkit.Location; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SpawnCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SpawnCommand.java index fdceb433..1caae337 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SpawnCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SpawnCommand.java @@ -20,9 +20,8 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.world.BuildWorldImpl; -import de.eintosti.buildsystem.world.SpawnManager; import de.eintosti.buildsystem.storage.WorldStorageImpl; +import de.eintosti.buildsystem.world.SpawnManager; import java.util.AbstractMap; import java.util.Locale; import org.bukkit.Location; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/event/EventDispatcher.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/event/EventDispatcher.java index 8d9d3b7c..1982e38e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/event/EventDispatcher.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/event/EventDispatcher.java @@ -19,8 +19,8 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.event.world.BuildWorldManipulationEvent; -import de.eintosti.buildsystem.world.BuildWorldImpl; import de.eintosti.buildsystem.storage.WorldStorageImpl; +import de.eintosti.buildsystem.world.BuildWorldImpl; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/event/world/BuildWorldManipulationEvent.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/event/world/BuildWorldManipulationEvent.java index 491fa166..15c11dc0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/event/world/BuildWorldManipulationEvent.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/event/world/BuildWorldManipulationEvent.java @@ -19,7 +19,6 @@ import de.eintosti.buildsystem.api.event.world.BuildWorldEvent; import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.world.BuildWorldImpl; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.jetbrains.annotations.NotNull; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/expansion/luckperms/calculators/RoleCalculator.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/expansion/luckperms/calculators/RoleCalculator.java index 7279fe00..547908f6 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/expansion/luckperms/calculators/RoleCalculator.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/expansion/luckperms/calculators/RoleCalculator.java @@ -20,9 +20,8 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.builder.Builders; -import de.eintosti.buildsystem.world.BuildWorldImpl; -import de.eintosti.buildsystem.world.builder.BuildersImpl; import de.eintosti.buildsystem.storage.WorldStorageImpl; +import de.eintosti.buildsystem.world.BuildWorldImpl; import java.util.Locale; import net.luckperms.api.context.ContextCalculator; import net.luckperms.api.context.ContextConsumer; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/AsyncPlayerPreLoginListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/AsyncPlayerPreLoginListener.java index bdaa753b..c1402f11 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/AsyncPlayerPreLoginListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/AsyncPlayerPreLoginListener.java @@ -23,13 +23,8 @@ import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.storage.PlayerStorage; import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.player.BuildPlayerImpl; -import de.eintosti.buildsystem.player.LogoutLocationImpl; -import de.eintosti.buildsystem.player.PlayerServiceImpl; -import de.eintosti.buildsystem.player.settings.SettingsImpl; -import de.eintosti.buildsystem.world.BuildWorldImpl; -import de.eintosti.buildsystem.world.SpawnManager; import de.eintosti.buildsystem.storage.WorldStorageImpl; +import de.eintosti.buildsystem.world.SpawnManager; import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BlockPhysicsListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BlockPhysicsListener.java index c8bc40d8..8cd0dd52 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BlockPhysicsListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BlockPhysicsListener.java @@ -20,9 +20,8 @@ import com.cryptomorin.xseries.XMaterial; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.version.util.DirectionUtil; -import de.eintosti.buildsystem.world.BuildWorldImpl; import de.eintosti.buildsystem.storage.WorldStorageImpl; +import de.eintosti.buildsystem.version.util.DirectionUtil; import java.util.List; import org.bukkit.World; import org.bukkit.block.Block; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BlockPlaceListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BlockPlaceListener.java index b5c6b78a..977405a4 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BlockPlaceListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BlockPlaceListener.java @@ -21,9 +21,8 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.version.customblocks.CustomBlock; -import de.eintosti.buildsystem.world.BuildWorldImpl; import de.eintosti.buildsystem.storage.WorldStorageImpl; +import de.eintosti.buildsystem.version.customblocks.CustomBlock; import java.util.HashMap; import java.util.Map; import org.bukkit.entity.Player; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BuildWorldResetUnloadListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BuildWorldResetUnloadListener.java index 18ebf1be..56de1b1a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BuildWorldResetUnloadListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/BuildWorldResetUnloadListener.java @@ -19,7 +19,6 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.world.BuildWorldImpl; import de.eintosti.buildsystem.storage.WorldStorageImpl; import org.bukkit.World; import org.bukkit.event.EventHandler; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/EntityDamageListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/EntityDamageListener.java index 962a88f3..6bafd487 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/EntityDamageListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/EntityDamageListener.java @@ -21,7 +21,6 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.config.ConfigValues; -import de.eintosti.buildsystem.world.BuildWorldImpl; import io.papermc.lib.PaperLib; import org.bukkit.Location; import org.bukkit.entity.EntityType; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/EntitySpawnListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/EntitySpawnListener.java index 6e986334..f5869da8 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/EntitySpawnListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/EntitySpawnListener.java @@ -19,9 +19,8 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.util.EntityAIManager; -import de.eintosti.buildsystem.world.BuildWorldImpl; import de.eintosti.buildsystem.storage.WorldStorageImpl; +import de.eintosti.buildsystem.util.EntityAIManager; import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java index 27f6b99b..34bfa851 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java @@ -32,6 +32,9 @@ import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.config.ConfigValues; import de.eintosti.buildsystem.navigator.ArmorStandManager; +import de.eintosti.buildsystem.navigator.inventory.ArchivedWorldsInventory; +import de.eintosti.buildsystem.navigator.inventory.PrivateWorldsInventory; +import de.eintosti.buildsystem.navigator.inventory.PublicWorldsInventory; import de.eintosti.buildsystem.player.PlayerServiceImpl; import de.eintosti.buildsystem.player.settings.SettingsManager; import de.eintosti.buildsystem.util.InventoryUtils; @@ -198,15 +201,15 @@ public void manageNewNavigatorInteraction(PlayerInteractAtEntityEvent event) { switch (inventoryType) { case NAVIGATOR: XSound.BLOCK_CHEST_OPEN.play(player); - plugin.getPublicWorldsInventory().openInventory(player); + new PublicWorldsInventory(plugin, player).openInventory(); break; case ARCHIVE: XSound.BLOCK_CHEST_OPEN.play(player); - plugin.getArchivedWorldsInventory().openInventory(player); + new ArchivedWorldsInventory(plugin, player).openInventory(); break; case PRIVATE: XSound.BLOCK_CHEST_OPEN.play(player); - plugin.getPrivateWorldsInventory().openInventory(player); + new PrivateWorldsInventory(plugin, player).openInventory(); break; } } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java index 061d73e0..55838c60 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java @@ -19,25 +19,30 @@ import com.google.common.collect.Sets; import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; import de.eintosti.buildsystem.util.InventoryUtils; -import java.util.Set; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; -public class ArchivedWorldsInventory extends FilteredWorldsInventory { +public class ArchivedWorldsInventory extends DisplayablesInventory { - private static final Visibility VISIBILITY = Visibility.IGNORE; - private static final Set VALID_STATUS = Sets.newHashSet(BuildWorldStatus.ARCHIVE); - - public ArchivedWorldsInventory(BuildSystemPlugin plugin) { - super(plugin, "archive_title", "archive_no_worlds", VISIBILITY, VALID_STATUS); + public ArchivedWorldsInventory(BuildSystemPlugin plugin, Player player) { + super( + plugin, + player, + Messages.getString("archive_title", player), + Messages.getString("archive_no_worlds", player), + Visibility.IGNORE, + Sets.newHashSet(BuildWorldStatus.ARCHIVE) + ); } @Override - protected Inventory createInventory(Player player) { - Inventory inventory = super.createInventory(player); + protected @NotNull Inventory createBaseInventoryPage() { + Inventory inventory = super.createBaseInventoryPage(); InventoryUtils.addGlassPane(player, inventory, 49); return inventory; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java new file mode 100644 index 00000000..cc310fd2 --- /dev/null +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018-2025, Thomas Meaney + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.eintosti.buildsystem.navigator.inventory; + +import com.cryptomorin.xseries.profiles.objects.Profileable; +import com.google.common.collect.Sets; +import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; +import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.player.PlayerServiceImpl; +import de.eintosti.buildsystem.util.InventoryUtils; +import java.util.Set; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An abstract inventory class extending {@link DisplayablesInventory} that provides common functionality for inventories which allow the creation of new {@link BuildWorld}s and + * {@link Folder}s. + */ +public abstract class CreatableWorldsInventory extends DisplayablesInventory { + + private static final Set VALID_STATUSES = Sets.newHashSet( + BuildWorldStatus.NOT_STARTED, + BuildWorldStatus.IN_PROGRESS, + BuildWorldStatus.ALMOST_FINISHED, + BuildWorldStatus.FINISHED + ); + + private static final String CREATE_WORLD_PROFILE = "3edd20be93520949e6ce789dc4f43efaeb28c717ee6bfcbbe02780142f716"; + private static final String CREATE_FOLDER_PROFILE = "69b861aabb316c4ed73b4e5428305782e735565ba2a053912e1efd834fa5a6f"; + + private final PlayerServiceImpl playerService; + + /** + * Constructs a new {@link CreatableWorldsInventory}. + * + * @param plugin The plugin instance + * @param player The player for whom this inventory is created + * @param inventoryTitle The inventory's title + * @param noWorldsMessage The "no worlds" message + * @param requiredVisibility The required {@link Visibility} for worlds to be displayed in this inventory + */ + protected CreatableWorldsInventory( + @NotNull BuildSystemPlugin plugin, + @NotNull Player player, + @NotNull String inventoryTitle, + @Nullable String noWorldsMessage, + @NotNull Visibility requiredVisibility + ) { + super(plugin, player, inventoryTitle, noWorldsMessage, requiredVisibility, VALID_STATUSES); + this.playerService = plugin.getPlayerService(); + } + + /** + * Overrides the base method to include the "create world" and "create folder" items in the inventory page layout. + * + * @return A newly created {@link Inventory} page with common base items and creation options. + */ + @Override + protected @NotNull Inventory createBaseInventoryPage() { + Inventory inventory = super.createBaseInventoryPage(); + addWorldCreateItem(inventory, player); + addFolderCreateItem(inventory, player); + return inventory; + } + + private void addWorldCreateItem(Inventory inventory, Player player) { + if (!playerService.canCreateWorld(player, requiredVisibility)) { + return; + } + + if (player.hasPermission(getWorldCreationPermission())) { + inventory.setItem(48, InventoryUtils.createSkull(Messages.getString(getWorldCreationItemTitleKey(), player), Profileable.detect(CREATE_WORLD_PROFILE))); + } else { + InventoryUtils.addGlassPane(player, inventory, 48); + } + } + + private void addFolderCreateItem(Inventory inventory, Player player) { + if (player.hasPermission("buildsystem.create.folder")) { + inventory.setItem(50, InventoryUtils.createSkull(Messages.getString("world_navigator_create_folder", player), Profileable.detect(CREATE_FOLDER_PROFILE))); + } else { + InventoryUtils.addGlassPane(player, inventory, 50); + } + } + + /** + * Gets the specific permission string required for a player to create a {@link BuildWorld} of the type managed by this inventory (e.g., {@code buildsystem.create.private}, + * {@code buildsystem.create.public}). + * + * @return The permission string for creating a world of this inventory's type. + */ + protected abstract @NotNull String getWorldCreationPermission(); + + /** + * Gets the message key the title of the "create world" item. + * + * @return The message key for the "create world" item's title. + */ + protected abstract @NotNull String getWorldCreationItemTitleKey(); +} \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java new file mode 100644 index 00000000..3f8b21a1 --- /dev/null +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -0,0 +1,597 @@ +package de.eintosti.buildsystem.navigator.inventory; + +import com.cryptomorin.xseries.XMaterial; +import com.cryptomorin.xseries.XSound; +import com.cryptomorin.xseries.messages.Titles; +import com.cryptomorin.xseries.profiles.objects.Profileable; +import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; +import de.eintosti.buildsystem.api.navigator.settings.WorldFilter; +import de.eintosti.buildsystem.api.navigator.settings.WorldSort; +import de.eintosti.buildsystem.api.player.settings.Settings; +import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; +import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.data.WorldData; +import de.eintosti.buildsystem.api.world.display.Displayable; +import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.player.PlayerServiceImpl; +import de.eintosti.buildsystem.player.settings.SettingsManager; +import de.eintosti.buildsystem.storage.FolderStorageImpl; +import de.eintosti.buildsystem.storage.WorldStorageImpl; +import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; +import de.eintosti.buildsystem.util.InventoryUtils; +import de.eintosti.buildsystem.util.PaginatedInventory; +import de.eintosti.buildsystem.util.PlayerChatInput; +import de.eintosti.buildsystem.util.StringUtils; +import de.eintosti.buildsystem.world.WorldServiceImpl; +import de.eintosti.buildsystem.world.creation.CreateInventory; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An abstract inventory class for displaying {@link Displayable} objects such as {@link Folder}s and {@link BuildWorld}s. This class is designed to be instantiated once per player + * to manage their specific inventory view. + */ +public abstract class DisplayablesInventory extends PaginatedInventory implements Listener { + + private static final int MAX_WORLDS_PER_PAGE = 36; + private static final int FIRST_WORD_SLOT = 9; + private static final int LAST_WORLD_SLOT = 44; + + private static final String PREVIOUS_PAGE_SKULL_PROFILE = "86971dd881dbaf4fd6bcaa93614493c612f869641ed59d1c9363a3666a5fa6"; + private static final String NEXT_PAGE_SKULL_PROFILE = "f32ca66056b72863e98f7f32bd7d94c7a0d796af691c9ac3a9136331352288f9"; + private static final String NO_WORLDS_SKULL_PROFILE = "2e3f50ba62cbda3ecf5479b62fedebd61d76589771cc19286bf2745cd71e47c6"; + + protected final BuildSystemPlugin plugin; + protected final PlayerServiceImpl playerService; + protected final SettingsManager settingsManager; + protected final FolderStorageImpl folderStorage; + protected final WorldStorageImpl worldStorage; + + protected final Player player; + protected final Visibility requiredVisibility; + private final Set validStatuses; + private final String inventoryTitle; + private final String noWorldsMessage; + + private List cachedDisplayables; + private Inventory[] generatedInventories; + + /** + * Constructs a new {@link DisplayablesInventory} for a specific player. + * + * @param plugin The plugin instance. + * @param player The player for whom this inventory is created. + * @param inventoryTitle The inventory's title. + * @param noWorldsMessage The "no worlds" message. + * @param requiredVisibility The required visibility for worlds to be displayed. + * @param validStatuses The set of valid statuses for worlds to be displayed. + */ + protected DisplayablesInventory( + @NotNull BuildSystemPlugin plugin, + @NotNull Player player, + @NotNull String inventoryTitle, + @Nullable String noWorldsMessage, + @NotNull Visibility requiredVisibility, + @NotNull Set<@NotNull BuildWorldStatus> validStatuses + ) { + this.plugin = plugin; + this.player = player; + this.inventoryTitle = inventoryTitle; + this.noWorldsMessage = noWorldsMessage; + this.requiredVisibility = requiredVisibility; + this.validStatuses = validStatuses; + + this.playerService = plugin.getPlayerService(); + this.settingsManager = plugin.getSettingsManager(); + WorldServiceImpl worldService = plugin.getWorldService(); + this.folderStorage = worldService.getFolderStorage(); + this.worldStorage = worldService.getWorldStorage(); + } + + /** + * Opens the inventory for the associated player. + */ + public void openInventory() { + initializeInventories(); + player.openInventory(generatedInventories[getInvIndex(player)]); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + /** + * Creates a single inventory page with common navigation and setting items. + * + * @return A new inventory instance. + */ + protected @NotNull Inventory createBaseInventoryPage() { + Inventory inventory = Bukkit.createInventory(player, 54, this.inventoryTitle); + InventoryUtils.fillWithGlass(inventory, player); + + addWorldSortItem(inventory); + addWorldFilterItem(inventory); + + inventory.setItem(52, InventoryUtils.createSkull(Messages.getString("gui_previous_page", player), Profileable.detect(PREVIOUS_PAGE_SKULL_PROFILE))); + inventory.setItem(53, InventoryUtils.createSkull(Messages.getString("gui_next_page", player), Profileable.detect(NEXT_PAGE_SKULL_PROFILE))); + + return inventory; + } + + /** + * Initializes all inventory pages based on the current player settings and world data. + */ + private void initializeInventories() { + this.cachedDisplayables = collectDisplayables(); + int numDisplayableObjects = this.cachedDisplayables.size(); + + int numPages = numDisplayableObjects == 0 ? 1 : (int) Math.ceil((double) numDisplayableObjects / MAX_WORLDS_PER_PAGE); + this.generatedInventories = new Inventory[numPages]; + + for (int pageIndex = 0; pageIndex < numPages; pageIndex++) { + Inventory currentPage = createBaseInventoryPage(); + this.generatedInventories[pageIndex] = currentPage; + + if (numDisplayableObjects == 0 && this.noWorldsMessage != null) { + currentPage.setItem(22, InventoryUtils.createSkull(this.noWorldsMessage, Profileable.detect(NO_WORLDS_SKULL_PROFILE))); + continue; + } + + int startItemIndex = pageIndex * MAX_WORLDS_PER_PAGE; + int endItemIndex = Math.min(startItemIndex + MAX_WORLDS_PER_PAGE, numDisplayableObjects); + + int currentSlot = FIRST_WORD_SLOT; + for (int i = startItemIndex; i < endItemIndex; i++) { + if (currentSlot > LAST_WORLD_SLOT) { + break; + } + currentPage.setItem(currentSlot++, this.cachedDisplayables.get(i).asItemStack(player)); + } + } + } + + /** + * Collects all {@link Displayable} items (i.e. {@link Folder}s and {@link BuildWorld}s) that should be shown to the player. + *

+ * The method applies the following logic: + *

    + *
  • Fetches all {@link BuildWorld}s visible to the player based on world access rights and filter settings.
  • + *
  • Sorts the filtered worlds according to the player's selected sort order.
  • + *
  • If a world is assigned to a {@link Folder}, it is excluded from display and the folder is shown instead.
  • + *
  • If a world is not assigned to any folder, it is displayed directly.
  • + *
  • All folders are listed first, followed by all standalone worlds.
  • + *
+ * This ensures that: + *
    + *
  • Worlds are never shown twice (both directly and via their folder).
  • + *
  • Folder grouping takes display priority over individual worlds.
  • + *
+ * + * @return A list of {@link Displayable} items to be presented in the UI, sorted with folders first. + */ + protected @NotNull List collectDisplayables() { + Settings settings = settingsManager.getSettings(player); + WorldDisplay worldDisplay = settings.getWorldDisplay(); + + Collection filteredWorlds = filterWorlds(collectWorlds(), worldDisplay); + Collection folders = collectFolders(filteredWorlds); + List standaloneWorlds = filteredWorlds.stream() + .filter(world -> !folderStorage.isAssignedToAnyFolder(world)) + .collect(Collectors.toList()); + + List displayables = new ArrayList<>(); + displayables.addAll(folders); + displayables.addAll(standaloneWorlds); + return displayables; + } + + /** + * Collects all potential {@link BuildWorld}s that be displayed to the player before filtering them based on visibility and status. + */ + protected Collection collectWorlds() { + return worldStorage.getBuildWorlds(); + } + + /** + * Filters the given collection of {@link BuildWorld}s based on their visibility and status, as well as the player's world display settings. + * + * @param buildWorlds The collection of build worlds to filter + * @param worldDisplay The world display settings for the player + * @return A collection of filtered and sorted {@link BuildWorld}s that are valid for display to the player + */ + protected Collection filterWorlds(Collection buildWorlds, WorldDisplay worldDisplay) { + return buildWorlds.stream() + .filter(this::isWorldValidForDisplay) + .filter(worldDisplay.getWorldFilter().apply()) + .sorted(createDisplayOrderComparator(worldDisplay.getWorldSort())) + .collect(Collectors.toList()); + } + + /** + * Collects all {@link Folder}s that contain a {@link BuildWorld} from the given collection. + * + * @param buildWorlds The collection of build worlds to check for folders + * @return A collection of folders that contain a build world + */ + private Collection collectFolders(Collection buildWorlds) { + return buildWorlds.stream() + .map(this.folderStorage::getAssignedFolder) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + } + + /** + * Checks if a given {@link BuildWorld} is valid for display based on visibility, status, and player permissions. + * + * @param buildWorld The world to check. + * @return {@code true} if the world should be displayed, {@link false} otherwise + */ + private boolean isWorldValidForDisplay(@NotNull BuildWorld buildWorld) { + WorldData worldData = buildWorld.getData(); + if (!this.worldStorage.isCorrectVisibility(worldData.privateWorld().get(), this.requiredVisibility)) { + return false; + } + + if (!this.validStatuses.contains(worldData.status().get())) { + return false; + } + + if (!buildWorld.getPermissions().canEnter(this.player)) { + return false; + } + + return Bukkit.getWorld(buildWorld.getName()) != null || !buildWorld.isLoaded(); + } + + /** + * Creates a comparator for sorting {@link BuildWorld}s based on the specified {@link WorldSort} order. + * + * @param worldSort The desired sorting order. + * @return The comparator + */ + private @NotNull Comparator createDisplayOrderComparator(@NotNull WorldSort worldSort) { + Comparator comparator; + switch (worldSort) { + case OLDEST_FIRST: + comparator = Comparator.comparingLong(BuildWorld::getCreationDate); + break; + case NEWEST_FIRST: + comparator = Comparator.comparingLong(BuildWorld::getCreationDate).reversed(); + break; + case PROJECT_A_TO_Z: + comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)); + break; + case PROJECT_Z_TO_A: + comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)).reversed(); + break; + case STATUS_NOT_STARTED: + comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()); + break; + case STATUS_FINISHED: + comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()).reversed(); + break; + case NAME_Z_TO_A: + comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)).reversed(); + break; + default: // NAME_A_TO_Z + comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)); + break; + } + return comparator; + } + + /** + * Adds the world sorting item to the inventory. + * + * @param inventory The inventory to add the item to + */ + private void addWorldSortItem(@NotNull Inventory inventory) { + Settings settings = settingsManager.getSettings(player); + WorldSort worldSort = settings.getWorldDisplay().getWorldSort(); + inventory.setItem(45, InventoryUtils.createItem(XMaterial.BOOK, Messages.getString("world_sort_title", player), Messages.getString(worldSort.getMessageKey(), player))); + } + + /** + * Adds the world filter item to the inventory. + * + * @param inventory The inventory to add the item to + */ + private void addWorldFilterItem(@NotNull Inventory inventory) { + Settings settings = settingsManager.getSettings(player); + WorldFilter worldFilter = settings.getWorldDisplay().getWorldFilter(); + + List lore = new ArrayList<>(); + lore.add(Messages.getString(worldFilter.getMode().getLoreKey(), player, new AbstractMap.SimpleEntry<>("%text%", worldFilter.getText()))); + lore.addAll(Messages.getStringList("world_filter_lore", player)); + + inventory.setItem(46, InventoryUtils.createItem(XMaterial.HOPPER, Messages.getString("world_filter_title", player), lore)); + } + + @EventHandler + public void onInventoryClick(@NotNull InventoryClickEvent event) { + if (!InventoryUtils.isValidClick(event, this.inventoryTitle)) { + return; + } + + ItemStack clickedItem = event.getCurrentItem(); + if (clickedItem == null) { + return; + } + + Settings settings = settingsManager.getSettings(player); + WorldDisplay worldDisplay = settings.getWorldDisplay(); + + switch (event.getSlot()) { + case 45: // Sort + Function newSortFunction = event.isLeftClick() ? this::getNextSort : this::getPreviousSort; + WorldSort newSort = newSortFunction.apply(worldDisplay.getWorldSort()); + worldDisplay.setWorldSort(newSort); + updateAndReopenInventory(); + return; + case 46: // Filter + handleFilterClick(event, worldDisplay); + return; + case 48: // Create world + if (clickedItem.getType() == XMaterial.PLAYER_HEAD.get()) { + XSound.ENTITY_CHICKEN_EGG.play(player); + plugin.getCreateInventory().openInventory(player, CreateInventory.Page.PREDEFINED, requiredVisibility); + return; + } + break; + case 50: // Create folder + if (clickedItem.getType() == XMaterial.PLAYER_HEAD.get()) { + XSound.ENTITY_CHICKEN_EGG.play(player); + player.closeInventory(); // Close to allow chat input + new PlayerChatInput(plugin, player, "enter_folder_name", input -> { + Folder folder = folderStorage.createFolder(input.trim()); + Messages.sendMessage(player, "worlds_folder_created", new AbstractMap.SimpleEntry<>("%folder%", folder.getName())); + // Reopen inventory after chat input is done + openInventory(); + }); + return; + } + break; + case 52: // Previous page + if (decrementInv(player, cachedDisplayables.size(), MAX_WORLDS_PER_PAGE)) { + player.openInventory(generatedInventories[getInvIndex(player)]); + } + return; + case 53: // Next page + if (incrementInv(player, cachedDisplayables.size(), MAX_WORLDS_PER_PAGE)) { + player.openInventory(generatedInventories[getInvIndex(player)]); + } + return; + } + + if (event.getSlot() >= FIRST_WORD_SLOT && event.getSlot() <= LAST_WORLD_SLOT) { + handleDisplayableItemClick(event, clickedItem); + } else if (event.getSlot() >= 45 && event.getSlot() <= 53 && clickedItem.getType() != XMaterial.PLAYER_HEAD.get()) { + XSound.BLOCK_CHEST_OPEN.play(player); + returnToPreviousInventory(); + } + } + + /** + * Sends the player back to the previous inventory. + */ + protected void returnToPreviousInventory() { + plugin.getNavigatorInventory().openInventory(player); + } + + /** + * Handles clicks on the filter item, managing mode changes and text input. + * + * @param event The InventoryClickEvent. + * @param worldDisplay The player's WorldDisplay settings. + */ + private void handleFilterClick(@NotNull InventoryClickEvent event, @NotNull WorldDisplay worldDisplay) { + WorldFilter worldFilter = worldDisplay.getWorldFilter(); + WorldFilter.Mode currentMode = worldFilter.getMode(); + + if (event.isShiftClick()) { + worldFilter.setMode(WorldFilter.Mode.NONE); + worldFilter.setText(""); + } else if (event.isLeftClick()) { + player.closeInventory(); + new PlayerChatInput(plugin, player, "world_filter_title", input -> { + worldFilter.setText(input.replace("\"", "")); + openInventory(); + }); + return; + } else if (event.isRightClick()) { + worldFilter.setMode(currentMode.getNext()); + } + + updateAndReopenInventory(); + } + + /** + * Handles clicks on {@link Displayable} items (worlds or folders) within the inventory. + * + * @param event The click event. + * @param clickedItem The item that was clicked. + */ + private void handleDisplayableItemClick(@NotNull InventoryClickEvent event, @NotNull ItemStack clickedItem) { + ItemMeta itemMeta = clickedItem.getItemMeta(); + if (itemMeta == null || !itemMeta.hasDisplayName()) { + return; + } + + String displayName = itemMeta.getDisplayName(); + if (event.getSlot() == 22 && + (displayName.equals(Messages.getString("world_navigator_no_worlds", player)) + || displayName.equals(Messages.getString("archive_no_worlds", player)) + || displayName.equals(Messages.getString("private_no_worlds", player)))) { + return; + } + + BuildWorld buildWorld = parseWorld(displayName); + if (buildWorld != null) { + manageWorldItemClick(event, buildWorld); + return; + } + + Folder folder = parseFolder(displayName); + if (folder != null) { + new FolderContentInventory(plugin, player, folder, this, requiredVisibility, validStatuses).openInventory(); + return; + } + + plugin.getLogger().warning("Clicked item does not represent a valid world or folder: " + displayName); + } + + /** + * Updates the inventory content and reopens it for the player. This is typically called after a setting (sort, filter) has changed. + */ + private void updateAndReopenInventory() { + setInvIndex(player, 0); + initializeInventories(); + player.openInventory(generatedInventories[getInvIndex(player)]); + } + + @EventHandler + public void onInventoryClose(@NotNull InventoryCloseEvent event) { + if (!event.getPlayer().equals(player)) { + return; + } + + for (Inventory inv : generatedInventories) { + if (event.getInventory().equals(inv)) { + HandlerList.unregisterAll(this); + return; + } + } + } + + /** + * Gets the next {@link WorldSort} order in the cycle. + * + * @param currentSort The current sort + * @return The next sort in the sequence + */ + private WorldSort getNextSort(WorldSort currentSort) { + switch (currentSort) { + case NEWEST_FIRST: + return WorldSort.OLDEST_FIRST; + case OLDEST_FIRST: + return WorldSort.NAME_A_TO_Z; + case PROJECT_A_TO_Z: + return WorldSort.PROJECT_Z_TO_A; + case PROJECT_Z_TO_A: + return WorldSort.STATUS_NOT_STARTED; + case STATUS_NOT_STARTED: + return WorldSort.STATUS_FINISHED; + case STATUS_FINISHED: + return WorldSort.NEWEST_FIRST; + case NAME_Z_TO_A: + return WorldSort.PROJECT_A_TO_Z; + default: // NAME_A_TO_Z + return WorldSort.NAME_Z_TO_A; + } + } + + /** + * Gets the previous {@link WorldSort} order in the cycle. + * + * @param currentSort The current sort + * @return The previous sort in the sequence. + */ + public WorldSort getPreviousSort(WorldSort currentSort) { + switch (currentSort) { + case NEWEST_FIRST: + return WorldSort.STATUS_FINISHED; + case OLDEST_FIRST: + return WorldSort.NEWEST_FIRST; + case PROJECT_A_TO_Z: + return WorldSort.NAME_Z_TO_A; + case PROJECT_Z_TO_A: + return WorldSort.PROJECT_A_TO_Z; + case STATUS_NOT_STARTED: + return WorldSort.PROJECT_Z_TO_A; + case STATUS_FINISHED: + return WorldSort.STATUS_NOT_STARTED; + case NAME_Z_TO_A: + return WorldSort.NAME_A_TO_Z; + default: // NAME_A_TO_Z + return WorldSort.OLDEST_FIRST; + } + } + + /** + * Parses the {@link BuildWorld} from an item's display name. + * + * @param displayName The display name of the item. + * @return The extracted world. + */ + private @Nullable BuildWorld parseWorld(@NotNull String displayName) { + String template = Messages.getString("world_item_title", player, new AbstractMap.SimpleEntry<>("%world%", "")); + String worldName = StringUtils.difference(template, displayName); + return worldStorage.getBuildWorld(worldName); + } + + /** + * Parses the {@link Folder} from an item's display name. + * + * @param displayName The display name of the item. + * @return The extracted folder. + */ + private @Nullable Folder parseFolder(@NotNull String displayName) { + String template = Messages.getString("folder_item_title", player, new AbstractMap.SimpleEntry<>("%folder%", "")); + String folderName = StringUtils.difference(template, displayName); + return folderStorage.getFolder(folderName); + } + + /** + * Manages the click action for an item representing a {@link BuildWorld}. + * + * @param event The InventoryClickEvent. + * @param buildWorld The BuildWorld associated with the clicked item. + */ + private void manageWorldItemClick(@NotNull InventoryClickEvent event, @NotNull BuildWorld buildWorld) { + if (event.isLeftClick() || buildWorld.getPermissions().canPerformCommand(player, WorldsTabComplete.WorldsArgument.EDIT.getPermission())) { + performNonEditClick(buildWorld); + return; + } + + if (buildWorld.isLoaded()) { + playerService.getPlayerStorage().getBuildPlayer(player).setCachedWorld(buildWorld); + XSound.BLOCK_CHEST_OPEN.play(player); + plugin.getEditInventory().openInventory(player, buildWorld); + } else { + player.closeInventory(); + XSound.ENTITY_ZOMBIE_BREAK_WOODEN_DOOR.play(player); + Titles.sendTitle(player, 5, 70, 20, " ", Messages.getString("world_not_loaded", player)); + } + } + + /** + * Performs a non-edit action for a {@link BuildWorld} item click (typically teleportation). + * + * @param buildWorld The BuildWorld to act upon. + */ + private void performNonEditClick(@NotNull BuildWorld buildWorld) { + playerService.closeNavigator(player); + buildWorld.getTeleporter().teleport(player); + } +} \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FilteredWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FilteredWorldsInventory.java deleted file mode 100644 index fc5309c0..00000000 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FilteredWorldsInventory.java +++ /dev/null @@ -1,528 +0,0 @@ -/* - * Copyright (c) 2018-2025, Thomas Meaney - * Copyright (c) contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.eintosti.buildsystem.navigator.inventory; - -import com.cryptomorin.xseries.XMaterial; -import com.cryptomorin.xseries.XSound; -import com.cryptomorin.xseries.messages.Titles; -import com.cryptomorin.xseries.profiles.objects.Profileable; -import de.eintosti.buildsystem.BuildSystemPlugin; -import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; -import de.eintosti.buildsystem.api.navigator.settings.WorldFilter; -import de.eintosti.buildsystem.api.navigator.settings.WorldSort; -import de.eintosti.buildsystem.api.player.settings.Settings; -import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; -import de.eintosti.buildsystem.api.world.data.Visibility; -import de.eintosti.buildsystem.api.world.data.WorldData; -import de.eintosti.buildsystem.api.world.display.Displayable; -import de.eintosti.buildsystem.api.world.display.Folder; -import de.eintosti.buildsystem.player.PlayerServiceImpl; -import de.eintosti.buildsystem.player.settings.SettingsManager; -import de.eintosti.buildsystem.storage.FolderStorageImpl; -import de.eintosti.buildsystem.storage.WorldStorageImpl; -import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; -import de.eintosti.buildsystem.util.InventoryUtils; -import de.eintosti.buildsystem.util.PaginatedInventory; -import de.eintosti.buildsystem.util.PlayerChatInput; -import de.eintosti.buildsystem.util.StringUtils; -import de.eintosti.buildsystem.world.WorldServiceImpl; -import de.eintosti.buildsystem.world.creation.CreateInventory; -import de.eintosti.buildsystem.world.modification.EditInventory; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -public abstract class FilteredWorldsInventory extends PaginatedInventory implements Listener { - - private static final int MAX_WORLDS = 36; - - private final BuildSystemPlugin plugin; - private final PlayerServiceImpl playerService; - private final SettingsManager settingsManager; - private final FolderStorageImpl folderStorage; - private final WorldStorageImpl worldStorage; - - private final String inventoryName; - private final String noWorldsText; - private final Visibility visibility; - private final Set validStatus; - - public FilteredWorldsInventory(BuildSystemPlugin plugin, String inventoryName, String noWorldsText, Visibility visibility, Set validStatus) { - this.plugin = plugin; - this.playerService = plugin.getPlayerService(); - this.settingsManager = plugin.getSettingsManager(); - WorldServiceImpl worldService = plugin.getWorldService(); - this.folderStorage = worldService.getFolderStorage(); - this.worldStorage = worldService.getWorldStorage(); - - this.inventoryName = inventoryName; - this.noWorldsText = noWorldsText; - this.visibility = visibility; - this.validStatus = validStatus; - - plugin.getServer().getPluginManager().registerEvents(this, plugin); - } - - protected Inventory createInventory(Player player) { - Inventory inventory = Bukkit.createInventory(null, 54, Messages.getString(inventoryName, player)); - InventoryUtils.fillWithGlass(inventory, player); - - addWorldSortItem(inventory, player); - addWorldFilterItem(inventory, player); - - inventory.setItem(52, InventoryUtils.createSkull(Messages.getString("gui_previous_page", player), Profileable.detect("86971dd881dbaf4fd6bcaa93614493c612f869641ed59d1c9363a3666a5fa6"))); - inventory.setItem(53, InventoryUtils.createSkull(Messages.getString("gui_next_page", player), Profileable.detect("f32ca66056b72863e98f7f32bd7d94c7a0d796af691c9ac3a9136331352288f9"))); - - return inventory; - } - - /** - * Gets the number of {@link Displayable} objects that are to be displayed in the inventory. - * - * @param player The player to show the inventory to - * @return The number of displayable objects - */ - private int calculateNumObjects(Player player) { - int numFolders = folderStorage.getFolders().size(); - int numWorlds = (int) worldStorage.getBuildWorlds().stream() - .filter(buildWorld -> isValidWorld(player, buildWorld)) - .count(); - return numFolders + numWorlds; - } - - /** - * Gets the visibility of the worlds that will be displayed. - * - * @return The visibility of the worlds that will be displayed - */ - protected Visibility getVisibility() { - return visibility; - } - - public void openInventory(Player player) { - player.openInventory(getInventory(player)); - } - - private Inventory getInventory(Player player) { - addWorlds(player); - return inventories[getInvIndex(player)]; - } - - private void addWorlds(Player player) { - int numObjects = calculateNumObjects(player); - int numInventories = numObjects % MAX_WORLDS == 0 - ? Math.max(numObjects, 1) - : numObjects + 1; - - inventories = new Inventory[numInventories]; - Inventory inventory = createInventory(player); - - int index = 0; - inventories[index] = inventory; - if (numObjects == 0) { - inventory.setItem(22, InventoryUtils.createSkull(Messages.getString(noWorldsText, player), Profileable.detect("2e3f50ba62cbda3ecf5479b62fedebd61d76589771cc19286bf2745cd71e47c6"))); - return; - } - - int columnObject = 9, maxColumnObject = 44; - for (Displayable displayable : collectDisplayables(player)) { - inventory.setItem(columnObject++, displayable.asItemStack(player)); - - if (columnObject > maxColumnObject) { - columnObject = 9; - inventory = createInventory(player); - inventories[++index] = inventory; - } - } - } - - /** - * Collects all {@link Displayable} items (i.e. {@link Folder}s and {@link BuildWorld}s) that should be shown to the player. - *

- * The method applies the following logic: - *

    - *
  • Fetches all {@link BuildWorld}s visible to the player based on world access rights and filter settings.
  • - *
  • Sorts the filtered worlds according to the player's selected sort order.
  • - *
  • If a world is assigned to a {@link Folder}, it is excluded from display and the folder is shown instead.
  • - *
  • If a world is not assigned to any folder, it is displayed directly.
  • - *
  • All folders are listed first, followed by all standalone worlds.
  • - *
- * This ensures that: - *
    - *
  • Worlds are never shown twice (both directly and via their folder).
  • - *
  • Folder grouping takes display priority over individual worlds.
  • - *
- * - * @param player The player for whom to collect displayable items - * @return A list of {@link Displayable} items to be presented in the UI, sorted with folders first. - */ - private List collectDisplayables(Player player) { - Settings settings = settingsManager.getSettings(player); - WorldDisplay worldDisplay = settings.getWorldDisplay(); - - List buildWorlds = worldStorage.getBuildWorlds().stream() - .filter(buildWorld -> isValidWorld(player, buildWorld)) - .filter(worldDisplay.getWorldFilter().apply()) - .sorted(createDisplayOrderComparator(worldDisplay.getWorldSort())) - .collect(Collectors.toList()); - - List folders = collectFolders(buildWorlds); - List standaloneWorlds = buildWorlds.stream() - .filter(world -> !this.folderStorage.isAssignedToAnyFolder(world)) - .collect(Collectors.toList()); - - List displayables = new ArrayList<>(); - displayables.addAll(folders); - displayables.addAll(standaloneWorlds); - return displayables; - } - - /** - * Collects all {@link Folder}s that contain a {@link BuildWorld} from the given list. - * - * @param buildWorlds The list of build worlds to check for folders - * @return A list of folders that contain a build world - */ - private List collectFolders(List buildWorlds) { - return buildWorlds.stream() - .map(this.folderStorage::getAssignedFolder) - .filter(Objects::nonNull) - .distinct() - .collect(Collectors.toList()); - } - - /** - * Gets if the world should be shown to the player in the navigator. - *

- * The following logic is applied to determine the above: - *

    - *
  • Does the player have the admin bypass permission?
  • - *
  • Is the player the creator of the world?
  • - *
  • Has the player been added as a builder?
  • - *
  • Does the player have the permission to see the world?
  • - *
- * - * @param player The player who the world will be shown to - * @param buildWorld The world to show - * @return {@code true} if the world should be shown to the player in the navigator, {@code false} otherwise - */ - private boolean isValidWorld(Player player, BuildWorld buildWorld) { - WorldData worldData = buildWorld.getData(); - if (!worldStorage.isCorrectVisibility(worldData.privateWorld().get(), visibility)) { - return false; - } - - if (!validStatus.contains(worldData.status().get())) { - return false; - } - - if (!buildWorld.getPermissions().canEnter(player)) { - return false; - } - - return Bukkit.getWorld(buildWorld.getName()) != null || !buildWorld.isLoaded(); - } - - /** - * Creates a comparator which sorts the {@link BuildWorld}s in the order they are to be displayed. - * - * @param worldSort The order in which the worlds are to be sorted - * @return The comparator - */ - private Comparator createDisplayOrderComparator(WorldSort worldSort) { - Comparator comparator; - switch (worldSort) { - case OLDEST_FIRST: - comparator = Comparator.comparingLong(BuildWorld::getCreationDate); - break; - case NEWEST_FIRST: - comparator = Comparator.comparingLong(BuildWorld::getCreationDate).reversed(); - break; - case PROJECT_A_TO_Z: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)); - break; - case PROJECT_Z_TO_A: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)).reversed(); - break; - case STATUS_NOT_STARTED: - comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()); - break; - case STATUS_FINISHED: - comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()).reversed(); - break; - case NAME_Z_TO_A: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)).reversed(); - break; - default: // NAME_A_TO_Z - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)); - break; - } - return comparator; - } - - private void addWorldSortItem(Inventory inventory, Player player) { - Settings settings = settingsManager.getSettings(player); - WorldSort worldSort = settings.getWorldDisplay().getWorldSort(); - inventory.setItem(45, InventoryUtils.createItem(XMaterial.BOOK, Messages.getString("world_sort_title", player), Messages.getString(worldSort.getMessageKey(), player))); - } - - private void addWorldFilterItem(Inventory inventory, Player player) { - Settings settings = settingsManager.getSettings(player); - WorldFilter worldFilter = settings.getWorldDisplay().getWorldFilter(); - - List lore = new ArrayList<>(); - lore.add(Messages.getString(worldFilter.getMode().getLoreKey(), player, new AbstractMap.SimpleEntry<>("%text%", worldFilter.getText()))); - lore.addAll(Messages.getStringList("world_filter_lore", player)); - - inventory.setItem(46, InventoryUtils.createItem(XMaterial.HOPPER, Messages.getString("world_filter_title", player), lore)); - } - - @EventHandler - public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, inventoryName)) { - return; - } - - ItemStack itemStack = event.getCurrentItem(); - if (itemStack == null) { - return; - } - - Player player = (Player) event.getWhoClicked(); - Settings settings = settingsManager.getSettings(player); - WorldDisplay worldDisplay = settings.getWorldDisplay(); - - switch (event.getSlot()) { - case 45: - Function newSortFunction = event.isLeftClick() ? this::getNextSort : this::getPreviousSort; - WorldSort newSort = newSortFunction.apply(worldDisplay.getWorldSort()); - worldDisplay.setWorldSort(newSort); - openInventory(player); - return; - case 46: - WorldFilter worldFilter = worldDisplay.getWorldFilter(); - WorldFilter.Mode currentMode = worldFilter.getMode(); - if (event.isShiftClick()) { - worldFilter.setMode(WorldFilter.Mode.NONE); - worldFilter.setText(""); - setInvIndex(player, 0); - openInventory(player); - } else if (event.isLeftClick()) { - new PlayerChatInput(plugin, player, "world_filter_title", input -> { - worldFilter.setText(input.replace("\"", "")); - setInvIndex(player, 0); - openInventory(player); - }); - } else if (event.isRightClick()) { - worldFilter.setMode(currentMode.getNext()); - setInvIndex(player, 0); - openInventory(player); - } - return; - case 49: - if (itemStack.getType() == XMaterial.PLAYER_HEAD.get()) { - XSound.ENTITY_CHICKEN_EGG.play(player); - plugin.getCreateInventory().openInventory(player, CreateInventory.Page.PREDEFINED, visibility); - return; - } - break; - case 50: - if (itemStack.getType() == XMaterial.PLAYER_HEAD.get()) { - XSound.ENTITY_CHICKEN_EGG.play(player); - player.closeInventory(); - new PlayerChatInput(plugin, player, "enter_folder_name", input -> { - Folder folder = folderStorage.createFolder(input.trim()); - Messages.sendMessage(player, "worlds_folder_created", - new AbstractMap.SimpleEntry<>("%folder%", folder.getName()) - ); - }); - return; - } - break; - case 52: - if (decrementInv(player, calculateNumObjects(player), MAX_WORLDS)) { - openInventory(player); - } - return; - case 53: - if (incrementInv(player, calculateNumObjects(player), MAX_WORLDS)) { - openInventory(player); - } - return; - } - - manageInventoryClick(event, player, itemStack); - } - - private WorldSort getNextSort(WorldSort sort) { - switch (sort) { - case NEWEST_FIRST: - return WorldSort.OLDEST_FIRST; - case OLDEST_FIRST: - return WorldSort.NAME_A_TO_Z; - case PROJECT_A_TO_Z: - return WorldSort.PROJECT_Z_TO_A; - case PROJECT_Z_TO_A: - return WorldSort.STATUS_NOT_STARTED; - case STATUS_NOT_STARTED: - return WorldSort.STATUS_FINISHED; - case STATUS_FINISHED: - return WorldSort.NEWEST_FIRST; - case NAME_Z_TO_A: - return WorldSort.PROJECT_A_TO_Z; - default: // NAME_A_TO_Z - return WorldSort.NAME_Z_TO_A; - } - } - - public WorldSort getPreviousSort(WorldSort sort) { - switch (sort) { - case NEWEST_FIRST: - return WorldSort.STATUS_FINISHED; - case OLDEST_FIRST: - return WorldSort.NEWEST_FIRST; - case PROJECT_A_TO_Z: - return WorldSort.NAME_Z_TO_A; - case PROJECT_Z_TO_A: - return WorldSort.PROJECT_A_TO_Z; - case STATUS_NOT_STARTED: - return WorldSort.PROJECT_Z_TO_A; - case STATUS_FINISHED: - return WorldSort.STATUS_NOT_STARTED; - case NAME_Z_TO_A: - return WorldSort.NAME_A_TO_Z; - default: // NAME_A_TO_Z - return WorldSort.OLDEST_FIRST; - } - } - - /** - * Manage clicking in a {@link FilteredWorldsInventory}. - *

- * If the clicked item is the icon of a {@link BuildWorld}, the click is managed by {@link #manageWorldItemClick(InventoryClickEvent, Player, ItemMeta, BuildWorld)}. Otherwise, - * the {@link NavigatorInventory} is opened if the glass pane at the bottom of the inventory is clicked. - * - * @param event The click event object to modify - * @param player The player who clicked - * @param itemStack The clicked item - */ - public void manageInventoryClick(InventoryClickEvent event, Player player, ItemStack itemStack) { - if (itemStack == null || itemStack.getItemMeta() == null) { - return; - } - - int slot = event.getSlot(); - ItemMeta itemMeta = itemStack.getItemMeta(); - String displayName = itemMeta.getDisplayName(); - - if (slot == 22 && - displayName.equals(Messages.getString("world_navigator_no_worlds", player)) - || displayName.equals(Messages.getString("archive_no_worlds", player)) - || displayName.equals(Messages.getString("private_no_worlds", player))) { - return; - } - - if (slot >= 9 && slot <= 44) { - BuildWorld buildWorld = worldStorage.getBuildWorld(getWorldName(player, displayName)); - manageWorldItemClick(event, player, itemMeta, buildWorld); - return; - } - - if (slot >= 45 && slot <= 53 && itemStack.getType() != XMaterial.PLAYER_HEAD.get()) { - XSound.BLOCK_CHEST_OPEN.play(player); - plugin.getNavigatorInventory().openInventory(player); - } - } - - /** - * Parse the name of a world from the given input. - * - * @param player The player used to parse the placeholders - * @param input The string to parse the name from - * @return The name of the world - */ - private String getWorldName(Player player, String input) { - String template = Messages.getString("world_item_title", player, new AbstractMap.SimpleEntry<>("%world%", "")); - return StringUtils.difference(template, input); - } - - /** - * Manage the clicking of an {@link ItemStack} that represents a {@link BuildWorld}. - *

- * If the click is a... - *

    - *
  • ...left-click, the world is loaded (if previously unloaded) and the player is teleported to said world.
  • - *
  • ...right-click, and the player is permitted to edit the world {@link de.eintosti.buildsystem.world.util.WorldPermissionsImpl#canPerformCommand(Player, String)}, - * the {@link EditInventory} for the world is opened for said player. If the player does not have the required permission, the click is handled as a normal left click.
  • - *
- * - * @param event The click event to modify - * @param player The player who clicked - * @param itemMeta The item meta of the clicked item - * @param buildWorld The world represented by the clicked item - */ - private void manageWorldItemClick(InventoryClickEvent event, Player player, ItemMeta itemMeta, BuildWorld buildWorld) { - if (event.isLeftClick() || buildWorld.getPermissions().canPerformCommand(player, WorldsTabComplete.WorldsArgument.EDIT.getPermission())) { - performNonEditClick(player, itemMeta); - return; - } - - if (buildWorld.isLoaded()) { - playerService.getPlayerStorage().getBuildPlayer(player).setCachedWorld(buildWorld); - XSound.BLOCK_CHEST_OPEN.play(player); - plugin.getEditInventory().openInventory(player, buildWorld); - } else { - player.closeInventory(); - XSound.ENTITY_ZOMBIE_BREAK_WOODEN_DOOR.play(player); - Titles.sendTitle(player, 5, 70, 20, " ", Messages.getString("world_not_loaded", player)); - } - } - - /** - * A "non-edit click" is a click (i.e., a right click) which does not open the {@link EditInventory}. - * - * @param player The player who clicked - * @param itemMeta The item meta of the clicked item - */ - private void performNonEditClick(Player player, ItemMeta itemMeta) { - playerService.closeNavigator(player); - String worldName = getWorldName(player, itemMeta.getDisplayName()); - BuildWorld buildWorld = worldStorage.getBuildWorld(worldName); - if (buildWorld == null) { - plugin.getLogger().warning("Could not find world " + worldName); - return; - } - buildWorld.getTeleporter().teleport(player); - } -} \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java new file mode 100644 index 00000000..2e3d23cd --- /dev/null +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018-2025, Thomas Meaney + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.eintosti.buildsystem.navigator.inventory; + +import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; +import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; +import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.display.Displayable; +import de.eintosti.buildsystem.api.world.display.Folder; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class FolderContentInventory extends DisplayablesInventory { + + private final Folder folder; + private final DisplayablesInventory parentInventory; + + public FolderContentInventory( + @NotNull BuildSystemPlugin plugin, + @NotNull Player player, + @NotNull Folder folder, + @NotNull DisplayablesInventory parentInventory, + @NotNull Visibility requiredVisibility, + @NotNull Set<@NotNull BuildWorldStatus> validStatuses + ) { + super( + plugin, + player, + Messages.getString("folder_title", player, new SimpleEntry<>("%folder%", folder.getName())), + null, + requiredVisibility, + validStatuses + ); + this.folder = folder; + this.parentInventory = parentInventory; + } + + @Override + protected @NotNull List collectDisplayables() { + WorldDisplay worldDisplay = settingsManager.getSettings(player).getWorldDisplay(); + Collection buildWorlds = filterWorlds(collectWorlds(), worldDisplay); + return new ArrayList<>(buildWorlds); + } + + @Override + protected Collection collectWorlds() { + return folder.getWorldUUIDs().stream() + .map(worldStorage::getBuildWorld) + .collect(Collectors.toList()); + } + + @Override + protected void returnToPreviousInventory() { + this.parentInventory.openInventory(); + } +} diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/NavigatorInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/NavigatorInventory.java index ea7eaf6c..3ce4056e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/NavigatorInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/NavigatorInventory.java @@ -63,21 +63,20 @@ private void fillGuiWithGlass(Player player, Inventory inventory) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "old_navigator_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("old_navigator_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); - switch (event.getSlot()) { case 11: - plugin.getPublicWorldsInventory().openInventory(player); + new PublicWorldsInventory(plugin, player).openInventory(); break; case 12: - plugin.getArchivedWorldsInventory().openInventory(player); + new ArchivedWorldsInventory(plugin, player).openInventory(); break; case 13: - plugin.getPrivateWorldsInventory().openInventory(player); + new PrivateWorldsInventory(plugin, player).openInventory(); break; case 15: if (!player.hasPermission("buildsystem.settings")) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java index b69c73ac..adfed445 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java @@ -17,46 +17,51 @@ */ package de.eintosti.buildsystem.navigator.inventory; -import com.cryptomorin.xseries.profiles.objects.Profileable; -import com.google.common.collect.Sets; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; +import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; -import de.eintosti.buildsystem.player.PlayerServiceImpl; -import de.eintosti.buildsystem.util.InventoryUtils; -import java.util.Set; import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; -public class PrivateWorldsInventory extends FilteredWorldsInventory { - - private static final Visibility VISIBILITY = Visibility.PRIVATE; - private static final Set VALID_STATUS = Sets.newHashSet( - BuildWorldStatus.NOT_STARTED, BuildWorldStatus.IN_PROGRESS, BuildWorldStatus.ALMOST_FINISHED, BuildWorldStatus.FINISHED - ); - - private final PlayerServiceImpl playerManager; +/** + * An inventory class specifically for displaying and managing private {@link BuildWorld}s. + */ +public class PrivateWorldsInventory extends CreatableWorldsInventory { - public PrivateWorldsInventory(BuildSystemPlugin plugin) { - super(plugin, "private_title", "private_no_worlds", VISIBILITY, VALID_STATUS); - this.playerManager = plugin.getPlayerService(); + /** + * Constructs a new {@link PrivateWorldsInventory} instance. + * + * @param plugin The plugin instance + * @param player The player for whom this inventory is created + */ + public PrivateWorldsInventory(@NotNull BuildSystemPlugin plugin, @NotNull Player player) { + super( + plugin, + player, + Messages.getString("private_title", player), + Messages.getString("private_no_worlds", player), + Visibility.PRIVATE + ); } + /** + * Returns the permission string required to create a private world. + * + * @return The permission string: "buildsystem.create.private". + */ @Override - protected Inventory createInventory(Player player) { - Inventory inventory = super.createInventory(player); - if (playerManager.canCreateWorld(player, super.getVisibility())) { - addWorldCreateItem(inventory, player); - } - return inventory; + protected @NotNull String getWorldCreationPermission() { + return "buildsystem.create.private"; } - private void addWorldCreateItem(Inventory inventory, Player player) { - if (player.hasPermission("buildsystem.create.private")) { - inventory.setItem(49, InventoryUtils.createSkull(Messages.getString("private_create_world", player), Profileable.detect("3edd20be93520949e6ce789dc4f43efaeb28c717ee6bfcbbe02780142f716"))); - } else { - InventoryUtils.addGlassPane(player, inventory, 49); - } + /** + * Returns the message key for the title of the "create private world" item. + * + * @return The message key: "private_create_world". + */ + @Override + protected @NotNull String getWorldCreationItemTitleKey() { + return "private_create_world"; } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java index 4b6b15ce..205b7bff 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java @@ -17,55 +17,51 @@ */ package de.eintosti.buildsystem.navigator.inventory; -import com.cryptomorin.xseries.profiles.objects.Profileable; -import com.google.common.collect.Sets; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; +import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; -import de.eintosti.buildsystem.player.PlayerServiceImpl; -import de.eintosti.buildsystem.util.InventoryUtils; -import java.util.Set; import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; -public class PublicWorldsInventory extends FilteredWorldsInventory { - - private static final Visibility VISIBILITY = Visibility.PUBLIC; - private static final Set VALID_STATUS = Sets.newHashSet( - BuildWorldStatus.NOT_STARTED, BuildWorldStatus.IN_PROGRESS, BuildWorldStatus.ALMOST_FINISHED, BuildWorldStatus.FINISHED - ); - - private final PlayerServiceImpl playerManager; +/** + * An inventory class specifically for displaying and managing public {@link BuildWorld}s. + */ +public class PublicWorldsInventory extends CreatableWorldsInventory { - public PublicWorldsInventory(BuildSystemPlugin plugin) { - super(plugin, "world_navigator_title", "world_navigator_no_worlds", VISIBILITY, VALID_STATUS); - this.playerManager = plugin.getPlayerService(); + /** + * Constructs a new {@link PublicWorldsInventory} instance. + * + * @param plugin The plugin instance + * @param player The player for whom this inventory is created + */ + public PublicWorldsInventory(@NotNull BuildSystemPlugin plugin, @NotNull Player player) { + super( + plugin, + player, + Messages.getString("world_navigator_title", player), + Messages.getString("world_navigator_no_worlds", player), + Visibility.PUBLIC + ); } + /** + * Returns the permission string required to create a private world. + * + * @return The permission string: "buildsystem.create.private". + */ @Override - protected Inventory createInventory(Player player) { - Inventory inventory = super.createInventory(player); - if (playerManager.canCreateWorld(player, super.getVisibility())) { - addWorldCreateItem(inventory, player); - } - addFolderCreateItem(inventory, player); - return inventory; + protected @NotNull String getWorldCreationPermission() { + return "buildsystem.create.public"; } - private void addWorldCreateItem(Inventory inventory, Player player) { - if (player.hasPermission("buildsystem.create.public")) { - inventory.setItem(49, InventoryUtils.createSkull(Messages.getString("world_navigator_create_world", player), Profileable.detect("3edd20be93520949e6ce789dc4f43efaeb28c717ee6bfcbbe02780142f716"))); - } else { - InventoryUtils.addGlassPane(player, inventory, 49); - } - } - - private void addFolderCreateItem(Inventory inventory, Player player) { - if (player.hasPermission("buildsystem.create.folder")) { - inventory.setItem(50, InventoryUtils.createSkull(Messages.getString("world_navigator_create_folder", player), Profileable.detect("3edd20be93520949e6ce789dc4f43efaeb28c717ee6bfcbbe02780142f716"))); - } else { - InventoryUtils.addGlassPane(player, inventory, 50); - } + /** + * Returns the message key for the title of the "create public world" item. + * + * @return The message key: "world_navigator_create_world". + */ + @Override + protected @NotNull String getWorldCreationItemTitleKey() { + return "world_navigator_create_world"; } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BlocksInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BlocksInventory.java index db2c8b76..6ed06e11 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BlocksInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BlocksInventory.java @@ -100,11 +100,11 @@ private void fillGuiWithGlass(Player player, Inventory inventory) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "blocks_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("blocks_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); switch (event.getSlot()) { case 1: giveCustomBlock(player, CustomBlock.FULL_OAK_BARCH); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java index cde3fdde..7e9c7e8b 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java @@ -17,13 +17,12 @@ */ package de.eintosti.buildsystem.player; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorInventoryType; import de.eintosti.buildsystem.api.player.BuildPlayer; import de.eintosti.buildsystem.api.player.CachedValues; import de.eintosti.buildsystem.api.player.LogoutLocation; import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.world.BuildWorld; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorInventoryType; -import de.eintosti.buildsystem.world.BuildWorldImpl; import java.util.UUID; import javax.annotation.Nullable; import org.bukkit.Location; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/DesignInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/DesignInventory.java index 625ec2ae..faffbdb0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/DesignInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/DesignInventory.java @@ -106,11 +106,11 @@ private void setItem(Player player, Inventory inventory, int position, XMaterial @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "design_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("design_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); Settings settings = plugin.getSettingsManager().getSettings(player); ItemStack itemStack = event.getCurrentItem(); if (itemStack.getType().toString().contains("STAINED_GLASS_PANE")) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsImpl.java index 8df0c897..6d2fca2f 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsImpl.java @@ -17,10 +17,10 @@ */ package de.eintosti.buildsystem.player.settings; -import de.eintosti.buildsystem.api.player.settings.DesignColor; import de.eintosti.buildsystem.api.navigator.settings.NavigatorType; -import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; +import de.eintosti.buildsystem.api.player.settings.DesignColor; +import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.navigator.settings.WorldDisplayImpl; import org.bukkit.scheduler.BukkitTask; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsInventory.java index 9e799e10..25e52aff 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsInventory.java @@ -137,11 +137,11 @@ private void addDesignItem(Inventory inventory, Player player) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "settings_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("settings_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); Settings settings = settingsManager.getSettings(player); switch (event.getSlot()) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SpeedInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SpeedInventory.java index 40c894ee..a8b181a5 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SpeedInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SpeedInventory.java @@ -64,11 +64,11 @@ private void fillGuiWithGlass(Player player, Inventory inventory) { @EventHandler public void oInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "speed_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("speed_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); if (!player.hasPermission("buildsystem.speed")) { player.closeInventory(); return; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java index b9db5693..f76ca5db 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.function.Function; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -66,6 +67,15 @@ public BuildWorld getBuildWorld(World world) { return getBuildWorld(world.getName()); } + @Override + @Nullable + public BuildWorld getBuildWorld(UUID uuid) { + return buildWorlds.values().stream() + .filter(buildWorld -> buildWorld.getUniqueId().equals(uuid)) + .findFirst() + .orElse(null); + } + @Override @Unmodifiable public Collection getBuildWorlds() { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/factory/PlayerStorageFactory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/factory/PlayerStorageFactory.java index 56fa8d1d..e5c0e88d 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/factory/PlayerStorageFactory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/factory/PlayerStorageFactory.java @@ -19,8 +19,6 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.storage.PlayerStorage; -import de.eintosti.buildsystem.storage.FolderStorageImpl; -import de.eintosti.buildsystem.storage.yaml.YamlFolderStorage; import de.eintosti.buildsystem.storage.yaml.YamlPlayerStorage; /** diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/InventoryUtils.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/InventoryUtils.java index 19684c01..6ac3c524 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/InventoryUtils.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/InventoryUtils.java @@ -197,13 +197,13 @@ public static void addWorldItem(Inventory inventory, int position, BuildWorld bu /** * Checks if a click event is valid for the given inventory title. * - * @param event The click event to check - * @param titleKey The key of the inventory title + * @param event The click event to check + * @param expectedTitle The expected inventory title * @return true if the click is valid, false otherwise */ - public static boolean isValidClick(InventoryClickEvent event, String titleKey) { - String title = XInventoryView.of(event.getView()).getTitle(); - if (!title.equals(Messages.getString(titleKey, (Player) event.getWhoClicked()))) { + public static boolean isValidClick(InventoryClickEvent event, String expectedTitle) { + String viewTitle = XInventoryView.of(event.getView()).getTitle(); + if (!viewTitle.equals(expectedTitle)) { return false; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/builder/BuilderInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/builder/BuilderInventory.java index 50f0db62..3b0dd816 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/builder/BuilderInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/builder/BuilderInventory.java @@ -145,11 +145,11 @@ private void fillGuiWithGlass(Inventory inventory, Player player) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "worldeditor_builders_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("worldeditor_builders_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); BuildWorld buildWorld = plugin.getPlayerService().getPlayerStorage().getBuildPlayer(player).getCachedWorld(); if (buildWorld == null) { player.closeInventory(); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java index da397bc3..66a64ecd 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java @@ -131,8 +131,8 @@ public BuildWorldCreatorImpl setCreationDate(long creationDate) { } /** - * Depending on the {@link BuildWorldImpl}'s {@link BuildWorldType}, the corresponding {@link World} will be generated in a different way. Then, if the creation of the world was - * successful and the config is set accordingly, the player is teleported to the world. + * Depending on the {@link BuildWorldImpl}'s {@link BuildWorldType}, the corresponding {@link World} will be generated in a different way. Then, if the creation of the world + * was successful and the config is set accordingly, the player is teleported to the world. * * @param player The player who is creating the world */ diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java index 14ab9a14..c1f0becd 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java @@ -180,11 +180,11 @@ private void fillGuiWithGlass(Player player, Inventory inventory, Page page) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "create_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("create_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); CreateInventory.Page newPage = null; switch (event.getSlot()) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/DeleteInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/DeleteInventory.java index 61c6a0ad..367722c1 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/DeleteInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/DeleteInventory.java @@ -80,11 +80,11 @@ private void fillGuiWithGlass(Inventory inventory) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "delete_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("delete_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); BuildWorld buildWorld = plugin.getPlayerService().getPlayerStorage().getBuildPlayer(player).getCachedWorld(); if (buildWorld == null) { Messages.sendMessage(player, "worlds_delete_error"); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java index 3b0673f2..16b8ff61 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java @@ -294,7 +294,8 @@ private String getDifficultyName(BuildWorld buildWorld, Player player) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "worldeditor_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("worldeditor_title", player))) { return; } @@ -303,7 +304,6 @@ public void onInventoryClick(InventoryClickEvent event) { return; } - Player player = (Player) event.getWhoClicked(); BuildWorld buildWorld = plugin.getPlayerService().getPlayerStorage().getBuildPlayer(player).getCachedWorld(); if (buildWorld == null) { player.closeInventory(); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/GameRuleInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/GameRuleInventory.java index 48e64448..74024a45 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/GameRuleInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/GameRuleInventory.java @@ -25,7 +25,6 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.util.InventoryUtils; import de.eintosti.buildsystem.version.gamerules.GameRules; -import de.eintosti.buildsystem.world.BuildWorldImpl; import java.util.Arrays; import java.util.UUID; import org.bukkit.Bukkit; @@ -85,11 +84,11 @@ private boolean isValidSlot(int slot) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "worldeditor_gamerules_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("worldeditor_gamerules_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); BuildWorld buildWorld = plugin.getPlayerService().getPlayerStorage().getBuildPlayer(player).getCachedWorld(); if (buildWorld == null) { player.closeInventory(); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/SetupInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/SetupInventory.java index a884148d..fe4ca0cb 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/SetupInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/SetupInventory.java @@ -79,11 +79,11 @@ private void fillGuiWithGlass(Player player, Inventory inventory) { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (!InventoryUtils.isValidClick(event, "setup_title")) { + Player player = (Player) event.getWhoClicked(); + if (!InventoryUtils.isValidClick(event, Messages.getString("setup_title", player))) { return; } - Player player = (Player) event.getWhoClicked(); InventoryAction action = event.getAction(); InventoryType type = event.getInventory().getType(); int slot = event.getRawSlot(); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldLoaderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldLoaderImpl.java index d8002b45..d03f05dd 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldLoaderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldLoaderImpl.java @@ -24,7 +24,6 @@ import de.eintosti.buildsystem.api.event.world.BuildWorldPostLoadEvent; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.util.WorldLoader; -import de.eintosti.buildsystem.world.BuildWorldImpl; import de.eintosti.buildsystem.world.creation.BuildWorldCreatorImpl; import java.util.AbstractMap; import org.bukkit.Bukkit; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldPermissionsImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldPermissionsImpl.java index 71e5da65..e99ba862 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldPermissionsImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldPermissionsImpl.java @@ -20,8 +20,8 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.builder.Builders; -import de.eintosti.buildsystem.api.world.data.WorldData; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; +import de.eintosti.buildsystem.api.world.data.WorldData; import de.eintosti.buildsystem.api.world.util.WorldPermissions; import java.util.function.Supplier; import org.bukkit.entity.Player; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldTeleporterImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldTeleporterImpl.java index d091e494..3d751682 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldTeleporterImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldTeleporterImpl.java @@ -22,7 +22,6 @@ import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.util.WorldTeleporter; -import de.eintosti.buildsystem.world.BuildWorldImpl; import io.papermc.lib.PaperLib; import org.bukkit.Bukkit; import org.bukkit.Location; From 9e11895ce598b9d09bb23d7066553a777a91624c Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 17:43:48 +0200 Subject: [PATCH 03/22] Change folder behaviour Instead of not having a predefined navigator section, folders now live in one specific one. This allows empty folders to be shown. Also allow folders to be nested --- .../eintosti/buildsystem/api/BuildSystem.java | 2 + .../buildsystem/api/BuildSystemProvider.java | 2 + .../api/event/world/BuildWorldEvent.java | 2 + .../api/event/world/BuildWorldLoadEvent.java | 2 + .../event/world/BuildWorldPostLoadEvent.java | 2 + .../world/BuildWorldPostUnloadEvent.java | 2 + .../event/world/BuildWorldUnloadEvent.java | 2 + ...entoryType.java => NavigatorCategory.java} | 56 +++++------- .../api/navigator/settings/NavigatorType.java | 5 ++ .../api/navigator/settings/WorldDisplay.java | 5 ++ .../api/navigator/settings/WorldFilter.java | 5 ++ .../api/navigator/settings/WorldSort.java | 5 ++ .../buildsystem/api/player/BuildPlayer.java | 13 ++- .../buildsystem/api/player/CachedValues.java | 5 ++ .../api/player/LogoutLocation.java | 5 ++ .../buildsystem/api/player/PlayerService.java | 11 +-- .../api/player/settings/DesignColor.java | 2 + .../api/player/settings/Settings.java | 6 ++ .../api/storage/FolderStorage.java | 20 ++++- .../api/storage/PlayerStorage.java | 5 ++ .../buildsystem/api/storage/Storage.java | 6 ++ .../buildsystem/api/storage/WorldStorage.java | 5 ++ .../buildsystem/api/world/BuildWorld.java | 3 + .../buildsystem/api/world/WorldService.java | 3 + .../api/world/builder/Builder.java | 9 +- .../api/world/builder/BuilderImpl.java | 5 ++ .../api/world/builder/Builders.java | 5 +- .../api/world/creation/BuildWorldCreator.java | 29 +++++- .../creation/generator/CustomGenerator.java | 5 ++ .../world/creation/generator/Generator.java | 7 ++ .../api/world/data/BuildWorldStatus.java | 6 +- .../api/world/data/BuildWorldType.java | 3 + .../api/world/data/Visibility.java | 3 + .../buildsystem/api/world/data/WorldData.java | 3 + .../api/world/display/Displayable.java | 2 + .../buildsystem/api/world/display/Folder.java | 36 +++++++- .../api/world/util/WorldLoader.java | 3 + .../api/world/util/WorldPermissions.java | 3 + .../api/world/util/WorldTeleporter.java | 3 + .../api/world/util/WorldUnloader.java | 3 + .../de/eintosti/buildsystem/Messages.java | 3 + .../subcommand/worlds/ImportSubCommand.java | 9 +- .../listener/NavigatorListener.java | 6 +- .../navigator/ArmorStandManager.java | 41 +++++++-- .../inventory/ArchivedWorldsInventory.java | 24 ++++- .../inventory/CreatableWorldsInventory.java | 18 ++-- .../inventory/DisplayablesInventory.java | 88 ++++++++++++------- .../inventory/FolderContentInventory.java | 38 +++++++- .../inventory/PrivateWorldsInventory.java | 2 + .../inventory/PublicWorldsInventory.java | 2 + .../buildsystem/player/BuildPlayerImpl.java | 12 +-- .../buildsystem/player/CachedValuesImpl.java | 4 - .../player/LogoutLocationImpl.java | 4 - .../buildsystem/player/PlayerServiceImpl.java | 15 ++-- .../storage/FolderStorageImpl.java | 11 ++- .../storage/yaml/YamlFolderStorage.java | 32 +++++-- .../tabcomplete/EmptyTabComplete.java | 5 -- .../buildsystem/util/StringCleaner.java | 76 ++++++++++++++++ .../buildsystem/world/WorldServiceImpl.java | 33 +++---- .../DeprecatedVoidGenerator.java | 3 - .../voidgenerator/ModernVoidGenerator.java | 3 - .../buildsystem/world/display/FolderImpl.java | 33 ++++++- 62 files changed, 568 insertions(+), 193 deletions(-) rename buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/{NavigatorInventoryType.java => NavigatorCategory.java} (53%) create mode 100644 buildsystem-core/src/main/java/de/eintosti/buildsystem/util/StringCleaner.java diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/BuildSystem.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/BuildSystem.java index 025a85f9..fe4574ca 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/BuildSystem.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/BuildSystem.java @@ -24,6 +24,8 @@ /** * The BuildSystem API. + * + * @since 3.0.0 */ public interface BuildSystem { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/BuildSystemProvider.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/BuildSystemProvider.java index 8a9eb7f9..10e66b9d 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/BuildSystemProvider.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/BuildSystemProvider.java @@ -22,6 +22,8 @@ /** * Provides static access to the {@link BuildSystem} API. + * + * @since 3.0.0 */ public class BuildSystemProvider { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldEvent.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldEvent.java index 95882349..3894d3c1 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldEvent.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldEvent.java @@ -25,6 +25,8 @@ /** * Represents a {@link BuildWorld} related event. + * + * @since 3.0.0 */ public class BuildWorldEvent extends Event { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldLoadEvent.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldLoadEvent.java index a418b93f..76b80dd0 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldLoadEvent.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldLoadEvent.java @@ -23,6 +23,8 @@ /** * Called when a {@link BuildWorld} is loaded. + * + * @since 3.0.0 */ public class BuildWorldLoadEvent extends BuildWorldEvent implements Cancellable { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldPostLoadEvent.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldPostLoadEvent.java index 7b2cec8f..a926ebe7 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldPostLoadEvent.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldPostLoadEvent.java @@ -22,6 +22,8 @@ /** * Called after a {@link BuildWorld} has loaded. + * + * @since 3.0.0 */ public class BuildWorldPostLoadEvent extends BuildWorldEvent { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldPostUnloadEvent.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldPostUnloadEvent.java index 600f31a6..f6124a90 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldPostUnloadEvent.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldPostUnloadEvent.java @@ -22,6 +22,8 @@ /** * Called after a {@link BuildWorld} has unloaded. + * + * @since 3.0.0 */ public class BuildWorldPostUnloadEvent extends BuildWorldEvent { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldUnloadEvent.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldUnloadEvent.java index 8d7a798f..51e7df7f 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldUnloadEvent.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/event/world/BuildWorldUnloadEvent.java @@ -23,6 +23,8 @@ /** * Called when a {@link BuildWorld} is unloaded. + * + * @since 3.0.0 */ public class BuildWorldUnloadEvent extends BuildWorldEvent implements Cancellable { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorInventoryType.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorCategory.java similarity index 53% rename from buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorInventoryType.java rename to buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorCategory.java index 629cf510..6c335e62 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorInventoryType.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorCategory.java @@ -21,65 +21,49 @@ import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.WorldData; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.ApiStatus.Internal; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; /** * Represents the different kinds of navigator types that can be opened. * - * @author einTosti + * @since 3.0.0 */ -public enum NavigatorInventoryType { +public enum NavigatorCategory { /** * The navigator inventory which contains all {@link BuildWorld}s that are still being built. */ - NAVIGATOR("§aWorld Navigator"), + PUBLIC, /** * The navigator inventory which contains all archived {@link BuildWorld}s. * * @see BuildWorldStatus#ARCHIVE */ - ARCHIVE("§6World Archive"), + ARCHIVE, /** - * The navigator inventory which contains all private {@link BuildWorld}s that can only be modified by the world's creator and all players who have been added as a {@link Builder}. + * The navigator inventory which contains all private {@link BuildWorld}s that can only be modified by the world's creator and all players who have been added as a + * {@link Builder}. * * @see WorldData#privateWorld() */ - PRIVATE("§bPrivate Worlds"); - - private final String armorStandName; - - NavigatorInventoryType(String armorStandName) { - this.armorStandName = armorStandName; - } - - @Nullable - public static NavigatorInventoryType matchInventoryType(Player player, String customName) { - String typeName = customName.replace(player.getName() + " × ", ""); - - for (NavigatorInventoryType navigatorInventoryType : values()) { - if (navigatorInventoryType.getArmorStandName().equalsIgnoreCase(typeName)) { - return navigatorInventoryType; - } - } - - return null; - } + PRIVATE; /** - * When opening the {@link NavigatorType#NEW} navigator, each {@link org.bukkit.entity.ArmorStand} has a custom name which is unique for every player and each - * {@link NavigatorInventoryType}. - *
- * Gets said name. + * Returns the appropriate {@link NavigatorCategory} based on the provided {@link BuildWorld}. * - * @return The name which an {@link org.bukkit.entity.ArmorStand} uses to represent a navigator inventory type + * @param buildWorld The {@link BuildWorld} to determine the category for + * @return The corresponding {@link NavigatorCategory} */ - @Internal - public String getArmorStandName() { - return armorStandName; + public static NavigatorCategory of(@NotNull BuildWorld buildWorld) { + WorldData worldData = buildWorld.getData(); + if (worldData.privateWorld().get()) { + return PRIVATE; + } else if (worldData.status().get() == BuildWorldStatus.ARCHIVE) { + return ARCHIVE; + } else { + return PUBLIC; + } } } \ No newline at end of file diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorType.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorType.java index 46bdd2ad..11f0c5b1 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorType.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorType.java @@ -17,6 +17,11 @@ */ package de.eintosti.buildsystem.api.navigator.settings; +/** + * Represents the type of the navigator. + * + * @since 3.0.0 + */ public enum NavigatorType { OLD, NEW; diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldDisplay.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldDisplay.java index e468c656..87bdf786 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldDisplay.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldDisplay.java @@ -19,6 +19,11 @@ import de.eintosti.buildsystem.api.world.BuildWorld; +/** + * Interface for managing the display settings of worlds in the navigator. + * + * @since 3.0.0 + */ public interface WorldDisplay { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldFilter.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldFilter.java index b7b9e789..46be9d4a 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldFilter.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldFilter.java @@ -21,6 +21,11 @@ import java.util.function.Predicate; import org.jetbrains.annotations.ApiStatus.Internal; +/** + * Interface for a world filter that restricts which {@link BuildWorld}s are shown to a user in the navigator. + * + * @since 3.0.0 + */ public interface WorldFilter { enum Mode { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldSort.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldSort.java index 636a54cb..d02e9a99 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldSort.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/WorldSort.java @@ -19,6 +19,11 @@ import org.jetbrains.annotations.ApiStatus.Internal; +/** + * Represents the sorting options for worlds in the navigator. + * + * @since 3.0.0 + */ public enum WorldSort { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/BuildPlayer.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/BuildPlayer.java index 96d0c4b7..0fdf4adf 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/BuildPlayer.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/BuildPlayer.java @@ -17,14 +17,13 @@ */ package de.eintosti.buildsystem.api.player; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorInventoryType; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.world.BuildWorld; +import java.util.UUID; import javax.annotation.Nullable; import org.bukkit.Location; import org.bukkit.entity.Player; - -import java.util.UUID; import org.jetbrains.annotations.ApiStatus.Internal; public interface BuildPlayer { @@ -110,19 +109,19 @@ public interface BuildPlayer { void setPreviousLocation(Location location); /** - * Gets the {@link NavigatorInventoryType} the player last looked at. + * Gets the {@link NavigatorCategory} the player last looked at. * * @return The last looked navigator inventory type */ @Internal @Nullable - NavigatorInventoryType getLastLookedAt(); + NavigatorCategory getLastLookedAt(); /** - * Sets the {@link NavigatorInventoryType} the player last looked at. + * Sets the {@link NavigatorCategory} the player last looked at. * * @param type The last looked navigator inventory type */ @Internal - void setLastLookedAt(NavigatorInventoryType type); + void setLastLookedAt(NavigatorCategory type); } \ No newline at end of file diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/CachedValues.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/CachedValues.java index e52ccd1e..7748df68 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/CachedValues.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/CachedValues.java @@ -22,6 +22,11 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.ApiStatus.Internal; +/** + * Interface for managing cached values of a player. + * + * @since 3.0.0 + */ @Internal public interface CachedValues { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/LogoutLocation.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/LogoutLocation.java index dba59c76..41c1fdd8 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/LogoutLocation.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/LogoutLocation.java @@ -20,6 +20,11 @@ import org.bukkit.Location; import org.jetbrains.annotations.ApiStatus.Internal; +/** + * Represents a player's logout location, which includes the world name and the location coordinates. + * + * @since 3.0.0 + */ @Internal public interface LogoutLocation { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/PlayerService.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/PlayerService.java index dd22cb81..239ae8fc 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/PlayerService.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/PlayerService.java @@ -18,16 +18,17 @@ package de.eintosti.buildsystem.api.player; import de.eintosti.buildsystem.api.storage.PlayerStorage; -import de.eintosti.buildsystem.api.storage.WorldStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Unmodifiable; - -import java.util.Collection; import java.util.Set; import java.util.UUID; +import org.bukkit.entity.Player; +/** + * Service for managing {@link BuildPlayer}. + * + * @since 3.0.0 + */ public interface PlayerService { PlayerStorage getPlayerStorage(); diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/settings/DesignColor.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/settings/DesignColor.java index ecc148aa..86b6d625 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/settings/DesignColor.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/settings/DesignColor.java @@ -19,6 +19,8 @@ /** * A {@link DesignColor} is the color which glass panes are tinted to in different menus. + * + * @since 3.0.0 */ public enum DesignColor { RED, diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/settings/Settings.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/settings/Settings.java index 9908b578..edd464c1 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/settings/Settings.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/settings/Settings.java @@ -19,11 +19,17 @@ import de.eintosti.buildsystem.api.navigator.settings.NavigatorType; import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; +import de.eintosti.buildsystem.api.player.BuildPlayer; import org.bukkit.GameMode; import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.ApiStatus.Internal; +/** + * Different settings a {@link BuildPlayer} can modify for themselves. + * + * @since 3.0.0 + */ public interface Settings { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java index 1a417c45..56fa9069 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java @@ -17,12 +17,18 @@ */ package de.eintosti.buildsystem.api.storage; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; import java.util.Collection; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; +/** + * Interface for managing the storage of {@link Folder} objects. + * + * @since 3.0.0 + */ public interface FolderStorage extends Storage { /** @@ -53,11 +59,21 @@ public interface FolderStorage extends Storage { Folder getFolder(String folderName, boolean caseSensitive); /** - * Creates a new {@link Folder} with the given name and adds it to the folder storage. + * Creates a new {@link Folder} with the given name. + * + * @param folderName The name folder to create + * @param category The category in which the folder should be displayed + */ + Folder createFolder(String folderName, NavigatorCategory category); + + /** + * Creates a new nested {@link Folder} with the given name. * * @param folderName The name folder to create + * @param category The category in which the folder should be displayed + * @param parent The parent folder, or {@code null} if this is a top-level folder */ - Folder createFolder(String folderName); + Folder createFolder(String folderName, NavigatorCategory category, @Nullable Folder parent); /** * Removes a folder and all of its worlds from the world-to-folder mapping. diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/PlayerStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/PlayerStorage.java index 421465f2..2d75c6cc 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/PlayerStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/PlayerStorage.java @@ -24,6 +24,11 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.Unmodifiable; +/** + * Interface for managing the storage of {@link BuildPlayer} objects. + * + * @since 3.0.0 + */ public interface PlayerStorage extends Storage { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/Storage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/Storage.java index b2b0359d..b571c2ac 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/Storage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/Storage.java @@ -19,6 +19,12 @@ import java.util.Collection; +/** + * A generic interface for storage operations. + * + * @param The type of objects to be stored + * @since 3.0.0 + */ public interface Storage { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java index bc58e3d7..660e1608 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java @@ -27,6 +27,11 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; +/** + * Interface for managing the storage of {@link BuildWorld} objects. + * + * @since 3.0.0 + */ public interface WorldStorage extends Storage { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java index 1ea69cc4..ae0fe200 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java @@ -32,6 +32,9 @@ import org.bukkit.World; import org.jetbrains.annotations.Nullable; +/** + * @since 3.0.0 + */ public interface BuildWorld extends Displayable { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/WorldService.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/WorldService.java index 9ccf2cb6..9a3a5f5e 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/WorldService.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/WorldService.java @@ -20,6 +20,9 @@ import de.eintosti.buildsystem.api.storage.FolderStorage; import de.eintosti.buildsystem.api.storage.WorldStorage; +/** + * @since 3.0.0 + */ public interface WorldService { WorldStorage getWorldStorage(); diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/Builder.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/Builder.java index a6465d2b..b90ca3bd 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/Builder.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/Builder.java @@ -17,11 +17,16 @@ */ package de.eintosti.buildsystem.api.world.builder; +import de.eintosti.buildsystem.api.world.BuildWorld; +import java.util.UUID; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; -import java.util.UUID; - +/** + * A {@link Builder} represents a player allowed to build in a {@link BuildWorld}. + * + * @since 3.0.0 + */ public interface Builder { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/BuilderImpl.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/BuilderImpl.java index 274870f2..f0336163 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/BuilderImpl.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/BuilderImpl.java @@ -19,6 +19,11 @@ import java.util.UUID; +/** + * Concrete implementation of the {@link Builder} interface. + * + * @since 3.0.0 + */ final class BuilderImpl implements Builder { static final String SEPARATOR = ","; diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/Builders.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/Builders.java index f08169b5..5276be70 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/Builders.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/builder/Builders.java @@ -17,6 +17,7 @@ */ package de.eintosti.buildsystem.api.world.builder; +import de.eintosti.buildsystem.api.world.BuildWorld; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -25,7 +26,9 @@ import org.jetbrains.annotations.Nullable; /** - * Interface for managing builders in a {@link de.eintosti.buildsystem.api.world.BuildWorld}. + * Interface for managing builders in a {@link BuildWorld}. + * + * @since 3.0.0 */ public interface Builders { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java index 237e67a8..09d2a061 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java @@ -19,14 +19,19 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.builder.Builder; -import de.eintosti.buildsystem.api.world.data.BuildWorldType; import de.eintosti.buildsystem.api.world.creation.generator.CustomGenerator; +import de.eintosti.buildsystem.api.world.data.BuildWorldType; import org.bukkit.Difficulty; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.Nullable; +/** + * Represents a creator for a {@link BuildWorld}. + * + * @since 3.0.0 + */ public interface BuildWorldCreator { /** @@ -71,15 +76,33 @@ public interface BuildWorldCreator { */ BuildWorldCreator setCustomGenerator(CustomGenerator customGenerator); + /** + * Sets whether the world should be private or not. + * + * @param privateWorld Whether the world should be private + * @return The world creator object + */ BuildWorldCreator setPrivate(boolean privateWorld); + /** + * Sets the difficulty of the world. + * + * @param difficulty The difficulty + * @return The world creator object + */ BuildWorldCreator setDifficulty(Difficulty difficulty); + /** + * Sets the creation date of the world. + * + * @param creationDate The creation date in milliseconds since epoch + * @return The world creator object + */ BuildWorldCreator setCreationDate(long creationDate); /** - * Depending on the {@link BuildWorld}'s {@link BuildWorldType}, the corresponding {@link World} will be generated in a different way. - * Then, if the creation of the world was successful and the config is set accordingly, the player is teleported to the world. + * Depending on the {@link BuildWorld}'s {@link BuildWorldType}, the corresponding {@link World} will be generated in a different way. Then, if the creation of the world was + * successful and the config is set accordingly, the player is teleported to the world. * * @param player The player who is creating the world */ diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/CustomGenerator.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/CustomGenerator.java index 50f0fcb4..445bde9c 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/CustomGenerator.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/CustomGenerator.java @@ -19,6 +19,11 @@ import org.bukkit.generator.ChunkGenerator; +/** + * Represents a custom chunk generator for a world. + * + * @since 3.0.0 + */ public interface CustomGenerator { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/Generator.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/Generator.java index 73e6768e..47602745 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/Generator.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/Generator.java @@ -17,6 +17,13 @@ */ package de.eintosti.buildsystem.api.world.creation.generator; +import de.eintosti.buildsystem.api.world.BuildWorld; + +/** + * Different kinds of world generators that can be used for a {@link BuildWorld}. + * + * @since 3.0.0 + */ public enum Generator { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/BuildWorldStatus.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/BuildWorldStatus.java index f9c371d1..fbd3b6ed 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/BuildWorldStatus.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/BuildWorldStatus.java @@ -21,6 +21,9 @@ import java.util.Locale; import org.jetbrains.annotations.ApiStatus.Internal; +/** + * @since 3.0.0 + */ public enum BuildWorldStatus { /** @@ -83,8 +86,7 @@ public String getPermission() { } /** - * Gets the stage in which the {@link BuildWorld} is currently in. - * A higher value means the world is further in development. + * Gets the stage in which the {@link BuildWorld} is currently in. A higher value means the world is further in development. * * @return the stage in which the world is currently in. */ diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/BuildWorldType.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/BuildWorldType.java index c3621840..a78ee2d5 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/BuildWorldType.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/BuildWorldType.java @@ -21,6 +21,9 @@ import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.Nullable; +/** + * @since 3.0.0 + */ public enum BuildWorldType { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/Visibility.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/Visibility.java index c03388c9..489a9d4e 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/Visibility.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/Visibility.java @@ -17,6 +17,9 @@ */ package de.eintosti.buildsystem.api.world.data; +/** + * @since 3.0.0 + */ public enum Visibility { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/WorldData.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/WorldData.java index 2803af30..c1112e3e 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/WorldData.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/WorldData.java @@ -24,6 +24,9 @@ import org.bukkit.Location; import org.jetbrains.annotations.Nullable; +/** + * @since 3.0.0 + */ public interface WorldData { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java index 51fbfb54..109c29f3 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java @@ -25,6 +25,8 @@ /** * Represents an object that can be displayed in an inventory. + * + * @since 3.0.0 */ public interface Displayable { diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java index 9e34095f..9d9b6356 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java @@ -17,13 +17,47 @@ */ package de.eintosti.buildsystem.api.world.display; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import java.util.List; import java.util.UUID; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; +/** + * @since 3.0.0 + */ public interface Folder extends Displayable { + /** + * Gets the category in which the {@link Folder} is displayed. + * + * @return The folder category + */ + NavigatorCategory getCategory(); + + /** + * Gets the folder's parent {@link Folder}. + * + * @return The parent folder, or {@code null} if this is a root folder + */ + @Nullable + Folder getParent(); + + /** + * Gets whether this folder has a parent {@link Folder}. + * + * @return {@code true} if this folder has a parent, {@code false} otherwise + */ + boolean hasParent(); + + /** + * Sets the folder's parent {@link Folder}. + * + * @param parent The parent folder to set, or {@code null} to remove the parent + */ + void setParent(@Nullable Folder parent); + /** * Gets a list of all world UUIDs in this folder. * @@ -60,4 +94,4 @@ public interface Folder extends Displayable { * @return The number of worlds in this folder */ int getWorldCount(); -} \ No newline at end of file +} \ No newline at end of file diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldLoader.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldLoader.java index 145a7dd2..dd249f03 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldLoader.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldLoader.java @@ -19,6 +19,9 @@ import org.bukkit.entity.Player; +/** + * @since 3.0.0 + */ public interface WorldLoader { void loadForPlayer(Player player); diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldPermissions.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldPermissions.java index fc79f292..917cd4b7 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldPermissions.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldPermissions.java @@ -21,6 +21,9 @@ import java.util.function.Supplier; import org.bukkit.entity.Player; +/** + * @since 3.0.0 + */ public interface WorldPermissions { /** diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldTeleporter.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldTeleporter.java index 3e5c7a6c..d94757ea 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldTeleporter.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldTeleporter.java @@ -20,6 +20,9 @@ import org.bukkit.Location; import org.bukkit.entity.Player; +/** + * @since 3.0.0 + */ public interface WorldTeleporter { void teleport(Player player); diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldUnloader.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldUnloader.java index 954cc8c9..91735201 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldUnloader.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/util/WorldUnloader.java @@ -17,6 +17,9 @@ */ package de.eintosti.buildsystem.api.world.util; +/** + * @since 3.0.0 + */ public interface WorldUnloader { void manageUnload(); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index efc1834e..92c86ae7 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -271,6 +271,9 @@ public static void createMessageFile() { setMessage(sb, "worlds_folder_world_not_in_folder", "%prefix% &c%world% is not contained within %folder%."); setMessage(sb, "worlds_folder_world_added_to_folder", "%prefix% &b%world% &7was &aadded &7to &b%folder%&7."); setMessage(sb, "worlds_folder_world_removed_from_folder", "%prefix% &b%world% &7was &cremoved &7from &b%folder%&7."); + setMessage(sb, "worlds_folder_exists", "%prefix% &cThis folder already exists."); + setMessage(sb, "worlds_folder_creation_invalid_characters", "%prefix% &7&oRemoved invalid characters from folder name."); + setMessage(sb, "worlds_folder_creation_name_bank", "%prefix% &cThe folder name cannot be blank."); setMessage(sb, "worlds_folder_created", "%prefix% &7The folder &b%folder% &7was &asuccessfully &7created."); addSpacer(sb, ""); setMessage(sb, "worlds_help_usage", "%prefix% &7Usage: &b/worlds help [page]"); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportSubCommand.java index 8f11851c..2e4be2f4 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportSubCommand.java @@ -26,11 +26,11 @@ import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; import de.eintosti.buildsystem.util.ArgumentParser; +import de.eintosti.buildsystem.util.StringCleaner; import de.eintosti.buildsystem.util.UUIDFetcher; import de.eintosti.buildsystem.world.WorldServiceImpl; import java.io.File; import java.util.AbstractMap; -import java.util.Arrays; import java.util.Locale; import java.util.UUID; import org.bukkit.Bukkit; @@ -71,12 +71,7 @@ public void execute(Player player, String[] args) { return; } - String invalidChar = Arrays.stream(worldName.split("")) - .filter(c -> - c.matches("[^A-Za-z\\d/_-]") || c.matches(plugin.getConfigValues().getInvalidNameCharacters()) - ) - .findFirst() - .orElse(null); + String invalidChar = StringCleaner.firstInvalidChar(worldName); if (invalidChar != null) { Messages.sendMessage(player, "worlds_import_invalid_character", new AbstractMap.SimpleEntry<>("%world%", worldName), diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java index 34bfa851..06fb2731 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java @@ -23,7 +23,7 @@ import com.cryptomorin.xseries.inventory.XInventoryView; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorInventoryType; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.navigator.settings.NavigatorType; import de.eintosti.buildsystem.api.player.CachedValues; import de.eintosti.buildsystem.api.player.settings.Settings; @@ -193,13 +193,13 @@ public void manageNewNavigatorInteraction(PlayerInteractAtEntityEvent event) { return; } - NavigatorInventoryType inventoryType = NavigatorInventoryType.matchInventoryType(player, customName); + NavigatorCategory inventoryType = ArmorStandManager.matchNavigatorCategory(player, customName); if (inventoryType == null) { return; } switch (inventoryType) { - case NAVIGATOR: + case PUBLIC: XSound.BLOCK_CHEST_OPEN.play(player); new PublicWorldsInventory(plugin, player).openInventory(); break; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/ArmorStandManager.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/ArmorStandManager.java index 766eda3c..2880b007 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/ArmorStandManager.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/ArmorStandManager.java @@ -19,16 +19,28 @@ import com.cryptomorin.xseries.profiles.builder.XSkull; import com.cryptomorin.xseries.profiles.objects.Profileable; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorInventoryType; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; +import java.util.EnumMap; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.UUID; import org.bukkit.Location; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; public class ArmorStandManager { + public static final Map ARMOR_STAND_NAMES; + + static { + ARMOR_STAND_NAMES = new EnumMap<>(NavigatorCategory.class); + ARMOR_STAND_NAMES.put(NavigatorCategory.PUBLIC, "§aWorld Navigator"); + ARMOR_STAND_NAMES.put(NavigatorCategory.ARCHIVE, "§6World Archive"); + ARMOR_STAND_NAMES.put(NavigatorCategory.PRIVATE, "§bPrivate Worlds"); + } + private static final float RADIUS = 2.2f; private static final float SPREAD = 90.0f; @@ -38,6 +50,19 @@ public ArmorStandManager() { this.armorStands = new HashMap<>(); } + @Nullable + public static NavigatorCategory matchNavigatorCategory(Player player, String customName) { + String typeName = customName.replace(player.getName() + " × ", ""); + + for (Entry entry : ArmorStandManager.ARMOR_STAND_NAMES.entrySet()) { + if (entry.getValue().equalsIgnoreCase(typeName)) { + return entry.getKey(); + } + } + + return null; + } + private Location calculatePosition(Player player, float angle) { Location playerLocation = player.getLocation(); float centerX = (float) playerLocation.getX(); @@ -52,12 +77,12 @@ private Location calculatePosition(Player player, float angle) { } @SuppressWarnings("deprecation") - private ArmorStand spawnArmorStand(Player player, Location location, NavigatorInventoryType inventoryType, boolean customSkull, String skullUrl) { + private ArmorStand spawnArmorStand(Player player, Location location, NavigatorCategory inventoryType, boolean customSkull, String skullUrl) { location.setY(location.getY() - 0.1); ArmorStand armorStand = location.getWorld().spawn(location, ArmorStand.class); armorStand.setVisible(false); - armorStand.setCustomName(player.getName() + " × " + inventoryType.getArmorStandName()); + armorStand.setCustomName(player.getName() + " × " + ARMOR_STAND_NAMES.get(inventoryType)); armorStand.setCustomNameVisible(false); armorStand.setGravity(false); armorStand.setCanPickupItems(false); @@ -79,17 +104,17 @@ public void spawnArmorStands(Player player) { private ArmorStand spawnWorldNavigator(Player player) { Location navigatorLocation = calculatePosition(player, SPREAD / 2 * -1); - return spawnArmorStand(player, navigatorLocation, NavigatorInventoryType.NAVIGATOR, true, "d5c6dc2bbf51c36cfc7714585a6a5683ef2b14d47d8ff714654a893f5da622"); + return spawnArmorStand(player, navigatorLocation, NavigatorCategory.PUBLIC, true, "d5c6dc2bbf51c36cfc7714585a6a5683ef2b14d47d8ff714654a893f5da622"); } private ArmorStand spawnWorldArchive(Player player) { Location archiveLocation = calculatePosition(player, 0); - return spawnArmorStand(player, archiveLocation, NavigatorInventoryType.ARCHIVE, true, "7f6bf958abd78295eed6ffc293b1aa59526e80f54976829ea068337c2f5e8"); + return spawnArmorStand(player, archiveLocation, NavigatorCategory.ARCHIVE, true, "7f6bf958abd78295eed6ffc293b1aa59526e80f54976829ea068337c2f5e8"); } private ArmorStand spawnPrivateWorlds(Player player) { Location privateLocation = calculatePosition(player, SPREAD / 2); - return spawnArmorStand(player, privateLocation, NavigatorInventoryType.PRIVATE, false, player.getName()); + return spawnArmorStand(player, privateLocation, NavigatorCategory.PRIVATE, false, player.getName()); } public void removeArmorStands(Player player) { @@ -105,8 +130,8 @@ public void removeArmorStands(Player player) { continue; } - for (NavigatorInventoryType inventoryType : NavigatorInventoryType.values()) { - if (customName.equals(playerName + " × " + inventoryType.getArmorStandName())) { + for (NavigatorCategory inventoryType : NavigatorCategory.values()) { + if (customName.equals(playerName + " × " + ARMOR_STAND_NAMES.get(inventoryType))) { armorStand.remove(); break; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java index 55838c60..2fa26bdd 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java @@ -17,9 +17,13 @@ */ package de.eintosti.buildsystem.navigator.inventory; +import static de.eintosti.buildsystem.navigator.inventory.CreatableWorldsInventory.CREATE_FOLDER_PROFILE; + +import com.cryptomorin.xseries.profiles.objects.Profileable; import com.google.common.collect.Sets; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; import de.eintosti.buildsystem.util.InventoryUtils; @@ -33,6 +37,7 @@ public ArchivedWorldsInventory(BuildSystemPlugin plugin, Player player) { super( plugin, player, + NavigatorCategory.ARCHIVE, Messages.getString("archive_title", player), Messages.getString("archive_no_worlds", player), Visibility.IGNORE, @@ -40,10 +45,23 @@ public ArchivedWorldsInventory(BuildSystemPlugin plugin, Player player) { ); } + /** + * Overrides the base method to include the "create folder" item in the inventory page layout. + * + * @return A newly created {@link Inventory} page with common base items and creation options + */ @Override - protected @NotNull Inventory createBaseInventoryPage() { - Inventory inventory = super.createBaseInventoryPage(); - InventoryUtils.addGlassPane(player, inventory, 49); + protected @NotNull Inventory createBaseInventoryPage(String inventoryTitle) { + Inventory inventory = super.createBaseInventoryPage(inventoryTitle); + addFolderCreateItem(inventory, player); return inventory; } + + private void addFolderCreateItem(Inventory inventory, Player player) { + if (player.hasPermission("buildsystem.create.folder")) { + inventory.setItem(50, InventoryUtils.createSkull(Messages.getString("world_navigator_create_folder", player), Profileable.detect(CREATE_FOLDER_PROFILE))); + } else { + InventoryUtils.addGlassPane(player, inventory, 49); + } + } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java index cc310fd2..839e01db 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java @@ -21,6 +21,7 @@ import com.google.common.collect.Sets; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; @@ -46,8 +47,8 @@ public abstract class CreatableWorldsInventory extends DisplayablesInventory { BuildWorldStatus.FINISHED ); - private static final String CREATE_WORLD_PROFILE = "3edd20be93520949e6ce789dc4f43efaeb28c717ee6bfcbbe02780142f716"; - private static final String CREATE_FOLDER_PROFILE = "69b861aabb316c4ed73b4e5428305782e735565ba2a053912e1efd834fa5a6f"; + static final String CREATE_WORLD_PROFILE = "3edd20be93520949e6ce789dc4f43efaeb28c717ee6bfcbbe02780142f716"; + static final String CREATE_FOLDER_PROFILE = "69b861aabb316c4ed73b4e5428305782e735565ba2a053912e1efd834fa5a6f"; private final PlayerServiceImpl playerService; @@ -63,22 +64,23 @@ public abstract class CreatableWorldsInventory extends DisplayablesInventory { protected CreatableWorldsInventory( @NotNull BuildSystemPlugin plugin, @NotNull Player player, + @NotNull NavigatorCategory category, @NotNull String inventoryTitle, @Nullable String noWorldsMessage, @NotNull Visibility requiredVisibility ) { - super(plugin, player, inventoryTitle, noWorldsMessage, requiredVisibility, VALID_STATUSES); + super(plugin, player, category, inventoryTitle, noWorldsMessage, requiredVisibility, VALID_STATUSES); this.playerService = plugin.getPlayerService(); } /** * Overrides the base method to include the "create world" and "create folder" items in the inventory page layout. * - * @return A newly created {@link Inventory} page with common base items and creation options. + * @return A newly created {@link Inventory} page with common base items and creation options */ @Override - protected @NotNull Inventory createBaseInventoryPage() { - Inventory inventory = super.createBaseInventoryPage(); + protected @NotNull Inventory createBaseInventoryPage(String inventoryTitle) { + Inventory inventory = super.createBaseInventoryPage(inventoryTitle); addWorldCreateItem(inventory, player); addFolderCreateItem(inventory, player); return inventory; @@ -108,14 +110,14 @@ private void addFolderCreateItem(Inventory inventory, Player player) { * Gets the specific permission string required for a player to create a {@link BuildWorld} of the type managed by this inventory (e.g., {@code buildsystem.create.private}, * {@code buildsystem.create.public}). * - * @return The permission string for creating a world of this inventory's type. + * @return The permission string for creating a world of this inventory's type */ protected abstract @NotNull String getWorldCreationPermission(); /** * Gets the message key the title of the "create world" item. * - * @return The message key for the "create world" item's title. + * @return The message key for the "create world" item's title */ protected abstract @NotNull String getWorldCreationItemTitleKey(); } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index 3f8b21a1..9386df90 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -6,6 +6,7 @@ import com.cryptomorin.xseries.profiles.objects.Profileable; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; import de.eintosti.buildsystem.api.navigator.settings.WorldFilter; import de.eintosti.buildsystem.api.navigator.settings.WorldSort; @@ -24,6 +25,7 @@ import de.eintosti.buildsystem.util.InventoryUtils; import de.eintosti.buildsystem.util.PaginatedInventory; import de.eintosti.buildsystem.util.PlayerChatInput; +import de.eintosti.buildsystem.util.StringCleaner; import de.eintosti.buildsystem.util.StringUtils; import de.eintosti.buildsystem.world.WorldServiceImpl; import de.eintosti.buildsystem.world.creation.CreateInventory; @@ -33,7 +35,6 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -71,8 +72,9 @@ public abstract class DisplayablesInventory extends PaginatedInventory implement protected final WorldStorageImpl worldStorage; protected final Player player; + protected final NavigatorCategory category; protected final Visibility requiredVisibility; - private final Set validStatuses; + protected final Set validStatuses; private final String inventoryTitle; private final String noWorldsMessage; @@ -82,16 +84,18 @@ public abstract class DisplayablesInventory extends PaginatedInventory implement /** * Constructs a new {@link DisplayablesInventory} for a specific player. * - * @param plugin The plugin instance. - * @param player The player for whom this inventory is created. - * @param inventoryTitle The inventory's title. - * @param noWorldsMessage The "no worlds" message. - * @param requiredVisibility The required visibility for worlds to be displayed. - * @param validStatuses The set of valid statuses for worlds to be displayed. + * @param plugin The plugin instance + * @param player The player for whom this inventory is created + * @param category The category of the inventory, used for organizing folders + * @param inventoryTitle The inventory's title + * @param noWorldsMessage The "no worlds" message + * @param requiredVisibility The required visibility for worlds to be displayed + * @param validStatuses The set of valid statuses for worlds to be displayed */ protected DisplayablesInventory( @NotNull BuildSystemPlugin plugin, @NotNull Player player, + @NotNull NavigatorCategory category, @NotNull String inventoryTitle, @Nullable String noWorldsMessage, @NotNull Visibility requiredVisibility, @@ -99,6 +103,7 @@ protected DisplayablesInventory( ) { this.plugin = plugin; this.player = player; + this.category = category; this.inventoryTitle = inventoryTitle; this.noWorldsMessage = noWorldsMessage; this.requiredVisibility = requiredVisibility; @@ -125,8 +130,8 @@ public void openInventory() { * * @return A new inventory instance. */ - protected @NotNull Inventory createBaseInventoryPage() { - Inventory inventory = Bukkit.createInventory(player, 54, this.inventoryTitle); + protected @NotNull Inventory createBaseInventoryPage(String inventoryTitle) { + Inventory inventory = Bukkit.createInventory(player, 54, inventoryTitle); InventoryUtils.fillWithGlass(inventory, player); addWorldSortItem(inventory); @@ -149,7 +154,7 @@ private void initializeInventories() { this.generatedInventories = new Inventory[numPages]; for (int pageIndex = 0; pageIndex < numPages; pageIndex++) { - Inventory currentPage = createBaseInventoryPage(); + Inventory currentPage = createBaseInventoryPage(this.inventoryTitle); this.generatedInventories[pageIndex] = currentPage; if (numDisplayableObjects == 0 && this.noWorldsMessage != null) { @@ -190,12 +195,10 @@ private void initializeInventories() { * @return A list of {@link Displayable} items to be presented in the UI, sorted with folders first. */ protected @NotNull List collectDisplayables() { - Settings settings = settingsManager.getSettings(player); - WorldDisplay worldDisplay = settings.getWorldDisplay(); + WorldDisplay worldDisplay = settingsManager.getSettings(player).getWorldDisplay(); - Collection filteredWorlds = filterWorlds(collectWorlds(), worldDisplay); - Collection folders = collectFolders(filteredWorlds); - List standaloneWorlds = filteredWorlds.stream() + Collection folders = collectFolders(); + List standaloneWorlds = filterWorlds(collectWorlds(), worldDisplay).stream() .filter(world -> !folderStorage.isAssignedToAnyFolder(world)) .collect(Collectors.toList()); @@ -205,6 +208,18 @@ private void initializeInventories() { return displayables; } + /** + * Collects all {@link Folder}s that belong to the specified category and do not have a parent folder. + * + * @return A collection of root folders in the specified category + */ + protected Collection collectFolders() { + return folderStorage.getFolders().stream() + .filter(folder -> folder.getCategory() == this.category) + .filter(folder -> !folder.hasParent()) + .collect(Collectors.toList()); + } + /** * Collects all potential {@link BuildWorld}s that be displayed to the player before filtering them based on visibility and status. */ @@ -227,20 +242,6 @@ protected Collection filterWorlds(Collection buildWorlds .collect(Collectors.toList()); } - /** - * Collects all {@link Folder}s that contain a {@link BuildWorld} from the given collection. - * - * @param buildWorlds The collection of build worlds to check for folders - * @return A collection of folders that contain a build world - */ - private Collection collectFolders(Collection buildWorlds) { - return buildWorlds.stream() - .map(this.folderStorage::getAssignedFolder) - .filter(Objects::nonNull) - .distinct() - .collect(Collectors.toList()); - } - /** * Checks if a given {@link BuildWorld} is valid for display based on visibility, status, and player permissions. * @@ -359,14 +360,29 @@ public void onInventoryClick(@NotNull InventoryClickEvent event) { return; } break; - case 50: // Create folder + case 49: // Create folder (archive) + case 50: // Create folder (not archive) if (clickedItem.getType() == XMaterial.PLAYER_HEAD.get()) { XSound.ENTITY_CHICKEN_EGG.play(player); player.closeInventory(); // Close to allow chat input new PlayerChatInput(plugin, player, "enter_folder_name", input -> { - Folder folder = folderStorage.createFolder(input.trim()); + if (StringCleaner.hasInvalidNameCharacters(input)) { + Messages.sendMessage(player, "worlds_folder_creation_invalid_characters"); + } + + String sanitizedName = StringCleaner.sanitize(input); + if (sanitizedName.isEmpty()) { + Messages.sendMessage(player, "worlds_folder_creation_name_bank"); + return; + } + + if (folderStorage.folderExists(sanitizedName)) { + Messages.sendMessage(player, "worlds_folder_exists"); + return; + } + + Folder folder = createFolder(sanitizedName); Messages.sendMessage(player, "worlds_folder_created", new AbstractMap.SimpleEntry<>("%folder%", folder.getName())); - // Reopen inventory after chat input is done openInventory(); }); return; @@ -392,6 +408,10 @@ public void onInventoryClick(@NotNull InventoryClickEvent event) { } } + protected Folder createFolder(String folderName) { + return folderStorage.createFolder(folderName, category); + } + /** * Sends the player back to the previous inventory. */ @@ -454,7 +474,7 @@ private void handleDisplayableItemClick(@NotNull InventoryClickEvent event, @Not Folder folder = parseFolder(displayName); if (folder != null) { - new FolderContentInventory(plugin, player, folder, this, requiredVisibility, validStatuses).openInventory(); + new FolderContentInventory(plugin, player, category, folder, this, requiredVisibility, validStatuses).openInventory(); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java index 2e3d23cd..5b6df02a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java @@ -19,6 +19,7 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; @@ -29,9 +30,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; import org.jetbrains.annotations.NotNull; public class FolderContentInventory extends DisplayablesInventory { @@ -42,6 +45,7 @@ public class FolderContentInventory extends DisplayablesInventory { public FolderContentInventory( @NotNull BuildSystemPlugin plugin, @NotNull Player player, + @NotNull NavigatorCategory category, @NotNull Folder folder, @NotNull DisplayablesInventory parentInventory, @NotNull Visibility requiredVisibility, @@ -50,6 +54,7 @@ public FolderContentInventory( super( plugin, player, + category, Messages.getString("folder_title", player, new SimpleEntry<>("%folder%", folder.getName())), null, requiredVisibility, @@ -59,20 +64,45 @@ public FolderContentInventory( this.parentInventory = parentInventory; } + @Override + protected @NotNull Inventory createBaseInventoryPage(String inventoryTitle) { + return this.parentInventory.createBaseInventoryPage(inventoryTitle); + } + @Override protected @NotNull List collectDisplayables() { - WorldDisplay worldDisplay = settingsManager.getSettings(player).getWorldDisplay(); + WorldDisplay worldDisplay = this.settingsManager.getSettings(this.player).getWorldDisplay(); + + Collection folders = collectFolders(); Collection buildWorlds = filterWorlds(collectWorlds(), worldDisplay); - return new ArrayList<>(buildWorlds); + + List displayables = new ArrayList<>(); + displayables.addAll(folders); + displayables.addAll(buildWorlds); + return displayables; + } + + @Override + protected Collection collectFolders() { + return folderStorage.getFolders().stream() + .filter(folder -> folder.getCategory() == this.category) + .filter(folder -> Objects.equals(folder.getParent(), this.folder)) + .collect(Collectors.toList()); } @Override protected Collection collectWorlds() { - return folder.getWorldUUIDs().stream() - .map(worldStorage::getBuildWorld) + return this.folder.getWorldUUIDs().stream() + .map(this.worldStorage::getBuildWorld) + .filter(Objects::nonNull) .collect(Collectors.toList()); } + @Override + protected Folder createFolder(String folderName) { + return this.folderStorage.createFolder(folderName, this.category, this.folder); + } + @Override protected void returnToPreviousInventory() { this.parentInventory.openInventory(); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java index adfed445..bc206f37 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java @@ -19,6 +19,7 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; import org.bukkit.entity.Player; @@ -39,6 +40,7 @@ public PrivateWorldsInventory(@NotNull BuildSystemPlugin plugin, @NotNull Player super( plugin, player, + NavigatorCategory.PRIVATE, Messages.getString("private_title", player), Messages.getString("private_no_worlds", player), Visibility.PRIVATE diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java index 205b7bff..3546276a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java @@ -19,6 +19,7 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; import org.bukkit.entity.Player; @@ -39,6 +40,7 @@ public PublicWorldsInventory(@NotNull BuildSystemPlugin plugin, @NotNull Player super( plugin, player, + NavigatorCategory.PUBLIC, Messages.getString("world_navigator_title", player), Messages.getString("world_navigator_no_worlds", player), Visibility.PUBLIC diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java index 7e9c7e8b..ee858468 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java @@ -17,7 +17,7 @@ */ package de.eintosti.buildsystem.player; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorInventoryType; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.player.BuildPlayer; import de.eintosti.buildsystem.api.player.CachedValues; import de.eintosti.buildsystem.api.player.LogoutLocation; @@ -27,10 +27,6 @@ import javax.annotation.Nullable; import org.bukkit.Location; -/** - * @author einTosti - * @since 2.21.0 - */ public class BuildPlayerImpl implements BuildPlayer { private final UUID uuid; @@ -40,7 +36,7 @@ public class BuildPlayerImpl implements BuildPlayer { private BuildWorld cachedWorld; private LogoutLocation logoutLocation; private Location previousLocation; - private NavigatorInventoryType lastLookedAt; + private NavigatorCategory lastLookedAt; public BuildPlayerImpl(UUID uuid, Settings settings) { this.uuid = uuid; @@ -98,12 +94,12 @@ public void setPreviousLocation(Location location) { @Override @Nullable - public NavigatorInventoryType getLastLookedAt() { + public NavigatorCategory getLastLookedAt() { return lastLookedAt; } @Override - public void setLastLookedAt(NavigatorInventoryType type) { + public void setLastLookedAt(NavigatorCategory type) { this.lastLookedAt = type; } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/CachedValuesImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/CachedValuesImpl.java index bc46d576..8bbec331 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/CachedValuesImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/CachedValuesImpl.java @@ -22,10 +22,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -/** - * @author einTosti - * @since 2.21.0 - */ public class CachedValuesImpl implements CachedValues { private GameMode gameMode; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/LogoutLocationImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/LogoutLocationImpl.java index 21bfa5e1..ad6b3e26 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/LogoutLocationImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/LogoutLocationImpl.java @@ -21,10 +21,6 @@ import org.bukkit.Bukkit; import org.bukkit.Location; -/** - * @author einTosti - * @since 2.21.0 - */ public class LogoutLocationImpl implements LogoutLocation { private final String worldName; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/PlayerServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/PlayerServiceImpl.java index e858061a..70243172 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/PlayerServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/PlayerServiceImpl.java @@ -23,7 +23,7 @@ import com.cryptomorin.xseries.messages.ActionBar; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorInventoryType; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.player.BuildPlayer; import de.eintosti.buildsystem.api.player.CachedValues; import de.eintosti.buildsystem.api.player.PlayerService; @@ -31,6 +31,7 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; import de.eintosti.buildsystem.config.ConfigValues; +import de.eintosti.buildsystem.navigator.ArmorStandManager; import de.eintosti.buildsystem.player.settings.SettingsManager; import de.eintosti.buildsystem.storage.WorldStorageImpl; import de.eintosti.buildsystem.storage.factory.PlayerStorageFactory; @@ -54,8 +55,8 @@ public class PlayerServiceImpl implements PlayerService { - private static final double MIN_HEIGHT = -0.16453003708696978; - private static final double MAX_HEIGHT = 0.16481381407766063; + private static final double MIN_LOOK_HEIGHT = -0.16453003708696978; + private static final double MAX_LOOK_HEIGHT = 0.16481381407766063; private final BuildSystemPlugin plugin; private final ConfigValues configValues; @@ -261,9 +262,9 @@ private void checkForEntity() { BuildPlayer buildPlayer = playerStorage.getBuildPlayer(player.getUniqueId()); double lookedPosition = player.getEyeLocation().getDirection().getY(); - if (lookedPosition >= MIN_HEIGHT && lookedPosition <= MAX_HEIGHT) { - NavigatorInventoryType inventoryType = NavigatorInventoryType.matchInventoryType(player, getEntityName(player)); - NavigatorInventoryType lastLookedAt = buildPlayer.getLastLookedAt(); + if (lookedPosition >= MIN_LOOK_HEIGHT && lookedPosition <= MAX_LOOK_HEIGHT) { + NavigatorCategory inventoryType = ArmorStandManager.matchNavigatorCategory(player, getEntityName(player)); + NavigatorCategory lastLookedAt = buildPlayer.getLastLookedAt(); if (lastLookedAt == null || lastLookedAt != inventoryType) { buildPlayer.setLastLookedAt(inventoryType); @@ -322,7 +323,7 @@ private String getEntityName(Player player) { return entity.getCustomName(); } - private void sendTypeInfo(Player player, NavigatorInventoryType inventoryType) { + private void sendTypeInfo(Player player, NavigatorCategory inventoryType) { if (inventoryType == null) { ActionBar.clearActionBar(player); return; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java index c7d5ff29..87e5d2d9 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java @@ -18,6 +18,7 @@ package de.eintosti.buildsystem.storage; import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.storage.FolderStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; @@ -30,6 +31,7 @@ import java.util.function.Function; import java.util.logging.Logger; import java.util.stream.Collectors; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; public abstract class FolderStorageImpl implements FolderStorage { @@ -59,8 +61,13 @@ public Collection getFolders() { } @Override - public Folder createFolder(String folderName) { - Folder folder = new FolderImpl(this, folderName); + public Folder createFolder(String folderName, NavigatorCategory category) { + return createFolder(folderName, category, null); + } + + @Override + public Folder createFolder(String folderName, NavigatorCategory category, @Nullable Folder parent) { + Folder folder = new FolderImpl(this, folderName, category, parent); foldersByName.put(folderName, folder); return folder; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java index dffcee7a..c5bf94fc 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java @@ -19,6 +19,7 @@ import com.cryptomorin.xseries.XMaterial; import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.storage.FolderStorageImpl; import de.eintosti.buildsystem.world.display.FolderImpl; @@ -31,6 +32,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; import org.bukkit.configuration.ConfigurationSection; @@ -90,8 +92,11 @@ public void save(Collection folders) { public @NotNull Map serializeFolder(Folder folder) { Map serializedFolder = new HashMap<>(); + serializedFolder.put("category", folder.getCategory().name()); + serializedFolder.put("parent", folder.hasParent() ? folder.getParent().getName() : null); serializedFolder.put("material", folder.getIcon().name()); - serializedFolder.put("worlds", folder.getWorldUUIDs().stream().map(UUID::toString).collect(Collectors.toList())); + serializedFolder.put("worlds", + folder.getWorldUUIDs().stream().map(UUID::toString).collect(Collectors.toList())); return serializedFolder; } @@ -110,14 +115,30 @@ public Collection load() { return new ArrayList<>(); } - return folders.stream() + // First pass: Create all folders without parent references + Map loadedFolders = folders.stream() .map(this::loadFolder) - .collect(Collectors.toCollection(ArrayList::new)); + .collect(Collectors.toMap(Folder::getName, Function.identity())); + + // Second pass: Set up parent references + for (String folderName : folders) { + String parentName = config.getString(FOLDERS_KEY + "." + folderName + ".parent"); + if (parentName != null) { + Folder folder = loadedFolders.get(folderName); + Folder parent = loadedFolders.get(parentName); + if (folder != null && parent != null) { + folder.setParent(parent); + } + } + } + + return new ArrayList<>(loadedFolders.values()); } private Folder loadFolder(String folderName) { final String path = FOLDERS_KEY + "." + folderName; + NavigatorCategory category = NavigatorCategory.valueOf(config.getString(path + ".category")); XMaterial defaultMaterial = XMaterial.CHEST; XMaterial material = XMaterial.matchXMaterial(config.getString(path + ".material", defaultMaterial.name())).orElse(defaultMaterial); List worlds = config.getStringList(path + ".worlds").stream().map(UUID::fromString).collect(Collectors.toList()); @@ -125,9 +146,10 @@ private Folder loadFolder(String folderName) { return new FolderImpl( this, folderName, + category, + null, // Parent will be set in second pass material, - worlds - ); + worlds); } @Override diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/EmptyTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/EmptyTabComplete.java index 6af608b3..596ea18c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/EmptyTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/EmptyTabComplete.java @@ -25,11 +25,6 @@ import org.bukkit.command.TabCompleter; import org.jetbrains.annotations.NotNull; -/** - * The tab completer which is used for commands which do not have any arguments. - * - * @author einTosti - */ public class EmptyTabComplete implements TabCompleter { public EmptyTabComplete(BuildSystemPlugin plugin) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/StringCleaner.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/StringCleaner.java new file mode 100644 index 00000000..f5d3cdd8 --- /dev/null +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/StringCleaner.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2025, Thomas Meaney + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.eintosti.buildsystem.util; + +import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.config.ConfigValues; +import java.util.Arrays; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Nullable; + +public class StringCleaner { + + private static final BuildSystemPlugin PLUGIN = JavaPlugin.getPlugin(BuildSystemPlugin.class); + + public static final String INVALID_NAME_CHARACTERS = "[^A-Za-z\\d/_-]"; + + private StringCleaner() { + } + + private static String getConfigInvalidNameCharacters() { + return PLUGIN.getConfigValues().getInvalidNameCharacters(); + } + + /** + * Checks if the input string contains any invalid characters as defined by {@link #INVALID_NAME_CHARACTERS} and {@link ConfigValues#getInvalidNameCharacters()}. + * + * @param input The input string to check + * @return {@code true} if the input contains invalid characters, {@code false} otherwise + */ + public static boolean hasInvalidNameCharacters(String input) { + return Arrays.stream(input.split("")).anyMatch(c -> c.matches(INVALID_NAME_CHARACTERS) || c.matches(getConfigInvalidNameCharacters())); + } + + /** + * Finds the first invalid character in the input string based on the defined invalid characters. + * + * @param input The input string to check for invalid characters + * @return The first invalid character found, or {@code null} if no invalid characters are present + */ + @Nullable + public static String firstInvalidChar(String input) { + return Arrays.stream(input.split("")) + .filter(c -> c.matches(INVALID_NAME_CHARACTERS) || c.matches(getConfigInvalidNameCharacters())) + .findFirst() + .orElse(null); + } + + /** + * Sanitizes the input string by removing invalid characters, replacing spaces with underscores, and trimming whitespace. + * + * @param input The input string to sanitize + * @return A sanitized version of the input string + */ + public static String sanitize(String input) { + return input + .replaceAll(INVALID_NAME_CHARACTERS, "") + .replaceAll(getConfigInvalidNameCharacters(), "") + .replace(" ", "_") + .trim(); + } +} diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java index d1fb13a8..5f640511 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java @@ -32,13 +32,13 @@ import de.eintosti.buildsystem.storage.factory.WorldStorageFactory; import de.eintosti.buildsystem.util.FileUtils; import de.eintosti.buildsystem.util.PlayerChatInput; +import de.eintosti.buildsystem.util.StringCleaner; import de.eintosti.buildsystem.world.creation.BuildWorldCreatorImpl; import de.eintosti.buildsystem.world.creation.generator.CustomGeneratorImpl; import io.papermc.lib.PaperLib; import java.io.File; import java.util.AbstractMap; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -83,16 +83,11 @@ public FolderStorageImpl getFolderStorage() { public void startWorldNameInput(Player player, BuildWorldType worldType, @Nullable String template, boolean privateWorld) { player.closeInventory(); new PlayerChatInput(plugin, player, "enter_world_name", input -> { - boolean hasInvalidChar = Arrays.stream(input.split("")).anyMatch(c -> c.matches("[^A-Za-z\\d/_-]") || c.matches(configValues.getInvalidNameCharacters())); - if (hasInvalidChar) { + if (StringCleaner.hasInvalidNameCharacters(input)) { Messages.sendMessage(player, "worlds_world_creation_invalid_characters"); } - String worldName = input - .replaceAll("[^A-Za-z\\d/_-]", "") - .replaceAll(configValues.getInvalidNameCharacters(), "") - .replace(" ", "_") - .trim(); + String worldName = StringCleaner.sanitize(input); if (worldName.isEmpty()) { Messages.sendMessage(player, "worlds_world_creation_name_bank"); return; @@ -215,10 +210,7 @@ public void run() { return; } - String invalidChar = Arrays.stream(worldName.split("")) - .filter(c -> c.matches("[^A-Za-z\\d/_-]") || c.matches(plugin.getConfigValues().getInvalidNameCharacters())) - .findFirst() - .orElse(null); + String invalidChar = StringCleaner.firstInvalidChar(worldName); if (invalidChar != null) { Messages.sendMessage(player, "worlds_importall_invalid_character", new AbstractMap.SimpleEntry<>("%world%", worldName), @@ -301,16 +293,11 @@ public void renameWorld(Player player, BuildWorld buildWorld, String newName) { return; } - boolean hasInvalidChar = Arrays.stream(newName.split("")).anyMatch(c -> c.matches("[^A-Za-z\\d/_-]") || c.matches(configValues.getInvalidNameCharacters())); - if (hasInvalidChar) { + if (StringCleaner.hasInvalidNameCharacters(newName)) { Messages.sendMessage(player, "worlds_world_creation_invalid_characters"); } - String parsedNewName = newName - .replaceAll("[^A-Za-z\\d/_-]", "") - .replaceAll(configValues.getInvalidNameCharacters(), "") - .replace(" ", "_") - .trim(); - if (parsedNewName.isEmpty()) { + String sanitizedNewName = StringCleaner.sanitize(newName); + if (sanitizedNewName.isEmpty()) { Messages.sendMessage(player, "worlds_world_creation_name_bank"); return; } @@ -335,11 +322,11 @@ public void renameWorld(Player player, BuildWorld buildWorld, String newName) { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> this.worldStorage.delete(oldName)); File oldWorldFile = new File(Bukkit.getWorldContainer(), oldName); - File newWorldFile = new File(Bukkit.getWorldContainer(), parsedNewName); + File newWorldFile = new File(Bukkit.getWorldContainer(), sanitizedNewName); FileUtils.copy(oldWorldFile, newWorldFile); FileUtils.deleteDirectory(oldWorldFile); - buildWorld.setName(parsedNewName); + buildWorld.setName(sanitizedNewName); this.worldStorage.addBuildWorld(buildWorld); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> this.worldStorage.save(buildWorld)); @@ -367,7 +354,7 @@ public void renameWorld(Player player, BuildWorld buildWorld, String newName) { Messages.sendMessage(player, "worlds_rename_set", new AbstractMap.SimpleEntry<>("%oldName%", oldName), - new AbstractMap.SimpleEntry<>("%newName%", parsedNewName) + new AbstractMap.SimpleEntry<>("%newName%", sanitizedNewName) ); } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/voidgenerator/DeprecatedVoidGenerator.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/voidgenerator/DeprecatedVoidGenerator.java index 614dc196..1c7008a5 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/voidgenerator/DeprecatedVoidGenerator.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/voidgenerator/DeprecatedVoidGenerator.java @@ -24,9 +24,6 @@ /** * The {@link VoidGenerator} that is used for servers which run on Spigot 1.13-1.16. - * - * @author einTosti - * @since 2.18.2 */ @SuppressWarnings("deprecation") public class DeprecatedVoidGenerator extends VoidGenerator { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/voidgenerator/ModernVoidGenerator.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/voidgenerator/ModernVoidGenerator.java index d276395f..c6105534 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/voidgenerator/ModernVoidGenerator.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/voidgenerator/ModernVoidGenerator.java @@ -27,9 +27,6 @@ /** * The {@link VoidGenerator} that is used for servers which run on Spigot 1.17+ - * - * @author einTosti - * @since 2.18.2 */ public class ModernVoidGenerator extends VoidGenerator { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java index 0febd891..c6daabb1 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java @@ -19,6 +19,7 @@ import com.cryptomorin.xseries.XMaterial; import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.storage.FolderStorageImpl; @@ -28,6 +29,7 @@ import java.util.List; import java.util.UUID; import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; public class FolderImpl implements Folder { @@ -35,17 +37,21 @@ public class FolderImpl implements Folder { private final FolderStorageImpl folderStorage; private final String name; + private final NavigatorCategory category; private final List worlds; + private Folder parent; private XMaterial material; - public FolderImpl(FolderStorageImpl folderStorage, String name) { - this(folderStorage, name, XMaterial.CHEST, new ArrayList<>()); + public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, @Nullable Folder parent) { + this(folderStorage, name, category, parent, XMaterial.CHEST, new ArrayList<>()); } - public FolderImpl(FolderStorageImpl folderStorage, String name, XMaterial material, List worlds) { + public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, Folder parent, XMaterial material, List worlds) { this.folderStorage = folderStorage; this.name = name; + this.category = category; + this.parent = parent; this.worlds = worlds; this.material = material; } @@ -55,6 +61,27 @@ public String getName() { return this.name; } + @Override + public NavigatorCategory getCategory() { + return this.category; + } + + @Override + @Nullable + public Folder getParent() { + return this.parent; + } + + @Override + public boolean hasParent() { + return this.parent != null; + } + + @Override + public void setParent(@Nullable Folder parent) { + this.parent = parent; + } + @Override @Unmodifiable public List getWorldUUIDs() { From dfdbb4ecfee050002cafea76c854f99508e70033 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 18:17:23 +0200 Subject: [PATCH 04/22] Only allow worlds to be added to folders of the same navigator category, add tab completion for folder subcommand --- buildsystem-api/javadoc/overview.html | 8 +- .../buildsystem/api/player/BuildPlayer.java | 2 +- .../buildsystem/api/player/PlayerService.java | 6 +- .../api/storage/FolderStorage.java | 2 +- .../api/storage/PlayerStorage.java | 2 +- .../api/world/display/Displayable.java | 4 + .../buildsystem/api/world/display/Folder.java | 1 - .../display}/NavigatorCategory.java | 2 +- .../de/eintosti/buildsystem/Messages.java | 1 + .../subcommand/worlds/FolderSubCommand.java | 9 ++ .../listener/NavigatorListener.java | 2 +- .../navigator/ArmorStandManager.java | 2 +- .../inventory/ArchivedWorldsInventory.java | 6 +- .../inventory/CreatableWorldsInventory.java | 6 +- .../inventory/DisplayablesInventory.java | 2 +- .../inventory/FolderContentInventory.java | 2 +- .../inventory/PrivateWorldsInventory.java | 2 +- .../inventory/PublicWorldsInventory.java | 2 +- .../buildsystem/player/BuildPlayerImpl.java | 2 +- .../buildsystem/player/PlayerServiceImpl.java | 2 +- .../storage/FolderStorageImpl.java | 2 +- .../storage/yaml/YamlFolderStorage.java | 2 +- .../tabcomplete/WorldsTabComplete.java | 97 +++++++++++++------ .../buildsystem/world/display/FolderImpl.java | 2 +- gradle/libs.versions.toml | 2 +- 25 files changed, 106 insertions(+), 64 deletions(-) rename buildsystem-api/src/main/java/de/eintosti/buildsystem/api/{navigator/settings => world/display}/NavigatorCategory.java (97%) diff --git a/buildsystem-api/javadoc/overview.html b/buildsystem-api/javadoc/overview.html index 2c679fc3..1b9d9413 100644 --- a/buildsystem-api/javadoc/overview.html +++ b/buildsystem-api/javadoc/overview.html @@ -1,11 +1,11 @@

- BuildSystem is a utility plugin targeted towards build-teams. - It allows worlds to be easily managed and also provides handy tools to assist with building. + BuildSystem is a utility plugin targeted towards build-teams. + It allows worlds to be easily managed and also provides handy tools to assist with building.

Useful Links

\ No newline at end of file diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/BuildPlayer.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/BuildPlayer.java index 0fdf4adf..0f55a5e9 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/BuildPlayer.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/BuildPlayer.java @@ -17,9 +17,9 @@ */ package de.eintosti.buildsystem.api.player; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import java.util.UUID; import javax.annotation.Nullable; import org.bukkit.Location; diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/PlayerService.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/PlayerService.java index 239ae8fc..3b90d0c3 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/PlayerService.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/player/PlayerService.java @@ -50,8 +50,7 @@ public interface PlayerService { boolean isInBuildMode(Player player); /** - * Gets whether the given player is allowed to create a new {@link BuildWorld}.
- * This depends on the following factors: + * Gets whether the given player is allowed to create a new {@link BuildWorld}.
This depends on the following factors: *
    *
  • Is the maximum number of worlds set by the config less than the amount of existing worlds?
  • *
  • Is the maximum number of worlds created by the player less than the amount of worlds said player is allowed to create?
  • @@ -66,8 +65,7 @@ public interface PlayerService { /** * Returns the maximum amount of {@link BuildWorld}s a player can create. *

    - * If the player has the permission {@code buildsystem.admin}, unlimited worlds can be created. - * Otherwise, there are two different permissions to set said amount: + * If the player has the permission {@code buildsystem.admin}, unlimited worlds can be created. Otherwise, there are two different permissions to set said amount: *

    * To set the maximum of... *

      diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java index 56fa9069..3de010be 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java @@ -17,9 +17,9 @@ */ package de.eintosti.buildsystem.api.storage; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import java.util.Collection; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/PlayerStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/PlayerStorage.java index 2d75c6cc..565c7c03 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/PlayerStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/PlayerStorage.java @@ -34,7 +34,7 @@ public interface PlayerStorage extends Storage { /** * Creates a new {@link BuildPlayer} with the given uuid and settings. * - * @param uuid The uuid of the player + * @param uuid The uuid of the player * @param settings The settings of the player * @return The created build-player */ diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java index 109c29f3..ee8fc33d 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java @@ -20,6 +20,7 @@ import com.cryptomorin.xseries.XMaterial; import java.util.List; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -74,9 +75,12 @@ public interface Displayable { default ItemStack asItemStack(Player player) { ItemStack itemStack = getIcon().parseItem(); ItemMeta itemMeta = itemStack.getItemMeta(); + itemMeta.setDisplayName(getDisplayName(player)); itemMeta.setLore(getLore(player)); + itemMeta.addItemFlags(ItemFlag.values()); itemStack.setItemMeta(itemMeta); + return itemStack; } } \ No newline at end of file diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java index 9d9b6356..9008abf5 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java @@ -17,7 +17,6 @@ */ package de.eintosti.buildsystem.api.world.display; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import java.util.List; import java.util.UUID; diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorCategory.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/NavigatorCategory.java similarity index 97% rename from buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorCategory.java rename to buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/NavigatorCategory.java index 6c335e62..80c1632a 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/navigator/settings/NavigatorCategory.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/NavigatorCategory.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package de.eintosti.buildsystem.api.navigator.settings; +package de.eintosti.buildsystem.api.world.display; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.builder.Builder; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index 92c86ae7..d6ffe7e2 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -268,6 +268,7 @@ public static void createMessageFile() { setMessage(sb, "worlds_folder_unknown_world", "%prefix% &cUnknown world."); setMessage(sb, "worlds_folder_world_already_in_folder", "%prefix% &c%world% is already contained within %folder%."); setMessage(sb, "worlds_folder_world_already_in_another_folder", "%prefix% &c%world% is already contained within another folder."); + setMessage(sb, "worlds_folder_world_category_mismatch", "%prefix% &cThe folder's category (%folder_category%) does not match the world's category (%world_category%)."); setMessage(sb, "worlds_folder_world_not_in_folder", "%prefix% &c%world% is not contained within %folder%."); setMessage(sb, "worlds_folder_world_added_to_folder", "%prefix% &b%world% &7was &aadded &7to &b%folder%&7."); setMessage(sb, "worlds_folder_world_removed_from_folder", "%prefix% &b%world% &7was &cremoved &7from &b%folder%&7."); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java index a9a127ea..b1e093c2 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java @@ -22,6 +22,7 @@ import de.eintosti.buildsystem.api.storage.FolderStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.command.subcommand.Argument; import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete.WorldsArgument; @@ -78,6 +79,14 @@ public void execute(Player player, String[] args) { return; } + if (folder.getCategory() != NavigatorCategory.of(buildWorld)) { + Messages.sendMessage(player, "worlds_folder_world_category_mismatch", + new AbstractMap.SimpleEntry<>("%folder_category%", folder.getCategory().name()), + new AbstractMap.SimpleEntry<>("%world_category%", NavigatorCategory.of(buildWorld).name()) + ); + return; + } + folder.addWorld(buildWorld); Messages.sendMessage(player, "worlds_folder_world_added_to_folder", folderPlaceholder, worldPlaceholder); break; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java index 06fb2731..e013f602 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java @@ -23,13 +23,13 @@ import com.cryptomorin.xseries.inventory.XInventoryView; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.navigator.settings.NavigatorType; import de.eintosti.buildsystem.api.player.CachedValues; import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.storage.WorldStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.config.ConfigValues; import de.eintosti.buildsystem.navigator.ArmorStandManager; import de.eintosti.buildsystem.navigator.inventory.ArchivedWorldsInventory; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/ArmorStandManager.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/ArmorStandManager.java index 2880b007..b5d93a58 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/ArmorStandManager.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/ArmorStandManager.java @@ -19,7 +19,7 @@ import com.cryptomorin.xseries.profiles.builder.XSkull; import com.cryptomorin.xseries.profiles.objects.Profileable; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java index 2fa26bdd..1c3522f6 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/ArchivedWorldsInventory.java @@ -23,9 +23,9 @@ import com.google.common.collect.Sets; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.util.InventoryUtils; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; @@ -59,9 +59,7 @@ public ArchivedWorldsInventory(BuildSystemPlugin plugin, Player player) { private void addFolderCreateItem(Inventory inventory, Player player) { if (player.hasPermission("buildsystem.create.folder")) { - inventory.setItem(50, InventoryUtils.createSkull(Messages.getString("world_navigator_create_folder", player), Profileable.detect(CREATE_FOLDER_PROFILE))); - } else { - InventoryUtils.addGlassPane(player, inventory, 49); + inventory.setItem(49, InventoryUtils.createSkull(Messages.getString("world_navigator_create_folder", player), Profileable.detect(CREATE_FOLDER_PROFILE))); } } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java index 839e01db..7b991a25 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/CreatableWorldsInventory.java @@ -21,11 +21,11 @@ import com.google.common.collect.Sets; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.player.PlayerServiceImpl; import de.eintosti.buildsystem.util.InventoryUtils; import java.util.Set; @@ -93,16 +93,12 @@ private void addWorldCreateItem(Inventory inventory, Player player) { if (player.hasPermission(getWorldCreationPermission())) { inventory.setItem(48, InventoryUtils.createSkull(Messages.getString(getWorldCreationItemTitleKey(), player), Profileable.detect(CREATE_WORLD_PROFILE))); - } else { - InventoryUtils.addGlassPane(player, inventory, 48); } } private void addFolderCreateItem(Inventory inventory, Player player) { if (player.hasPermission("buildsystem.create.folder")) { inventory.setItem(50, InventoryUtils.createSkull(Messages.getString("world_navigator_create_folder", player), Profileable.detect(CREATE_FOLDER_PROFILE))); - } else { - InventoryUtils.addGlassPane(player, inventory, 50); } } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index 9386df90..087b7ac6 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -6,7 +6,6 @@ import com.cryptomorin.xseries.profiles.objects.Profileable; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; import de.eintosti.buildsystem.api.navigator.settings.WorldFilter; import de.eintosti.buildsystem.api.navigator.settings.WorldSort; @@ -17,6 +16,7 @@ import de.eintosti.buildsystem.api.world.data.WorldData; import de.eintosti.buildsystem.api.world.display.Displayable; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.player.PlayerServiceImpl; import de.eintosti.buildsystem.player.settings.SettingsManager; import de.eintosti.buildsystem.storage.FolderStorageImpl; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java index 5b6df02a..4bf70398 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java @@ -19,13 +19,13 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; import de.eintosti.buildsystem.api.world.display.Displayable; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collection; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java index bc206f37..087c7dd9 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PrivateWorldsInventory.java @@ -19,9 +19,9 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java index 3546276a..11f1a8c7 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/PublicWorldsInventory.java @@ -19,9 +19,9 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java index ee858468..563175df 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/BuildPlayerImpl.java @@ -17,12 +17,12 @@ */ package de.eintosti.buildsystem.player; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.player.BuildPlayer; import de.eintosti.buildsystem.api.player.CachedValues; import de.eintosti.buildsystem.api.player.LogoutLocation; import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import java.util.UUID; import javax.annotation.Nullable; import org.bukkit.Location; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/PlayerServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/PlayerServiceImpl.java index 70243172..c4374043 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/PlayerServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/PlayerServiceImpl.java @@ -23,13 +23,13 @@ import com.cryptomorin.xseries.messages.ActionBar; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.player.BuildPlayer; import de.eintosti.buildsystem.api.player.CachedValues; import de.eintosti.buildsystem.api.player.PlayerService; import de.eintosti.buildsystem.api.storage.PlayerStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.config.ConfigValues; import de.eintosti.buildsystem.navigator.ArmorStandManager; import de.eintosti.buildsystem.player.settings.SettingsManager; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java index 87e5d2d9..5a026585 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java @@ -18,10 +18,10 @@ package de.eintosti.buildsystem.storage; import de.eintosti.buildsystem.BuildSystemPlugin; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.storage.FolderStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.world.display.FolderImpl; import java.util.AbstractMap; import java.util.Collection; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java index c5bf94fc..fc22439d 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java @@ -19,8 +19,8 @@ import com.cryptomorin.xseries.XMaterial; import de.eintosti.buildsystem.BuildSystemPlugin; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.storage.FolderStorageImpl; import de.eintosti.buildsystem.world.display.FolderImpl; import java.io.File; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java index 5687a0a5..1410bbfb 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java @@ -19,12 +19,18 @@ import com.google.common.collect.Lists; import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.api.storage.FolderStorage; import de.eintosti.buildsystem.api.storage.WorldStorage; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.WorldService; import de.eintosti.buildsystem.api.world.builder.Builders; import de.eintosti.buildsystem.api.world.creation.generator.Generator; import de.eintosti.buildsystem.api.world.data.BuildWorldType; +import de.eintosti.buildsystem.api.world.display.Displayable; +import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.command.subcommand.Argument; +import de.eintosti.buildsystem.util.StringCleaner; import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -44,9 +50,12 @@ public class WorldsTabComplete extends ArgumentSorter implements TabCompleter { private final WorldStorage worldStorage; + private final FolderStorage folderStorage; public WorldsTabComplete(BuildSystemPlugin plugin) { - this.worldStorage = plugin.getWorldService().getWorldStorage(); + WorldService worldService = plugin.getWorldService(); + this.worldStorage = worldService.getWorldStorage(); + this.folderStorage = worldService.getFolderStorage(); plugin.getCommand("worlds").setTabCompleter(this); } @@ -121,19 +130,15 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman } Builders builders = buildWorld.getBuilders(); if (builders.isCreator(player)) { - for (String builderName : builders.getBuilderNames()) { - addArgument(args[1], builderName, arrayList); - } + builders.getBuilderNames().forEach(builderName -> addArgument(args[1], builderName, arrayList)); } break; } case "import": { String[] directories = Bukkit.getWorldContainer().list((dir, name) -> { - for (String charString : name.split("")) { - if (charString.matches("[^A-Za-z0-9/_-]")) { - return false; - } + if (StringCleaner.hasInvalidNameCharacters(name)) { + return false; } File worldFolder = new File(dir, name); @@ -157,34 +162,66 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman } break; } + + case "folder": { + folderStorage.getFolders().stream() + .map(Displayable::getName) + .forEach(folderName -> addArgument(args[1], folderName, arrayList)); + break; + } } return arrayList; } default: - // Add arguments to /worlds import - if (!args[0].equalsIgnoreCase("import")) { - return arrayList; - } + switch (args[0].toLowerCase(Locale.ROOT)) { + case "import": { + Map> arguments = new HashMap>() {{ + put("-g", Arrays.stream(Generator.values()).filter(generator -> generator != Generator.CUSTOM) + .map(Enum::name) + .collect(Collectors.toList()) + ); + put("-c", Lists.newArrayList()); + put("-t", Arrays.stream(BuildWorldType.values()).map(Enum::name).collect(Collectors.toList())); + }}; + + if (args.length % 2 == 1) { + arguments.keySet().stream() + .filter(key -> !Lists.newArrayList(args).contains(key)) + .forEach(argument -> addArgument(args[args.length - 1], argument, arrayList)); + } else { + List values = arguments.get(args[args.length - 2]); + if (values != null) { + for (String argument : values) { + addArgument(args[args.length - 1], argument, arrayList); + } + } + } + break; + } - Map> arguments = new HashMap>() {{ - put("-g", Arrays.stream(Generator.values()).filter(generator -> generator != Generator.CUSTOM) - .map(Enum::name) - .collect(Collectors.toList()) - ); - put("-c", Lists.newArrayList()); - put("-t", Arrays.stream(BuildWorldType.values()).map(Enum::name).collect(Collectors.toList())); - }}; - - if (args.length % 2 == 1) { - arguments.keySet().stream() - .filter(key -> !Lists.newArrayList(args).contains(key)) - .forEach(argument -> addArgument(args[args.length - 1], argument, arrayList)); - } else { - List values = arguments.get(args[args.length - 2]); - if (values != null) { - for (String argument : values) { - addArgument(args[args.length - 1], argument, arrayList); + case "folder": { + switch (args.length) { + case 3: + arrayList.addAll(Arrays.asList("add", "remove")); + break; + case 4: + Folder folder = folderStorage.getFolder(args[1]); + if (folder == null) { + return arrayList; + } + worldStorage.getBuildWorlds().stream() + .filter(world -> NavigatorCategory.of(world) == folder.getCategory()) + .filter(world -> { + if (args[2].equalsIgnoreCase("add")) { + return !folderStorage.isAssignedToAnyFolder(world); + } else if (args[2].equalsIgnoreCase("remove")) { + return folder.containsWorld(world); + } + return false; + }) + .forEach(world -> addArgument(args[3], world.getName(), arrayList)); + break; } } } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java index c6daabb1..30944484 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java @@ -19,9 +19,9 @@ import com.cryptomorin.xseries.XMaterial; import de.eintosti.buildsystem.Messages; -import de.eintosti.buildsystem.api.navigator.settings.NavigatorCategory; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; +import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.storage.FolderStorageImpl; import java.util.AbstractMap; import java.util.ArrayList; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0e06ca9f..6e0f073c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ paperlib = { group = "io.papermc", name = "paperlib", version.ref = "paperlib" } luckperms = { group = "net.luckperms", name = "api", version.ref = "luckperms" } placeholderapi = { group = "me.clip", name = "placeholderapi", version.ref = "placeholderapi" } worldedit = { group = "com.sk89q.worldedit", name = "worldedit-core", version.ref = "worldedit" } -axiompaper= {group = "maven.modrinth.workaround", name = "axiom-paper-plugin", version.ref="axiompaper"} +axiompaper = { group = "maven.modrinth.workaround", name = "axiom-paper-plugin", version.ref = "axiompaper" } # Third Party annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" } From 16d52ced8b813e7cc86ad7bcf7812b3a8d62071f Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 19:01:10 +0200 Subject: [PATCH 05/22] Use `WorldPermissionsImpl.of(buildWorld)` for permissions where world might be `null` --- .../command/subcommand/worlds/AddBuilderSubCommand.java | 3 ++- .../command/subcommand/worlds/RemoveBuilderSubCommand.java | 3 ++- .../command/subcommand/worlds/RenameSubCommand.java | 3 ++- .../command/subcommand/worlds/SetCreatorSubCommand.java | 3 ++- .../command/subcommand/worlds/SetItemSubCommand.java | 3 ++- .../command/subcommand/worlds/SetPermissionSubCommand.java | 3 ++- .../command/subcommand/worlds/SetProjectSubCommand.java | 3 ++- .../command/subcommand/worlds/SetStatusSubCommand.java | 3 ++- .../command/subcommand/worlds/UnimportSubCommand.java | 3 ++- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/AddBuilderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/AddBuilderSubCommand.java index 280ba552..daaef187 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/AddBuilderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/AddBuilderSubCommand.java @@ -29,6 +29,7 @@ import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; import de.eintosti.buildsystem.util.PlayerChatInput; import de.eintosti.buildsystem.util.UUIDFetcher; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import java.util.AbstractMap; import java.util.UUID; import org.bukkit.Bukkit; @@ -43,7 +44,7 @@ public class AddBuilderSubCommand implements SubCommand { public AddBuilderSubCommand(BuildSystemPlugin plugin, String worldName) { this.plugin = plugin; this.buildWorld = plugin.getWorldService().getWorldStorage().getBuildWorld(worldName); - this.permissions = buildWorld.getPermissions(); + this.permissions = WorldPermissionsImpl.of(buildWorld); } @Override diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveBuilderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveBuilderSubCommand.java index 2016974e..50838596 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveBuilderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveBuilderSubCommand.java @@ -28,6 +28,7 @@ import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; import de.eintosti.buildsystem.util.PlayerChatInput; import de.eintosti.buildsystem.util.UUIDFetcher; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import java.util.AbstractMap; import java.util.UUID; import org.bukkit.Bukkit; @@ -42,7 +43,7 @@ public class RemoveBuilderSubCommand implements SubCommand { public RemoveBuilderSubCommand(BuildSystemPlugin plugin, String worldName) { this.plugin = plugin; this.buildWorld = plugin.getWorldService().getWorldStorage().getBuildWorld(worldName); - this.permissions = buildWorld.getPermissions(); + this.permissions = WorldPermissionsImpl.of(buildWorld); } @Override diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RenameSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RenameSubCommand.java index 840e0f0b..7e6dbca2 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RenameSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RenameSubCommand.java @@ -25,6 +25,7 @@ import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; import de.eintosti.buildsystem.util.PlayerChatInput; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import org.bukkit.entity.Player; public class RenameSubCommand implements SubCommand { @@ -39,7 +40,7 @@ public RenameSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (!buildWorld.getPermissions().canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetCreatorSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetCreatorSubCommand.java index 5700ca00..532582c7 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetCreatorSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetCreatorSubCommand.java @@ -27,6 +27,7 @@ import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; import de.eintosti.buildsystem.util.PlayerChatInput; import de.eintosti.buildsystem.util.UUIDFetcher; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import java.util.AbstractMap; import org.bukkit.entity.Player; @@ -42,7 +43,7 @@ public SetCreatorSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (!buildWorld.getPermissions().canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetItemSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetItemSubCommand.java index 16175b85..97225e4e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetItemSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetItemSubCommand.java @@ -24,6 +24,7 @@ import de.eintosti.buildsystem.command.subcommand.Argument; import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import java.util.AbstractMap; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -41,7 +42,7 @@ public SetItemSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (!buildWorld.getPermissions().canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetPermissionSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetPermissionSubCommand.java index 7a842c38..8c7f92a9 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetPermissionSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetPermissionSubCommand.java @@ -25,6 +25,7 @@ import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; import de.eintosti.buildsystem.util.PlayerChatInput; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import java.util.AbstractMap; import org.bukkit.entity.Player; @@ -40,7 +41,7 @@ public SetPermissionSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (!buildWorld.getPermissions().canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetProjectSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetProjectSubCommand.java index fefa65d2..2a247365 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetProjectSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetProjectSubCommand.java @@ -25,6 +25,7 @@ import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; import de.eintosti.buildsystem.util.PlayerChatInput; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import java.util.AbstractMap; import org.bukkit.entity.Player; @@ -40,7 +41,7 @@ public SetProjectSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (!buildWorld.getPermissions().canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetStatusSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetStatusSubCommand.java index eecde751..1f43038a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetStatusSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetStatusSubCommand.java @@ -23,6 +23,7 @@ import de.eintosti.buildsystem.command.subcommand.Argument; import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import org.bukkit.entity.Player; public class SetStatusSubCommand implements SubCommand { @@ -37,7 +38,7 @@ public SetStatusSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (!buildWorld.getPermissions().canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/UnimportSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/UnimportSubCommand.java index cd9e9bff..ab82cd24 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/UnimportSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/UnimportSubCommand.java @@ -23,6 +23,7 @@ import de.eintosti.buildsystem.command.subcommand.Argument; import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import java.util.AbstractMap; import org.bukkit.entity.Player; @@ -38,7 +39,7 @@ public UnimportSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (!buildWorld.getPermissions().canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } From f15c8bec3a1ec546f3ab509365ce659f144298a7 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 19:36:56 +0200 Subject: [PATCH 06/22] Add permissions to folders, add the ability to alter folder icons --- .../buildsystem/api/world/data/WorldData.java | 2 +- .../buildsystem/api/world/display/Folder.java | 23 ++++++ .../buildsystem/BuildSystemPlugin.java | 1 + .../de/eintosti/buildsystem/Messages.java | 4 + .../subcommand/worlds/FolderSubCommand.java | 78 +++++++++++++++---- .../buildsystem/config/ConfigValues.java | 8 ++ .../storage/yaml/YamlFolderStorage.java | 6 +- .../storage/yaml/YamlWorldStorage.java | 2 +- .../tabcomplete/WorldsTabComplete.java | 7 +- .../buildsystem/world/data/WorldDataImpl.java | 72 +++++++++++------ .../buildsystem/world/display/FolderImpl.java | 38 +++++++-- .../src/main/resources/config.yml | 4 +- 12 files changed, 198 insertions(+), 47 deletions(-) diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/WorldData.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/WorldData.java index c1112e3e..b8ebd99f 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/WorldData.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/data/WorldData.java @@ -48,7 +48,7 @@ public interface WorldData { /** * Gets the permission needed to enter the {@link BuildWorld}. * - * @return The permission + * @return The permission, or '{@code -}' if no permission is required */ Type permission(); diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java index 9008abf5..de5b7eda 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java @@ -20,6 +20,7 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import java.util.List; import java.util.UUID; +import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; @@ -93,4 +94,26 @@ public interface Folder extends Displayable { * @return The number of worlds in this folder */ int getWorldCount(); + + /** + * Gets the permission required to access this folder. + * + * @return The permission, or '{@code -}' if no permission is required + */ + String getPermission(); + + /** + * Sets the permission required to access this folder. + * + * @param permission The permission to set, or '{@code -}' to remove the permission + */ + void setPermission(String permission); + + /** + * Gets whether the folder is visible to the specified player. + * + * @param player The player to check + * @return {@code true} if the player can view the folder, {@code false} otherwise + */ + boolean canView(Player player); } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java index 3323cf07..fc95474d 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java @@ -356,6 +356,7 @@ private int getPlayersWithNavigator(NavigatorType navigatorType) { .count(); } })); + metrics.addCustomChart(new SimplePie("folder_override_permissions", () -> String.valueOf(configValues.isFolderOverridePermissions()))); } private void registerExpansions() { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index d6ffe7e2..b607f574 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -276,6 +276,8 @@ public static void createMessageFile() { setMessage(sb, "worlds_folder_creation_invalid_characters", "%prefix% &7&oRemoved invalid characters from folder name."); setMessage(sb, "worlds_folder_creation_name_bank", "%prefix% &cThe folder name cannot be blank."); setMessage(sb, "worlds_folder_created", "%prefix% &7The folder &b%folder% &7was &asuccessfully &7created."); + setMessage(sb, "worlds_folder_permission_set", "%prefix% &b%folder%&7's permission was successfully changed."); + setMessage(sb, "worlds_folder_item_set", "%prefix% &b%folder%&7's item was successfully changed."); addSpacer(sb, ""); setMessage(sb, "worlds_help_usage", "%prefix% &7Usage: &b/worlds help [page]"); setMessage(sb, "worlds_help_invalid_page", "%prefix% &cInvalid page."); @@ -468,6 +470,8 @@ public static void createMessageFile() { setMessage(sb, "folder_title", "&3» &8%folder%"); setMessage(sb, "folder_item_title", "&3&l%folder%"); setMessage(sb, "folder_item_lore", Arrays.asList( + "&7Permission&8: &b%permission%", + "", "&7Worlds&8: &b%worlds%" )); setMessage(sb, "world_item_builders_builder_template", "&b%builder%&7, "); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java index b1e093c2..5f598828 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java @@ -17,6 +17,8 @@ */ package de.eintosti.buildsystem.command.subcommand.worlds; +import com.cryptomorin.xseries.XMaterial; +import com.cryptomorin.xseries.XSound; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.storage.FolderStorage; @@ -26,30 +28,34 @@ import de.eintosti.buildsystem.command.subcommand.Argument; import de.eintosti.buildsystem.command.subcommand.SubCommand; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete.WorldsArgument; +import de.eintosti.buildsystem.util.PlayerChatInput; import de.eintosti.buildsystem.world.WorldServiceImpl; import java.util.AbstractMap; import java.util.Locale; import java.util.Map.Entry; +import org.bukkit.Material; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; public class FolderSubCommand implements SubCommand { + private final BuildSystemPlugin plugin; private final WorldServiceImpl worldService; public FolderSubCommand(BuildSystemPlugin plugin) { + this.plugin = plugin; this.worldService = plugin.getWorldService(); } @Override public void execute(Player player, String[] args) { - if (args.length != 4) { + if (args.length < 2) { Messages.sendMessage(player, "worlds_folder_usage"); return; } String folderName = args[1]; - String operation = args[2].toLowerCase(Locale.ROOT); - String worldName = args[3]; + String operation = args.length > 2 ? args[2].toLowerCase(Locale.ROOT) : ""; FolderStorage folderStorage = worldService.getFolderStorage(); Folder folder = folderStorage.getFolder(folderName); @@ -58,6 +64,35 @@ public void execute(Player player, String[] args) { return; } + switch (operation) { + case "add": + case "remove": { + if (args.length != 4) { + Messages.sendMessage(player, "worlds_folder_usage"); + return; + } + handleWorldFolderOperation(player, folder, operation, args[3]); + break; + } + + case "setpermission": { + handlePermissionInput(player, folder); + break; + } + + case "setitem": { + handleIconChange(player, folder); + break; + } + + default: { + Messages.sendMessage(player, "worlds_folder_usage"); + break; + } + } + } + + private void handleWorldFolderOperation(Player player, Folder folder, String operation, String worldName) { BuildWorld buildWorld = worldService.getWorldStorage().getBuildWorld(worldName); if (buildWorld == null) { Messages.sendMessage(player, "worlds_folder_unknown_world"); @@ -68,13 +103,13 @@ public void execute(Player player, String[] args) { Entry worldPlaceholder = new AbstractMap.SimpleEntry<>("%world%", buildWorld.getName()); switch (operation) { - case "add": { + case "add": if (folder.containsWorld(buildWorld)) { Messages.sendMessage(player, "worlds_folder_world_already_in_folder", folderPlaceholder, worldPlaceholder); return; } - if (folderStorage.isAssignedToAnyFolder(buildWorld)) { + if (worldService.getFolderStorage().isAssignedToAnyFolder(buildWorld)) { Messages.sendMessage(player, "worlds_folder_world_already_in_another_folder", worldPlaceholder); return; } @@ -90,9 +125,8 @@ public void execute(Player player, String[] args) { folder.addWorld(buildWorld); Messages.sendMessage(player, "worlds_folder_world_added_to_folder", folderPlaceholder, worldPlaceholder); break; - } - case "remove": { + case "remove": if (!folder.containsWorld(buildWorld)) { Messages.sendMessage(player, "worlds_folder_world_not_in_folder", folderPlaceholder, worldPlaceholder); return; @@ -101,17 +135,35 @@ public void execute(Player player, String[] args) { folder.removeWorld(buildWorld); Messages.sendMessage(player, "worlds_folder_world_removed_from_folder", folderPlaceholder, worldPlaceholder); break; - } + } + } - default: { - Messages.sendMessage(player, "worlds_folder_usage"); - break; - } + private void handlePermissionInput(Player player, Folder folder) { + new PlayerChatInput(plugin, player, "enter_world_permission", input -> { + folder.setPermission(input.trim()); + + XSound.ENTITY_PLAYER_LEVELUP.play(player); + Messages.sendMessage(player, "worlds_folder_permission_set", + new AbstractMap.SimpleEntry<>("%folder%", folder.getName()) + ); + }); + } + + private void handleIconChange(Player player, Folder folder) { + ItemStack itemStack = player.getItemInHand(); + if (itemStack.getType() == Material.AIR) { + Messages.sendMessage(player, "worlds_setitem_hand_empty"); + return; } + + folder.setIcon(XMaterial.matchXMaterial(itemStack)); + Messages.sendMessage(player, "worlds_folder_item_set", + new AbstractMap.SimpleEntry<>("%folder%", folder.getName()) + ); } @Override public Argument getArgument() { return WorldsArgument.FOLDER; } -} \ No newline at end of file +} diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/config/ConfigValues.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/config/ConfigValues.java index d0874f53..e52dfb3d 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/config/ConfigValues.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/config/ConfigValues.java @@ -72,6 +72,7 @@ public class ConfigValues { private boolean[] worldBuildersEnabled; private boolean saveFromDeath; private boolean teleportToMapSpawn; + private boolean folderOverridePermissions; private int sunriseTime; private int noonTime; @@ -163,6 +164,9 @@ public void setConfigValues() { this.maxPrivateWorldAmount = config.getInt("world.max-amount.private", -1); this.voidBlock = config.getBoolean("world.void-block", true); + + // Folder + this.folderOverridePermissions = config.getBoolean("folder.override-permissions", true); } /** @@ -375,4 +379,8 @@ public boolean isTeleportToMapSpawn() { public String getInvalidNameCharacters() { return invalidNameCharacters; } + + public boolean isFolderOverridePermissions() { + return folderOverridePermissions; + } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java index fc22439d..4e366ef0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java @@ -95,8 +95,8 @@ public void save(Collection folders) { serializedFolder.put("category", folder.getCategory().name()); serializedFolder.put("parent", folder.hasParent() ? folder.getParent().getName() : null); serializedFolder.put("material", folder.getIcon().name()); - serializedFolder.put("worlds", - folder.getWorldUUIDs().stream().map(UUID::toString).collect(Collectors.toList())); + serializedFolder.put("permission", folder.getPermission()); + serializedFolder.put("worlds", folder.getWorldUUIDs().stream().map(UUID::toString).collect(Collectors.toList())); return serializedFolder; } @@ -141,6 +141,7 @@ private Folder loadFolder(String folderName) { NavigatorCategory category = NavigatorCategory.valueOf(config.getString(path + ".category")); XMaterial defaultMaterial = XMaterial.CHEST; XMaterial material = XMaterial.matchXMaterial(config.getString(path + ".material", defaultMaterial.name())).orElse(defaultMaterial); + String permission = config.getString(path + ".permission", "-"); List worlds = config.getStringList(path + ".worlds").stream().map(UUID::fromString).collect(Collectors.toList()); return new FolderImpl( @@ -149,6 +150,7 @@ private Folder loadFolder(String folderName) { category, null, // Parent will be set in second pass material, + permission, worlds); } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java index 3a896c82..121406ee 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java @@ -209,7 +209,7 @@ private WorldDataImpl parseWorldData(String worldName) { return new WorldDataImpl( worldName, customSpawn, permission, project, difficulty, material, worldStatus, blockBreaking, blockInteractions, blockPlacement, buildersEnabled, explosions, mobAi, physics, privateWorld, - lastLoaded, lastUnloaded, lastEdited + lastLoaded, lastUnloaded, lastEdited, plugin.getConfigValues() ); } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java index 1410bbfb..c1fd2d65 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java @@ -203,13 +203,18 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman case "folder": { switch (args.length) { case 3: - arrayList.addAll(Arrays.asList("add", "remove")); + Arrays.asList("add", "remove", "setPermission", "setItem").forEach(arg -> addArgument(arg, arg, arrayList)); break; case 4: + if (!args[2].equalsIgnoreCase("add") && !args[2].equalsIgnoreCase("remove")) { + return arrayList; + } + Folder folder = folderStorage.getFolder(args[1]); if (folder == null) { return arrayList; } + worldStorage.getBuildWorlds().stream() .filter(world -> NavigatorCategory.of(world) == folder.getCategory()) .filter(world -> { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/data/WorldDataImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/data/WorldDataImpl.java index e51adfe2..eb5d51cd 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/data/WorldDataImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/data/WorldDataImpl.java @@ -18,19 +18,26 @@ package de.eintosti.buildsystem.world.data; import com.cryptomorin.xseries.XMaterial; +import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.WorldService; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.WorldData; +import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.config.ConfigValues; import java.util.HashMap; import java.util.Map; import org.bukkit.Bukkit; import org.bukkit.Difficulty; import org.bukkit.Location; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class WorldDataImpl implements WorldData { + private final ConfigValues configValues; + private final Map> data = new HashMap<>(); private final Type customSpawn = register("spawn"); @@ -57,28 +64,27 @@ public class WorldDataImpl implements WorldData { private String worldName; public WorldDataImpl(String worldName, boolean privateWorld, XMaterial material, ConfigValues configValues) { - this.customSpawn.set(null); - this.permission.set(configValues.getDefaultPermission(privateWorld).replace("%world%", worldName)); - this.project.set("-"); - - this.difficulty.set(configValues.getWorldDifficulty()); - this.material.set(material); - this.status.set(BuildWorldStatus.NOT_STARTED); - - this.blockBreaking.set(configValues.isWorldBlockBreaking()); - this.blockInteractions.set(configValues.isWorldBlockInteractions()); - this.blockPlacement.set(configValues.isWorldBlockPlacement()); - this.buildersEnabled.set(configValues.isWorldBuildersEnabled(privateWorld)); - this.explosions.set(configValues.isWorldExplosions()); - this.mobAi.set(configValues.isWorldMobAi()); - this.physics.set(configValues.isWorldPhysics()); - this.privateWorld.set(privateWorld); - - this.lastEdited.set((long) -1); - this.lastLoaded.set((long) -1); - this.lastUnloaded.set((long) -1); - - this.worldName = worldName; + this( + worldName, + null, + configValues.getDefaultPermission(privateWorld).replace("%world%", worldName), + "-", + configValues.getWorldDifficulty(), + material, + BuildWorldStatus.NOT_STARTED, + configValues.isWorldBlockBreaking(), + configValues.isWorldBlockInteractions(), + configValues.isWorldBlockPlacement(), + configValues.isWorldBuildersEnabled(privateWorld), + configValues.isWorldExplosions(), + configValues.isWorldMobAi(), + configValues.isWorldPhysics(), + privateWorld, + -1L, + -1L, + -1L, + configValues + ); } public WorldDataImpl( @@ -99,7 +105,8 @@ public WorldDataImpl( boolean privateWorld, long lastLoaded, long lastUnloaded, - long lastEdited + long lastEdited, + ConfigValues configValues ) { this.customSpawn.set(customSpawn); this.permission.set(permission); @@ -123,6 +130,7 @@ public WorldDataImpl( this.lastUnloaded.set(lastUnloaded); this.worldName = worldName; + this.configValues = configValues; } public Type register(@NotNull String key) { @@ -160,6 +168,16 @@ public Location getCustomSpawnLocation() { @Override public Type permission() { + if (configValues.isFolderOverridePermissions()) { + WorldService worldService = JavaPlugin.getPlugin(BuildSystemPlugin.class).getWorldService(); + BuildWorld buildWorld = worldService.getWorldStorage().getBuildWorld(worldName); + if (buildWorld != null) { + Folder assignedFolder = worldService.getFolderStorage().getAssignedFolder(buildWorld); + if (assignedFolder != null) { + return new TypeImpl<>(assignedFolder.getPermission()); + } + } + } return permission; } @@ -251,6 +269,14 @@ public static class TypeImpl implements Type { private T value; + public TypeImpl() { + this.value = null; + } + + public TypeImpl(T value) { + this.value = value; + } + @Override public T get() { return value; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java index 30944484..afe9f1e2 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java @@ -22,7 +22,9 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.api.world.display.NavigatorCategory; +import de.eintosti.buildsystem.api.world.util.WorldPermissions; import de.eintosti.buildsystem.storage.FolderStorageImpl; +import de.eintosti.buildsystem.world.util.WorldPermissionsImpl; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; @@ -42,18 +44,20 @@ public class FolderImpl implements Folder { private Folder parent; private XMaterial material; + private String permission; public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, @Nullable Folder parent) { - this(folderStorage, name, category, parent, XMaterial.CHEST, new ArrayList<>()); + this(folderStorage, name, category, parent, XMaterial.CHEST, "-", new ArrayList<>()); } - public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, Folder parent, XMaterial material, List worlds) { + public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, Folder parent, XMaterial material, String permission, List worlds) { this.folderStorage = folderStorage; this.name = name; this.category = category; this.parent = parent; this.worlds = worlds; this.material = material; + this.permission = permission; } @Override @@ -120,6 +124,31 @@ public void setIcon(XMaterial material) { this.material = material; } + @Override + public String getPermission() { + return this.permission; + } + + @Override + public void setPermission(String permission) { + this.permission = permission; + } + + @Override + public boolean canView(Player player) { + // We can pass null as a world since we are only checking for bypass permissions + WorldPermissions permissions = WorldPermissionsImpl.of(null); + if (permissions.hasAdminPermission(player) || permissions.canBypassViewPermission(player)) { + return true; + } + + if (this.permission.equals("-")) { + return true; + } + + return player.hasPermission(permission); + } + @Override public String getDisplayName(Player player) { return Messages.getString("folder_item_title", player, @@ -129,11 +158,10 @@ public String getDisplayName(Player player) { @Override public List getLore(Player player) { - List lore = new ArrayList<>(); - lore.add(Messages.getString("folder_item_lore", player, + return new ArrayList<>(Messages.getStringList("folder_item_lore", player, + new AbstractMap.SimpleEntry<>("%permission%", permission), new AbstractMap.SimpleEntry<>("%worlds%", String.valueOf(getWorldCount()))) ); - return lore; } @Override diff --git a/buildsystem-core/src/main/resources/config.yml b/buildsystem-core/src/main/resources/config.yml index 1d9069cc..89108bee 100644 --- a/buildsystem-core/src/main/resources/config.yml +++ b/buildsystem-core/src/main/resources/config.yml @@ -61,4 +61,6 @@ world: - world - world_nether - worth_the_end - void-block: true \ No newline at end of file + void-block: true +folder: + override-permissions: true \ No newline at end of file From 0752ca450c043cea8c8f8f1a41144c4b9da8f7e4 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 21:19:57 +0200 Subject: [PATCH 07/22] Fix permission check sometimes being inverted --- .../command/subcommand/worlds/BuildersSubCommand.java | 2 +- .../command/subcommand/worlds/DeleteSubCommand.java | 2 +- .../buildsystem/command/subcommand/worlds/EditSubCommand.java | 2 +- .../command/subcommand/worlds/FolderSubCommand.java | 2 ++ .../buildsystem/command/subcommand/worlds/InfoSubCommand.java | 2 +- .../command/subcommand/worlds/RemoveSpawnSubCommand.java | 2 +- .../command/subcommand/worlds/SetSpawnSubCommand.java | 2 +- .../buildsystem/navigator/inventory/DisplayablesInventory.java | 3 ++- 8 files changed, 10 insertions(+), 7 deletions(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/BuildersSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/BuildersSubCommand.java index 78d3298d..98e0a424 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/BuildersSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/BuildersSubCommand.java @@ -38,7 +38,7 @@ public BuildersSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/DeleteSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/DeleteSubCommand.java index 38856735..3533e703 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/DeleteSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/DeleteSubCommand.java @@ -38,7 +38,7 @@ public DeleteSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/EditSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/EditSubCommand.java index d5054d52..2a4f2f5d 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/EditSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/EditSubCommand.java @@ -40,7 +40,7 @@ public EditSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java index 5f598828..a115267a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java @@ -54,6 +54,8 @@ public void execute(Player player, String[] args) { return; } + //TODO: Permissions check + String folderName = args[1]; String operation = args.length > 2 ? args[2].toLowerCase(Locale.ROOT) : ""; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java index 40a2c641..0fcdb9af 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java @@ -43,7 +43,7 @@ public InfoSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { - if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveSpawnSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveSpawnSubCommand.java index be2b2c72..f61e2b4b 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveSpawnSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveSpawnSubCommand.java @@ -38,7 +38,7 @@ public RemoveSpawnSubCommand(BuildSystemPlugin plugin) { @Override public void execute(Player player, String[] args) { BuildWorld buildWorld = plugin.getWorldService().getWorldStorage().getBuildWorld(player.getWorld()); - if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java index 2644324e..f3f1061c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java @@ -39,7 +39,7 @@ public SetSpawnSubCommand(BuildSystemPlugin plugin) { @Override public void execute(Player player, String[] args) { BuildWorld buildWorld = plugin.getWorldService().getWorldStorage().getBuildWorld(player.getWorld()); - if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { + if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { plugin.sendPermissionMessage(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index 087b7ac6..7f4b8677 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -217,6 +217,7 @@ protected Collection collectFolders() { return folderStorage.getFolders().stream() .filter(folder -> folder.getCategory() == this.category) .filter(folder -> !folder.hasParent()) + .filter(folder -> folder.canView(this.player)) .collect(Collectors.toList()); } @@ -589,7 +590,7 @@ public WorldSort getPreviousSort(WorldSort currentSort) { * @param buildWorld The BuildWorld associated with the clicked item. */ private void manageWorldItemClick(@NotNull InventoryClickEvent event, @NotNull BuildWorld buildWorld) { - if (event.isLeftClick() || buildWorld.getPermissions().canPerformCommand(player, WorldsTabComplete.WorldsArgument.EDIT.getPermission())) { + if (event.isLeftClick() || !buildWorld.getPermissions().canPerformCommand(player, WorldsTabComplete.WorldsArgument.EDIT.getPermission())) { performNonEditClick(buildWorld); return; } From 6d3916fc5a3bf4e9036ea523baba28ff3047f35f Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 21:22:15 +0200 Subject: [PATCH 08/22] Only display folders to player which they can view --- .../buildsystem/navigator/inventory/FolderContentInventory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java index 4bf70398..6e3f3d3b 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java @@ -87,6 +87,7 @@ protected Collection collectFolders() { return folderStorage.getFolders().stream() .filter(folder -> folder.getCategory() == this.category) .filter(folder -> Objects.equals(folder.getParent(), this.folder)) + .filter(folder -> folder.canView(this.player)) .collect(Collectors.toList()); } From f48ae89f44f6f1e7767435878f94f85743599a04 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 21:40:30 +0200 Subject: [PATCH 09/22] Add command to delete empty folders --- .../api/storage/FolderStorage.java | 11 +++++-- .../buildsystem/api/storage/WorldStorage.java | 7 ----- .../de/eintosti/buildsystem/Messages.java | 4 ++- .../subcommand/worlds/FolderSubCommand.java | 29 ++++++++++++++++--- .../storage/FolderStorageImpl.java | 9 ++++-- .../buildsystem/storage/WorldStorageImpl.java | 16 +++++----- .../tabcomplete/WorldsTabComplete.java | 2 +- .../buildsystem/world/WorldServiceImpl.java | 2 +- .../buildsystem/world/display/FolderImpl.java | 10 +++---- 9 files changed, 60 insertions(+), 30 deletions(-) diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java index 3de010be..9ef64479 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java @@ -76,12 +76,19 @@ public interface FolderStorage extends Storage { Folder createFolder(String folderName, NavigatorCategory category, @Nullable Folder parent); /** - * Removes a folder and all of its worlds from the world-to-folder mapping. + * Removes the {@link Folder} with the given name. * - * @param folderName the name of the folder to remove + * @param folderName The name of the folder to remove */ void removeFolder(String folderName); + /** + * Removes the given {@link Folder}. + * + * @param folder The folder to remove + */ + void removeFolder(Folder folder); + /** * Checks if a {@link Folder} with the given name (case-insensitive) exists. * diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java index 660e1608..d629f7ca 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/WorldStorage.java @@ -83,13 +83,6 @@ public interface WorldStorage extends Storage { */ void removeBuildWorld(BuildWorld buildWorld); - /** - * Removes a {@link BuildWorld} from the world map. - * - * @param name The name of the {@link BuildWorld} to remove - */ - void removeBuildWorld(String name); - /** * Checks if a {@link BuildWorld} with the given name (case-insensitive) exists. * diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index b607f574..d9bcb6af 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -263,7 +263,7 @@ public static void createMessageFile() { setMessage(sb, "worlds_edit_unknown_world", "%prefix% &cUnknown world."); setMessage(sb, "worlds_edit_error", "%prefix% &cError: Please try again."); addSpacer(sb, ""); - setMessage(sb, "worlds_folder_usage", "%prefix% &7Usage: &b/worlds folder "); + setMessage(sb, "worlds_folder_usage", "%prefix% &7Usage: &b/worlds folder []"); setMessage(sb, "worlds_folder_unknown_folder", "%prefix% &cUnknown folder."); setMessage(sb, "worlds_folder_unknown_world", "%prefix% &cUnknown world."); setMessage(sb, "worlds_folder_world_already_in_folder", "%prefix% &c%world% is already contained within %folder%."); @@ -276,6 +276,8 @@ public static void createMessageFile() { setMessage(sb, "worlds_folder_creation_invalid_characters", "%prefix% &7&oRemoved invalid characters from folder name."); setMessage(sb, "worlds_folder_creation_name_bank", "%prefix% &cThe folder name cannot be blank."); setMessage(sb, "worlds_folder_created", "%prefix% &7The folder &b%folder% &7was &asuccessfully &7created."); + setMessage(sb, "worlds_folder_not_empty", "%prefix% &cThe folder %folder% is not empty."); + setMessage(sb, "worlds_folder_deleted", "%prefix% &7The folder &b%folder% &7was &asuccessfully &7deleted."); setMessage(sb, "worlds_folder_permission_set", "%prefix% &b%folder%&7's permission was successfully changed."); setMessage(sb, "worlds_folder_item_set", "%prefix% &b%folder%&7's item was successfully changed."); addSpacer(sb, ""); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java index a115267a..94adae2f 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java @@ -41,10 +41,12 @@ public class FolderSubCommand implements SubCommand { private final BuildSystemPlugin plugin; private final WorldServiceImpl worldService; + private final FolderStorage folderStorage; public FolderSubCommand(BuildSystemPlugin plugin) { this.plugin = plugin; this.worldService = plugin.getWorldService(); + this.folderStorage = worldService.getFolderStorage(); } @Override @@ -59,8 +61,7 @@ public void execute(Player player, String[] args) { String folderName = args[1]; String operation = args.length > 2 ? args[2].toLowerCase(Locale.ROOT) : ""; - FolderStorage folderStorage = worldService.getFolderStorage(); - Folder folder = folderStorage.getFolder(folderName); + Folder folder = this.folderStorage.getFolder(folderName); if (folder == null) { Messages.sendMessage(player, "worlds_folder_unknown_folder"); return; @@ -87,6 +88,11 @@ public void execute(Player player, String[] args) { break; } + case "delete": { + handleDeletion(player, folder); + break; + } + default: { Messages.sendMessage(player, "worlds_folder_usage"); break; @@ -111,7 +117,7 @@ private void handleWorldFolderOperation(Player player, Folder folder, String ope return; } - if (worldService.getFolderStorage().isAssignedToAnyFolder(buildWorld)) { + if (folderStorage.isAssignedToAnyFolder(buildWorld)) { Messages.sendMessage(player, "worlds_folder_world_already_in_another_folder", worldPlaceholder); return; } @@ -141,7 +147,7 @@ private void handleWorldFolderOperation(Player player, Folder folder, String ope } private void handlePermissionInput(Player player, Folder folder) { - new PlayerChatInput(plugin, player, "enter_world_permission", input -> { + new PlayerChatInput(this.plugin, player, "enter_world_permission", input -> { folder.setPermission(input.trim()); XSound.ENTITY_PLAYER_LEVELUP.play(player); @@ -164,6 +170,21 @@ private void handleIconChange(Player player, Folder folder) { ); } + private void handleDeletion(Player player, Folder folder) { + if (folder.getWorldCount() > 0) { + Messages.sendMessage(player, "worlds_folder_not_empty", + new AbstractMap.SimpleEntry<>("%folder%", folder.getName()) + ); + return; + } + + this.folderStorage.removeFolder(folder); + Messages.sendMessage(player, "worlds_folder_deleted", + new AbstractMap.SimpleEntry<>("%folder%", folder.getName()) + ); + XSound.ENTITY_PLAYER_LEVELUP.play(player); + } + @Override public Argument getArgument() { return WorldsArgument.FOLDER; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java index 5a026585..e43639bf 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java @@ -84,6 +84,11 @@ public void removeFolder(String folderName) { } } + @Override + public void removeFolder(Folder folder) { + removeFolder(folder.getName()); + } + @Override public boolean folderExists(String folderName) { return folderExists(folderName, false); @@ -127,11 +132,11 @@ public void assignWorldToFolder(BuildWorld buildWorld, String folderName) { } /** - * Removes the mapping of a {@link BuildWorld} to its assigned {@link Folder}. + * Removes the mapping of a {@link BuildWorld} from its assigned {@link Folder}. * * @param buildWorld The world to unassign */ - public void unassignWorldToFolder(BuildWorld buildWorld) { + public void unassignWorldFromFolder(BuildWorld buildWorld) { this.worldToFolderMap.remove(buildWorld.getUniqueId()); } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java index f76ca5db..9a3fddac 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java @@ -21,6 +21,7 @@ import de.eintosti.buildsystem.api.storage.WorldStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.config.ConfigValues; import de.eintosti.buildsystem.world.BuildWorldImpl; import de.eintosti.buildsystem.world.creation.BuildWorldCreatorImpl; @@ -89,12 +90,13 @@ public void addBuildWorld(BuildWorld buildWorld) { @Override public void removeBuildWorld(BuildWorld buildWorld) { - removeBuildWorld(buildWorld.getName()); - } + this.buildWorlds.remove(buildWorld.getName()); - @Override - public void removeBuildWorld(String name) { - buildWorlds.remove(name); + // Also remove world from any folder it may be in + Folder assignedFolder = plugin.getWorldService().getFolderStorage().getAssignedFolder(buildWorld); + if (assignedFolder != null) { + assignedFolder.removeWorld(buildWorld); + } } @Override @@ -105,9 +107,9 @@ public boolean worldExists(String worldName) { @Override public boolean worldExists(String worldName, boolean caseSensitive) { if (caseSensitive) { - return buildWorlds.containsKey(worldName); + return this.buildWorlds.containsKey(worldName); } else { - return buildWorlds.keySet().stream().anyMatch(name -> name.equalsIgnoreCase(worldName)); + return this.buildWorlds.keySet().stream().anyMatch(name -> name.equalsIgnoreCase(worldName)); } } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java index c1fd2d65..65202656 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java @@ -203,7 +203,7 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman case "folder": { switch (args.length) { case 3: - Arrays.asList("add", "remove", "setPermission", "setItem").forEach(arg -> addArgument(arg, arg, arrayList)); + Arrays.asList("add", "remove", "delete", "setPermission", "setItem").forEach(arg -> addArgument(arg, arg, arrayList)); break; case 4: if (!args[2].equalsIgnoreCase("add") && !args[2].equalsIgnoreCase("remove")) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java index 5f640511..52ad3e20 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java @@ -318,7 +318,7 @@ public void renameWorld(Player player, BuildWorld buildWorld, String newName) { } Bukkit.unloadWorld(oldWorld, true); Bukkit.getWorlds().remove(oldWorld); - this.worldStorage.removeBuildWorld(oldName); + this.worldStorage.removeBuildWorld(buildWorld); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> this.worldStorage.delete(oldName)); File oldWorldFile = new File(Bukkit.getWorldContainer(), oldName); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java index afe9f1e2..a29d2671 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java @@ -106,7 +106,7 @@ public void addWorld(BuildWorld buildWorld) { @Override public void removeWorld(BuildWorld buildWorld) { this.worlds.remove(buildWorld.getUniqueId()); - this.folderStorage.unassignWorldToFolder(buildWorld); + this.folderStorage.unassignWorldFromFolder(buildWorld); } @Override @@ -165,16 +165,16 @@ public List getLore(Player player) { } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object other) { + if (this == other) { return true; } - if (o == null || getClass() != o.getClass()) { + if (other == null || getClass() != other.getClass()) { return false; } - FolderImpl folder = (FolderImpl) o; + FolderImpl folder = (FolderImpl) other; return name.equals(folder.name); } From d8c985406cc8ac5c52de921641d087e20c550acb Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 17 Jun 2025 22:08:53 +0200 Subject: [PATCH 10/22] Fix world info item being displayed at the wrong position --- .../eintosti/buildsystem/world/modification/EditInventory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java index 16b8ff61..b2d0a638 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java @@ -146,7 +146,7 @@ private void addBuildWorldInfoItem(Player player, Inventory inventory, BuildWorl if (material == XMaterial.PLAYER_HEAD) { InventoryUtils.addWorldItem(inventory, 4, buildWorld, displayName, new ArrayList<>()); } else { - inventory.setItem(29, InventoryUtils.createItem(material, displayName)); + inventory.setItem(4, InventoryUtils.createItem(material, displayName)); } } From e1e3fee713aad0a627c1beac998eeca101e861d6 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Thu, 19 Jun 2025 19:52:51 +0200 Subject: [PATCH 11/22] Fix circular loading on startup --- .../buildsystem/BuildSystemPlugin.java | 23 ++++++++++--------- .../buildsystem/storage/WorldStorageImpl.java | 3 +-- .../buildsystem/world/WorldServiceImpl.java | 4 ++++ .../world/creation/BuildWorldCreatorImpl.java | 4 ++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java index fc95474d..5da2a215 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java @@ -160,6 +160,10 @@ public class BuildSystemPlugin extends JavaPlugin { public void onLoad() { createTemplateFolder(); Messages.createMessageFile(); + + if (!findCraftBukkitVersion()) { + this.setEnabled(false); + } } @Override @@ -168,10 +172,7 @@ public void onEnable() { this.saveConfig(); initClasses(); - if (!initVersionedClasses()) { - this.setEnabled(false); - return; - } + initVersionedClasses(); registerCommands(); registerTabCompleter(); @@ -226,11 +227,8 @@ public void onDisable() { )); } - private boolean initVersionedClasses() { + private boolean findCraftBukkitVersion() { MinecraftVersion minecraftVersion = MinecraftVersion.getCurrent(); - if (minecraftVersion == null) { - return false; - } this.craftBukkitVersion = CraftBukkitVersion.matchCraftBukkitVersion(minecraftVersion); if (craftBukkitVersion == CraftBukkitVersion.UNKNOWN) { @@ -241,8 +239,6 @@ private boolean initVersionedClasses() { } getLogger().info(String.format(Locale.ROOT, "Detected server version: %s (%s)", minecraftVersion, craftBukkitVersion.name())); - this.customBlocks = craftBukkitVersion.initCustomBlocks(); - this.gameRules = craftBukkitVersion.initGameRules(); return true; } @@ -253,7 +249,7 @@ private void initClasses() { this.armorStandManager = new ArmorStandManager(); this.playerService = new PlayerServiceImpl(this); this.noClipManager = new NoClipManager(this); - this.worldService = new WorldServiceImpl(this); + (this.worldService = new WorldServiceImpl(this)).init(); this.settingsManager = new SettingsManager(this); this.spawnManager = new SpawnManager(this); @@ -271,6 +267,11 @@ private void initClasses() { this.statusInventory = new StatusInventory(this); } + private void initVersionedClasses() { + this.customBlocks = craftBukkitVersion.initCustomBlocks(); + this.gameRules = craftBukkitVersion.initGameRules(); + } + private void registerCommands() { new BackCommand(this); new BlocksCommand(this); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java index 9a3fddac..08338dd5 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java @@ -53,7 +53,6 @@ public WorldStorageImpl(BuildSystemPlugin plugin) { this.logger = plugin.getLogger(); this.buildWorlds = load().stream().collect(Collectors.toMap(BuildWorld::getName, Function.identity())); - this.loadWorlds(); } @Override @@ -158,7 +157,7 @@ public boolean isCorrectVisibility(boolean privateWorld, Visibility visibility) } } - private void loadWorlds() { + public void loadWorlds() { boolean loadAllWorlds = !plugin.getConfigValues().isUnloadWorlds(); if (loadAllWorlds) { logger.info("*** All worlds will be loaded now ***"); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java index 52ad3e20..0b967ed9 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java @@ -70,6 +70,10 @@ public WorldServiceImpl(BuildSystemPlugin plugin) { this.folderStorage = new FolderStorageFactory(plugin).createStorage(); } + public void init() { + this.worldStorage.loadWorlds(); + } + @Override public WorldStorageImpl getWorldStorage() { return worldStorage; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java index 66a64ecd..0f6ec51c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java @@ -19,13 +19,13 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.Messages; +import de.eintosti.buildsystem.api.storage.WorldStorage; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.creation.BuildWorldCreator; import de.eintosti.buildsystem.api.world.creation.generator.CustomGenerator; import de.eintosti.buildsystem.api.world.data.BuildWorldType; import de.eintosti.buildsystem.config.ConfigValues; -import de.eintosti.buildsystem.storage.WorldStorageImpl; import de.eintosti.buildsystem.util.FileUtils; import de.eintosti.buildsystem.version.util.MinecraftVersion; import de.eintosti.buildsystem.world.BuildWorldImpl; @@ -56,7 +56,7 @@ public class BuildWorldCreatorImpl implements BuildWorldCreator { private final BuildSystemPlugin plugin; - private final WorldStorageImpl worldStorage; + private final WorldStorage worldStorage; private String worldName; private Builder creator; From 8411689578c81985e82327d5d4f7495b2deaf7f0 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Thu, 19 Jun 2025 20:03:01 +0200 Subject: [PATCH 12/22] Fix inventory click listener not being registered when using pagination --- .../navigator/inventory/DisplayablesInventory.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index 7f4b8677..2dc3e253 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -120,7 +120,9 @@ protected DisplayablesInventory( * Opens the inventory for the associated player. */ public void openInventory() { - initializeInventories(); + if (this.generatedInventories == null) { + initializeInventories(); + } player.openInventory(generatedInventories[getInvIndex(player)]); plugin.getServer().getPluginManager().registerEvents(this, plugin); } @@ -391,12 +393,12 @@ public void onInventoryClick(@NotNull InventoryClickEvent event) { break; case 52: // Previous page if (decrementInv(player, cachedDisplayables.size(), MAX_WORLDS_PER_PAGE)) { - player.openInventory(generatedInventories[getInvIndex(player)]); + openInventory(); } return; case 53: // Next page if (incrementInv(player, cachedDisplayables.size(), MAX_WORLDS_PER_PAGE)) { - player.openInventory(generatedInventories[getInvIndex(player)]); + openInventory(); } return; } From 000ffcd0323d6ba740ae2603b8620374d5f7bb36 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Thu, 19 Jun 2025 20:11:43 +0200 Subject: [PATCH 13/22] Fix player skull textures not being set --- .../buildsystem/api/world/display/Displayable.java | 14 +++++++++++++- .../navigator/inventory/DisplayablesInventory.java | 2 +- .../eintosti/buildsystem/util/InventoryUtils.java | 12 ++++++------ .../eintosti/buildsystem/world/BuildWorldImpl.java | 11 +++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java index ee8fc33d..d81470f5 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java @@ -20,6 +20,7 @@ import com.cryptomorin.xseries.XMaterial; import java.util.List; import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -83,4 +84,15 @@ default ItemStack asItemStack(Player player) { return itemStack; } -} \ No newline at end of file + + /** + * Adds this displayable to an {@link Inventory} at the given slot. + * + * @param inventory The inventory to add the item to + * @param slot The slot in the inventory to add the item + * @param player The player viewing the inventory + */ + default void addToInventory(Inventory inventory, int slot, Player player) { + inventory.setItem(slot, asItemStack(player)); + } +} \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index 2dc3e253..f978e46c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -172,7 +172,7 @@ private void initializeInventories() { if (currentSlot > LAST_WORLD_SLOT) { break; } - currentPage.setItem(currentSlot++, this.cachedDisplayables.get(i).asItemStack(player)); + this.cachedDisplayables.get(i).addToInventory(currentPage, currentSlot++, player); } } } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/InventoryUtils.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/InventoryUtils.java index 6ac3c524..e64cc5b8 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/InventoryUtils.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/InventoryUtils.java @@ -155,23 +155,23 @@ public static XMaterial getColoredGlassPane(Player player) { } /** - * Adds a world item to the given inventory at the specified position. + * Adds a world item to the given inventory at the specified slot. * * @param inventory The inventory to add the item to - * @param position The position to add the item at + * @param slot The slot to add the item at * @param buildWorld The world to create the item for * @param displayName The display name of the item * @param lore The lore of the item */ - public static void addWorldItem(Inventory inventory, int position, BuildWorld buildWorld, String displayName, List lore) { + public static void addWorldItem(Inventory inventory, int slot, BuildWorld buildWorld, String displayName, List lore) { XMaterial material = buildWorld.getData().material().get(); if (material != XMaterial.PLAYER_HEAD) { - inventory.setItem(position, createItem(material, displayName, lore)); + inventory.setItem(slot, createItem(material, displayName, lore)); return; } // Initially set a default head - inventory.setItem(position, createItem(XMaterial.PLAYER_HEAD, displayName, lore)); + inventory.setItem(slot, createItem(XMaterial.PLAYER_HEAD, displayName, lore)); // Then try to set texture asynchronously XSkull.createItem() @@ -190,7 +190,7 @@ public static void addWorldItem(Inventory inventory, int position, BuildWorld bu itemMeta.setDisplayName(displayName); itemMeta.setLore(lore); itemStack.setItemMeta(itemMeta); - inventory.setItem(position, itemStack); + inventory.setItem(slot, itemStack); }); } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java index f43ec1ac..5078b0d0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java @@ -30,6 +30,7 @@ import de.eintosti.buildsystem.api.world.util.WorldPermissions; import de.eintosti.buildsystem.api.world.util.WorldTeleporter; import de.eintosti.buildsystem.tabcomplete.WorldsTabComplete; +import de.eintosti.buildsystem.util.InventoryUtils; import de.eintosti.buildsystem.world.builder.BuildersImpl; import de.eintosti.buildsystem.world.data.WorldDataImpl; import de.eintosti.buildsystem.world.util.WorldLoaderImpl; @@ -45,6 +46,7 @@ import org.bukkit.Difficulty; import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Nullable; @@ -203,6 +205,15 @@ public List getLore(Player player) { return lore; } + @Override + public void addToInventory(Inventory inventory, int slot, Player player) { + if (getIcon() == XMaterial.PLAYER_HEAD) { + InventoryUtils.addWorldItem(inventory, slot, this, getDisplayName(player), getLore(player)); + return; + } + BuildWorld.super.addToInventory(inventory, slot, player); + } + @Override public Profileable asProfilable() { return builders.hasCreator() From 4c15859dee7db0fe74438db0a490b06cc9552dd0 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Thu, 19 Jun 2025 20:33:33 +0200 Subject: [PATCH 14/22] Fix deletion cancellation item misalignment --- .../buildsystem/world/modification/DeleteInventory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/DeleteInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/DeleteInventory.java index 367722c1..39e67756 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/DeleteInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/DeleteInventory.java @@ -51,7 +51,7 @@ private Inventory getInventory(Player player, BuildWorld buildWorld) { Messages.getString("delete_world_name", player, new AbstractMap.SimpleEntry<>("%world%", buildWorld.getName())), Messages.getStringList("delete_world_name_lore", player)) ); - inventory.setItem(14, InventoryUtils.createItem(XMaterial.RED_DYE, + inventory.setItem(15, InventoryUtils.createItem(XMaterial.RED_DYE, Messages.getString("delete_world_cancel", player)) ); @@ -101,7 +101,6 @@ public void onInventoryClick(InventoryClickEvent event) { XSound.ENTITY_ZOMBIE_BREAK_WOODEN_DOOR.play(player); player.closeInventory(); Messages.sendMessage(player, "worlds_delete_canceled", new AbstractMap.SimpleEntry<>("%world%", buildWorld.getName())); - } } } \ No newline at end of file From d6167f7ae21bdbf0c9522ab150a917bb7f7ad8e9 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Thu, 19 Jun 2025 21:04:36 +0200 Subject: [PATCH 15/22] Automatically add world to folder if created in one --- .../api/world/creation/BuildWorldCreator.java | 39 +- .../de/eintosti/buildsystem/Messages.java | 4 +- .../inventory/DisplayablesInventory.java | 12 +- .../inventory/FolderContentInventory.java | 6 + .../buildsystem/storage/WorldStorageImpl.java | 2 +- .../buildsystem/world/WorldServiceImpl.java | 18 +- .../world/creation/BuildWorldCreatorImpl.java | 335 +++++++++--------- .../world/creation/CreateInventory.java | 19 +- .../world/util/WorldLoaderImpl.java | 5 +- 9 files changed, 233 insertions(+), 207 deletions(-) diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java index 09d2a061..5aaf1841 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java @@ -21,6 +21,7 @@ import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.creation.generator.CustomGenerator; import de.eintosti.buildsystem.api.world.data.BuildWorldType; +import de.eintosti.buildsystem.api.world.display.Folder; import org.bukkit.Difficulty; import org.bukkit.World; import org.bukkit.entity.Player; @@ -76,6 +77,14 @@ public interface BuildWorldCreator { */ BuildWorldCreator setCustomGenerator(CustomGenerator customGenerator); + /** + * Sets the folder in which the world should be created. + * + * @param folder The folder where the world should be created + * @return The world creator object + */ + BuildWorldCreator setFolder(Folder folder); + /** * Sets whether the world should be private or not. * @@ -101,30 +110,42 @@ public interface BuildWorldCreator { BuildWorldCreator setCreationDate(long creationDate); /** - * Depending on the {@link BuildWorld}'s {@link BuildWorldType}, the corresponding {@link World} will be generated in a different way. Then, if the creation of the world was - * successful and the config is set accordingly, the player is teleported to the world. + * Creates and generates a new {@link BuildWorld} using the settings configured in this builder. + *

      + * This process includes creating the world files, registering the world with the plugin, and notifying the player of the progress. * * @param player The player who is creating the world */ void createWorld(Player player); /** - * Imports an existing world as a {@link BuildWorld}. + * Imports an existing world directory as a new {@link BuildWorld}. * * @param player The player who is importing the world - * @param teleport Should the player be teleported to the world after importing is finished + * @param teleport If true, the player will be teleported to the world after the import is finished */ void importWorld(Player player, boolean teleport); + /** + * Generates the underlying Bukkit {@link World} and applies post-generation settings. + *

      + * Only generates the world if the world was not created in a newer Minecraft version that the server is running. + * + * @param buildWorld The build world to generate + * @return The generated {@link World}, or {@code null} if generation failed + */ @Nullable - World generateBukkitWorld(); + default World generateBukkitWorld(BuildWorld buildWorld) { + return generateBukkitWorld(buildWorld, true); + } /** - * Generate the {@link World} linked to a {@link BuildWorld}. + * Generates the underlying Bukkit {@link World} and applies post-generation settings. * - * @param checkVersion Should the world version be checked - * @return The world object + * @param buildWorld The build world to generate. + * @param checkVersion If true, verify that the world's data version is compatible. + * @return The generated {@link World}, or null if generation failed. */ @Nullable - World generateBukkitWorld(boolean checkVersion); + World generateBukkitWorld(BuildWorld buildWorld, boolean checkVersion); } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index d9bcb6af..b8c6acf6 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -443,7 +443,7 @@ public static void createMessageFile() { addSpacer(sb, "# World Navigator"); setMessage(sb, "world_navigator_title", "&3» &8World Navigator"); setMessage(sb, "world_navigator_no_worlds", "&c&nNo worlds available"); - setMessage(sb, "world_navigator_create_world", "&bCreate a public World"); + setMessage(sb, "world_navigator_create_world", "&bCreate a Public World"); setMessage(sb, "world_navigator_create_folder", "&bCreate a Folder"); setMessage(sb, "world_item_title", "&3&l%world%"); setMessage(sb, "world_item_lore_normal", Arrays.asList( @@ -505,7 +505,7 @@ public static void createMessageFile() { addSpacer(sb, "# Private Worlds"); setMessage(sb, "private_title", "&3» &8Private Worlds"); setMessage(sb, "private_no_worlds", "&c&nNo worlds available"); - setMessage(sb, "private_create_world", "&bCreate a private World"); + setMessage(sb, "private_create_world", "&bCreate a Private World"); addSpacer(sb, ""); addSpacer(sb, "# Setup"); setMessage(sb, "setup_title", "&3» &8Setup"); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index f978e46c..62298724 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -359,7 +359,7 @@ public void onInventoryClick(@NotNull InventoryClickEvent event) { case 48: // Create world if (clickedItem.getType() == XMaterial.PLAYER_HEAD.get()) { XSound.ENTITY_CHICKEN_EGG.play(player); - plugin.getCreateInventory().openInventory(player, CreateInventory.Page.PREDEFINED, requiredVisibility); + beginWorldCreation(); return; } break; @@ -411,19 +411,23 @@ public void onInventoryClick(@NotNull InventoryClickEvent event) { } } + protected void beginWorldCreation() { + this.plugin.getCreateInventory().openInventory(this.player, CreateInventory.Page.PREDEFINED, this.requiredVisibility, null); + } + protected Folder createFolder(String folderName) { - return folderStorage.createFolder(folderName, category); + return this.folderStorage.createFolder(folderName, this.category); } /** * Sends the player back to the previous inventory. */ protected void returnToPreviousInventory() { - plugin.getNavigatorInventory().openInventory(player); + this.plugin.getNavigatorInventory().openInventory(this.player); } /** - * Handles clicks on the filter item, managing mode changes and text input. + * Handles clicks on the filter item, managing mode changes, and text input. * * @param event The InventoryClickEvent. * @param worldDisplay The player's WorldDisplay settings. diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java index 6e3f3d3b..0b28bd05 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java @@ -26,6 +26,7 @@ import de.eintosti.buildsystem.api.world.display.Displayable; import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.api.world.display.NavigatorCategory; +import de.eintosti.buildsystem.world.creation.CreateInventory; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collection; @@ -99,6 +100,11 @@ protected Collection collectWorlds() { .collect(Collectors.toList()); } + @Override + protected void beginWorldCreation() { + plugin.getCreateInventory().openInventory(this.player, CreateInventory.Page.PREDEFINED, this.requiredVisibility, this.folder); + } + @Override protected Folder createFolder(String folderName) { return this.folderStorage.createFolder(folderName, this.category, this.folder); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java index 08338dd5..bb45d8b9 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/WorldStorageImpl.java @@ -192,7 +192,7 @@ private LoadResult loadWorld(BuildWorld buildWorld, boolean alwaysLoad) { return LoadResult.NOT_BLACKLISTED; } - World world = new BuildWorldCreatorImpl(plugin, buildWorld).generateBukkitWorld(); + World world = new BuildWorldCreatorImpl(plugin, worldName).generateBukkitWorld(buildWorld); if (world == null) { return LoadResult.FAILED; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java index 0b967ed9..500d26c4 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java @@ -25,6 +25,7 @@ import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.creation.generator.Generator; import de.eintosti.buildsystem.api.world.data.BuildWorldType; +import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.config.ConfigValues; import de.eintosti.buildsystem.storage.FolderStorageImpl; import de.eintosti.buildsystem.storage.WorldStorageImpl; @@ -84,7 +85,7 @@ public FolderStorageImpl getFolderStorage() { return folderStorage; } - public void startWorldNameInput(Player player, BuildWorldType worldType, @Nullable String template, boolean privateWorld) { + public void startWorldNameInput(Player player, BuildWorldType worldType, @Nullable String template, boolean privateWorld, @Nullable Folder folder) { player.closeInventory(); new PlayerChatInput(plugin, player, "enter_world_name", input -> { if (StringCleaner.hasInvalidNameCharacters(input)) { @@ -98,14 +99,14 @@ public void startWorldNameInput(Player player, BuildWorldType worldType, @Nullab } if (worldType == BuildWorldType.CUSTOM) { - startCustomGeneratorInput(player, worldName, template, privateWorld); + startCustomGeneratorInput(player, worldName, template, privateWorld, folder); } else { - createWorld(player, worldName, worldType, null, template, privateWorld); + createWorld(player, worldName, worldType, null, template, privateWorld, folder); } }); } - private void startCustomGeneratorInput(Player player, String worldName, String template, boolean privateWorld) { + private void startCustomGeneratorInput(Player player, String worldName, String template, boolean privateWorld, Folder folder) { new PlayerChatInput(plugin, player, "enter_generator_name", input -> { String[] generatorInfo = input.split(":"); if (generatorInfo.length == 1) { @@ -121,16 +122,17 @@ private void startCustomGeneratorInput(Player player, String worldName, String t CustomGeneratorImpl customGenerator = new CustomGeneratorImpl(generatorInfo[0], chunkGenerator); plugin.getLogger().info("Using custom world generator: " + customGenerator.getName()); - createWorld(player, worldName, BuildWorldType.CUSTOM, customGenerator, template, privateWorld); + createWorld(player, worldName, BuildWorldType.CUSTOM, customGenerator, template, privateWorld, folder); }); } - private void createWorld(Player player, String worldName, BuildWorldType worldType, CustomGeneratorImpl customGenerator, String template, boolean privateWorld) { + private void createWorld(Player player, String worldName, BuildWorldType worldType, CustomGeneratorImpl customGenerator, String template, boolean privateWorld, Folder folder) { new BuildWorldCreatorImpl(plugin, worldName) .setType(worldType) .setTemplate(template) .setPrivate(privateWorld) .setCustomGenerator(customGenerator) + .setFolder(folder) .createWorld(player); } @@ -170,7 +172,7 @@ public boolean importWorld(Player player, String worldName, Builder creator, Bui .setPrivate(false) .setCreationDate(FileUtils.getDirectoryCreation(new File(Bukkit.getWorldContainer(), worldName))); - if (worldCreator.isHigherVersion()) { + if (worldCreator.isDataVersionTooHigh()) { String key = single ? "import" : "importall"; Messages.sendMessage(player, "worlds_" + key + "_newer_version", new AbstractMap.SimpleEntry<>("%world%", worldName) @@ -334,7 +336,7 @@ public void renameWorld(Player player, BuildWorld buildWorld, String newName) { this.worldStorage.addBuildWorld(buildWorld); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> this.worldStorage.save(buildWorld)); - World newWorld = new BuildWorldCreatorImpl(plugin, buildWorld).generateBukkitWorld(false); + World newWorld = new BuildWorldCreatorImpl(plugin, sanitizedNewName).generateBukkitWorld(buildWorld, false); Location spawnLocation = oldWorld.getSpawnLocation(); spawnLocation.setWorld(newWorld); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java index 0f6ec51c..abcc7f29 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java @@ -25,6 +25,7 @@ import de.eintosti.buildsystem.api.world.creation.BuildWorldCreator; import de.eintosti.buildsystem.api.world.creation.generator.CustomGenerator; import de.eintosti.buildsystem.api.world.data.BuildWorldType; +import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.config.ConfigValues; import de.eintosti.buildsystem.util.FileUtils; import de.eintosti.buildsystem.version.util.MinecraftVersion; @@ -39,6 +40,7 @@ import java.io.IOException; import java.util.AbstractMap; import java.util.Locale; +import java.util.logging.Level; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Difficulty; @@ -49,23 +51,24 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * @author Trichtern - * @since 2.21.0 - */ public class BuildWorldCreatorImpl implements BuildWorldCreator { + private static final String LEVEL_DAT_FILE_NAME = "level.dat"; + private static final String TEMPLATES_DIRECTORY = "templates"; + private static final int VOID_BLOCK_Y = 64; + private final BuildSystemPlugin plugin; private final WorldStorage worldStorage; private String worldName; private Builder creator; - private boolean privateWorld = false; + private boolean isPrivate = false; private BuildWorldType worldType = BuildWorldType.NORMAL; private CustomGenerator customGenerator = null; private long creationDate = System.currentTimeMillis(); private String template = null; private Difficulty difficulty; + private Folder folder; public BuildWorldCreatorImpl(BuildSystemPlugin plugin, @NotNull String name) { this.plugin = plugin; @@ -75,16 +78,6 @@ public BuildWorldCreatorImpl(BuildSystemPlugin plugin, @NotNull String name) { setDifficulty(plugin.getConfigValues().getWorldDifficulty()); } - public BuildWorldCreatorImpl(BuildSystemPlugin plugin, BuildWorld buildWorld) { - this(plugin, buildWorld.getName()); - - setDifficulty(buildWorld.getData().difficulty().get()); - setCreationDate(buildWorld.getCreationDate()); - setType(buildWorld.getType()); - setCustomGenerator(buildWorld.getCustomGenerator()); - setPrivate(buildWorld.getData().privateWorld().get()); - } - @Override public BuildWorldCreatorImpl setName(String name) { this.worldName = name; @@ -115,248 +108,248 @@ public BuildWorldCreatorImpl setCustomGenerator(CustomGenerator customGenerator) return this; } - public BuildWorldCreatorImpl setPrivate(boolean privateWorld) { - this.privateWorld = privateWorld; + @Override + public BuildWorldCreatorImpl setFolder(@Nullable Folder folder) { + this.folder = folder; + return this; + } + + @Override + public BuildWorldCreatorImpl setPrivate(boolean isPrivate) { + this.isPrivate = isPrivate; return this; } + @Override public BuildWorldCreatorImpl setDifficulty(Difficulty difficulty) { this.difficulty = difficulty; return this; } + @Override public BuildWorldCreatorImpl setCreationDate(long creationDate) { this.creationDate = creationDate; return this; } - /** - * Depending on the {@link BuildWorldImpl}'s {@link BuildWorldType}, the corresponding {@link World} will be generated in a different way. Then, if the creation of the world - * was successful and the config is set accordingly, the player is teleported to the world. - * - * @param player The player who is creating the world - */ + @Override public void createWorld(Player player) { if (worldStorage.worldAndFolderExist(worldName)) { Messages.sendMessage(player, "worlds_world_exists"); return; } - if (worldType == BuildWorldType.TEMPLATE) { - createTemplateWorld(player); - } else { - createPredefinedOrCustomWorld(player); + boolean success = (worldType == BuildWorldType.TEMPLATE) + ? createWorldFromTemplate(player) + : createWorldFromGenerator(player); + + if (success) { + teleportAfterCreation(player); + Messages.sendMessage(player, "worlds_creation_finished"); } } - private BuildWorldImpl createBuildWorldObject(Player player) { - BuildWorldImpl buildWorld = new BuildWorldImpl( - worldName, - creator == null ? Builder.of(player) : creator, - worldType, - creationDate, - privateWorld, - customGenerator - ); - buildWorld.getData().lastLoaded().set(System.currentTimeMillis()); - return buildWorld; + @Override + public void importWorld(Player player, boolean teleport) { + BuildWorld buildWorld = createAndRegisterBuildWorld(player); + generateBukkitWorld(buildWorld, true); + if (teleport) { + teleportAfterCreation(player); + } } /** - * Generate a {@link BuildWorldImpl} with a predefined or custom generator. + * Handles the creation of worlds that use a generator (i.e., not a template). * - * @param player The player who is creating the world + * @param player The player creating the world + * @return true if the process started successfully, false otherwise */ - private void createPredefinedOrCustomWorld(Player player) { - BuildWorld buildWorld = createBuildWorldObject(player); - worldStorage.addBuildWorld(buildWorld); - + private boolean createWorldFromGenerator(Player player) { Messages.sendMessage(player, "worlds_world_creation_started", new AbstractMap.SimpleEntry<>("%world%", worldName), new AbstractMap.SimpleEntry<>("%type%", Messages.getString(worldType.getMessageKey(), player)) ); - finishPreparationsAndGenerate(buildWorld); - teleportAfterCreation(player); - Messages.sendMessage(player, "worlds_creation_finished"); - } - /** - * Imports an existing world as a {@link BuildWorldImpl}. - * - * @param player The player who is importing the world - * @param teleport Should the player be teleported to the world after importing is finished - */ - public void importWorld(Player player, boolean teleport) { - BuildWorld buildWorld = createBuildWorldObject(player); - worldStorage.addBuildWorld(buildWorld); - finishPreparationsAndGenerate(buildWorld); - if (teleport) { - teleportAfterCreation(player); - } + BuildWorld buildWorld = createAndRegisterBuildWorld(player); + generateBukkitWorld(buildWorld, false); // Version check is not needed for new worlds. + return true; } /** - * Generate a {@link BuildWorldImpl} with a template. + * Handles the creation of worlds from a template file. * - * @param player The player who is creating the world + * @param player The player creating the world + * @return {@code true} if the creation was successful, {@code false} otherwise */ - private void createTemplateWorld(Player player) { - File worldFile = new File(Bukkit.getWorldContainer(), worldName); - File templateFile = new File(plugin.getDataFolder() + File.separator + "templates" + File.separator + template); + private boolean createWorldFromTemplate(Player player) { + File templateFile = new File(plugin.getDataFolder(), TEMPLATES_DIRECTORY + File.separator + template); if (!templateFile.exists()) { Messages.sendMessage(player, "worlds_template_does_not_exist"); - return; + return false; } - BuildWorld buildWorld = createBuildWorldObject(player); - worldStorage.addBuildWorld(buildWorld); - Messages.sendMessage(player, "worlds_template_creation_started", new AbstractMap.SimpleEntry<>("%world%", worldName), new AbstractMap.SimpleEntry<>("%template%", template) ); + + File worldFile = new File(Bukkit.getWorldContainer(), worldName); FileUtils.copy(templateFile, worldFile); - Bukkit.createWorld(WorldCreator.name(worldName) - .type(org.bukkit.WorldType.FLAT) - .generateStructures(false)); - teleportAfterCreation(player); - Messages.sendMessage(player, "worlds_creation_finished"); + + BuildWorld buildWorld = createAndRegisterBuildWorld(player); + generateBukkitWorld(buildWorld, true); // Version check is important for templates. + return true; } /** - * Certain {@link BuildWorldType}s require modifications to the world after its generation. + * Creates the {@link BuildWorld} object and registers it. * - * @param buildWorld The build world object + * @param player The player creating the world + * @return The newly created {@link BuildWorld} instance */ - private void finishPreparationsAndGenerate(BuildWorld buildWorld) { - BuildWorldType worldType = buildWorld.getType(); - World bukkitWorld = generateBukkitWorld(); - if (bukkitWorld == null) { - return; - } + private BuildWorld createAndRegisterBuildWorld(Player player) { + BuildWorldImpl buildWorld = new BuildWorldImpl( + worldName, + creator == null ? Builder.of(player) : creator, + worldType, + creationDate, + isPrivate, + customGenerator + ); - switch (worldType) { - case VOID: - if (plugin.getConfigValues().isVoidBlock()) { - bukkitWorld.getBlockAt(0, 64, 0).setType(Material.GOLD_BLOCK); - } - bukkitWorld.setSpawnLocation(0, 65, 0); - break; - case FLAT: - int y = MinecraftVersion.getCurrent().isEqualOrHigherThan(MinecraftVersion.CAVES_18) ? -60 : 4; - bukkitWorld.setSpawnLocation(0, y, 0); - break; - default: - break; + buildWorld.getData().lastLoaded().set(System.currentTimeMillis()); + worldStorage.addBuildWorld(buildWorld); + if (folder != null) { + folder.addWorld(buildWorld); } + + return buildWorld; } + @Override @Nullable - public World generateBukkitWorld() { - return generateBukkitWorld(true); + public World generateBukkitWorld(BuildWorld buildWorld, boolean checkVersion) { + if (checkVersion && isDataVersionTooHigh()) { + plugin.getLogger().warning(String.format(Locale.ROOT, + "\"%s\" was created in a newer version of Minecraft (%s > %s). Skipping...", + worldName, parseDataVersion(), plugin.getCraftBukkitVersion().getDataVersion() + )); + return null; + } + + WorldCreator worldCreator = createBukkitWorldCreator(); + World bukkitWorld = Bukkit.createWorld(worldCreator); + + if (bukkitWorld != null) { + applyDefaultWorldSettings(bukkitWorld); + applyPostGenerationSettings(bukkitWorld, buildWorld.getType()); + updateWorldDataVersion(); + } + + return bukkitWorld; } /** - * Generate the {@link World} linked to a {@link BuildWorldImpl}. + * Creates and configures a {@link WorldCreator} based on the specified {@link BuildWorldType}. * - * @param checkVersion Should the world version be checked - * @return The world object + * @return A configured {@link WorldCreator} */ - @Nullable - public World generateBukkitWorld(boolean checkVersion) { - if (checkVersion && isHigherVersion()) { - plugin.getLogger() - .warning(String.format(Locale.ROOT, - "\"%s\" was created in a newer version of Minecraft (%s > %s). Skipping...", - worldName, parseDataVersion(), plugin.getCraftBukkitVersion().getDataVersion() - )); - return null; - } - + private WorldCreator createBukkitWorldCreator() { WorldCreator worldCreator = new WorldCreator(worldName); - org.bukkit.WorldType bukkitWorldType; switch (worldType) { case VOID: - worldCreator.generateStructures(false); - bukkitWorldType = org.bukkit.WorldType.FLAT; - MinecraftVersion minecraftVersion = MinecraftVersion.getCurrent(); - if (minecraftVersion.isEqualOrHigherThan(MinecraftVersion.CAVES_17)) { - worldCreator.generator(new ModernVoidGenerator()); - } else if (minecraftVersion.isEqualOrHigherThan(MinecraftVersion.AQUATIC_13)) { - worldCreator.generator(new DeprecatedVoidGenerator()); - } else { - worldCreator.generatorSettings("2;0;1"); - } + configureVoidWorld(worldCreator); break; - case FLAT: case PRIVATE: worldCreator.generateStructures(false); - bukkitWorldType = org.bukkit.WorldType.FLAT; + worldCreator.type(org.bukkit.WorldType.FLAT); break; - case NETHER: worldCreator.generateStructures(true); - bukkitWorldType = org.bukkit.WorldType.NORMAL; worldCreator.environment(World.Environment.NETHER); break; - case END: worldCreator.generateStructures(true); - bukkitWorldType = org.bukkit.WorldType.NORMAL; worldCreator.environment(World.Environment.THE_END); break; - case CUSTOM: if (customGenerator != null) { worldCreator.generator(customGenerator.getChunkGenerator()); } - // Drop through - - default: + // Fall-through to NORMAL for default settings + default: // NORMAL worldCreator.generateStructures(true); - bukkitWorldType = org.bukkit.WorldType.NORMAL; worldCreator.environment(World.Environment.NORMAL); break; } - worldCreator.type(bukkitWorldType); + return worldCreator; + } - World bukkitWorld = Bukkit.createWorld(worldCreator); - if (bukkitWorld != null) { - ConfigValues configValues = plugin.getConfigValues(); - bukkitWorld.setDifficulty(difficulty); - bukkitWorld.setTime(configValues.getNoonTime()); - bukkitWorld.getWorldBorder().setSize(configValues.getWorldBorderSize()); - bukkitWorld.setKeepSpawnInMemory(configValues.isTeleportAfterCreation()); - configValues.getDefaultGameRules().forEach(bukkitWorld::setGameRuleValue); + private void configureVoidWorld(WorldCreator worldCreator) { + worldCreator.generateStructures(false); + worldCreator.type(org.bukkit.WorldType.FLAT); + MinecraftVersion minecraftVersion = MinecraftVersion.getCurrent(); + + if (minecraftVersion.isEqualOrHigherThan(MinecraftVersion.CAVES_17)) { + worldCreator.generator(new ModernVoidGenerator()); + } else if (minecraftVersion.isEqualOrHigherThan(MinecraftVersion.AQUATIC_13)) { + worldCreator.generator(new DeprecatedVoidGenerator()); + } else { + // Legacy void generator settings + worldCreator.generatorSettings("2;0;1"); } + } - updateDataVersion(); - return bukkitWorld; + /** + * Applies standard server settings (difficulty, time, border, gamerules) to a newly created world. + * + * @param bukkitWorld The world to configure + */ + private void applyDefaultWorldSettings(World bukkitWorld) { + ConfigValues config = plugin.getConfigValues(); + bukkitWorld.setDifficulty(this.difficulty); + bukkitWorld.setTime(config.getNoonTime()); + bukkitWorld.getWorldBorder().setSize(config.getWorldBorderSize()); + bukkitWorld.setKeepSpawnInMemory(config.isTeleportAfterCreation()); + config.getDefaultGameRules().forEach(bukkitWorld::setGameRuleValue); } /** - * Once a chunk has been loaded in a newer version of Minecraft, then it cannot be loaded in an older version again. Paper allows the server admin to bypass this check with - * {@code }, so we do as well. + * Applies settings that are specific to the world type after it has been generated. * - * @return {@code true} if the world was generated in a higher Minecraft version, otherwise {@code false} + * @param bukkitWorld The world to modify + * @param worldType The type of the world */ - public boolean isHigherVersion() { + private void applyPostGenerationSettings(World bukkitWorld, BuildWorldType worldType) { + switch (worldType) { + case VOID: + if (plugin.getConfigValues().isVoidBlock()) { + bukkitWorld.getBlockAt(0, VOID_BLOCK_Y, 0).setType(Material.GOLD_BLOCK); + } + bukkitWorld.setSpawnLocation(0, VOID_BLOCK_Y + 1, 0); + break; + case FLAT: + int spawnY = MinecraftVersion.getCurrent().isEqualOrHigherThan(MinecraftVersion.CAVES_18) ? -60 : 4; + bukkitWorld.setSpawnLocation(0, spawnY, 0); + break; + default: + // No special post-generation steps for other types + break; + } + } + + public boolean isDataVersionTooHigh() { if (Boolean.getBoolean("Paper.ignoreWorldDataVersion")) { return false; } - return parseDataVersion() > plugin.getCraftBukkitVersion().getDataVersion(); + int worldVersion = parseDataVersion(); + return worldVersion > plugin.getCraftBukkitVersion().getDataVersion(); } - /** - * Parses the world's data version, as stored in {@code level.dat}. - * - * @return The world's data version if found, otherwise -1 if unable to parse - * @see Data version - */ - public int parseDataVersion() { - File levelFile = new File(Bukkit.getWorldContainer() + File.separator + worldName, "level.dat"); + private int parseDataVersion() { + File levelFile = new File(new File(Bukkit.getWorldContainer(), worldName), LEVEL_DAT_FILE_NAME); if (!levelFile.exists()) { return -1; } @@ -365,21 +358,15 @@ public int parseDataVersion() { CompoundTag level = new Nbt().fromFile(levelFile); CompoundTag data = level.get("Data"); IntTag dataVersion = data.getInt("DataVersion"); - return dataVersion != null ? dataVersion.getValue() : -1; } catch (IOException e) { - e.printStackTrace(); + plugin.getLogger().log(Level.WARNING, "Failed to parse level.dat for world " + worldName, e); + return -1; } - - return -1; } - /** - * The {@code level.dat} file is not updated when a newer Minecraft version loads chunks, making the world not loadable. Therefore, manually sets the world's - * {@code DataVersion} to the current server version, if lower. - */ - private void updateDataVersion() { - File levelFile = new File(Bukkit.getWorldContainer() + File.separator + worldName, "level.dat"); + private void updateWorldDataVersion() { + File levelFile = new File(new File(Bukkit.getWorldContainer(), worldName), LEVEL_DAT_FILE_NAME); if (!levelFile.exists()) { return; } @@ -388,18 +375,20 @@ private void updateDataVersion() { Nbt nbt = new Nbt(); CompoundTag level = nbt.fromFile(levelFile); CompoundTag data = level.get("Data"); - IntTag dataVersion = data.getInt("DataVersion"); - if (dataVersion == null) { + IntTag dataVersionTag = data.getInt("DataVersion"); + if (dataVersionTag == null) { return; } + int worldVersion = dataVersionTag.getValue(); int serverVersion = plugin.getCraftBukkitVersion().getDataVersion(); - if (dataVersion.getValue() < serverVersion) { - dataVersion.setValue(serverVersion); + + if (worldVersion < serverVersion) { + dataVersionTag.setValue(serverVersion); nbt.toFile(level, levelFile, CompressionType.GZIP); } } catch (IOException e) { - e.printStackTrace(); + plugin.getLogger().log(Level.WARNING, "Failed to update level.dat for world " + worldName, e); } } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java index c1f0becd..b4a0ba64 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java @@ -25,6 +25,7 @@ import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.world.data.BuildWorldType; import de.eintosti.buildsystem.api.world.data.Visibility; +import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.util.InventoryUtils; import de.eintosti.buildsystem.util.PaginatedInventory; import de.eintosti.buildsystem.world.WorldServiceImpl; @@ -40,6 +41,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; public class CreateInventory extends PaginatedInventory implements Listener { @@ -50,6 +52,7 @@ public class CreateInventory extends PaginatedInventory implements Listener { private int numTemplates = 0; private Visibility visibility; + private Folder folder; private boolean createPrivateWorld; public CreateInventory(BuildSystemPlugin plugin) { @@ -85,9 +88,10 @@ private Inventory getInventory(Player player, Page page) { return inventory; } - public void openInventory(Player player, Page page, Visibility visibility) { + public void openInventory(Player player, Page page, Visibility visibility, @Nullable Folder folder) { this.visibility = visibility; this.createPrivateWorld = visibility == Visibility.PRIVATE; + this.folder = folder; if (page == Page.TEMPLATES) { addTemplates(player, page); @@ -116,8 +120,7 @@ private void addPredefinedWorldItem(Player player, Inventory inventory, int posi } private void addTemplates(Player player, Page page) { - File[] templateFiles = new File(plugin.getDataFolder() + File.separator + "templates") - .listFiles(new TemplateFilter()); + File[] templateFiles = new File(plugin.getDataFolder() + File.separator + "templates").listFiles(new TemplateFilter()); int columnTemplate = 29, maxColumnTemplate = 33; int fileLength = templateFiles != null ? templateFiles.length : 0; @@ -200,7 +203,7 @@ public void onInventoryClick(InventoryClickEvent event) { } if (newPage != null) { - openInventory(player, newPage, this.visibility); + openInventory(player, newPage, this.visibility, this.folder); XSound.ENTITY_CHICKEN_EGG.play(player); return; } @@ -239,14 +242,14 @@ public void onInventoryClick(InventoryClickEvent event) { return; } - worldService.startWorldNameInput(player, worldType, null, createPrivateWorld); + worldService.startWorldNameInput(player, worldType, null, this.createPrivateWorld, this.folder); XSound.ENTITY_CHICKEN_EGG.play(player); break; } case GENERATOR: { if (slot == 31) { - worldService.startWorldNameInput(player, BuildWorldType.CUSTOM, null, createPrivateWorld); + worldService.startWorldNameInput(player, BuildWorldType.CUSTOM, null, this.createPrivateWorld, this.folder); XSound.ENTITY_CHICKEN_EGG.play(player); } break; @@ -261,7 +264,7 @@ public void onInventoryClick(InventoryClickEvent event) { XMaterial xMaterial = XMaterial.matchXMaterial(itemStack); switch (xMaterial) { case FILLED_MAP: - worldService.startWorldNameInput(player, BuildWorldType.TEMPLATE, itemStack.getItemMeta().getDisplayName(), createPrivateWorld); + this.worldService.startWorldNameInput(player, BuildWorldType.TEMPLATE, itemStack.getItemMeta().getDisplayName(), this.createPrivateWorld, this.folder); break; case PLAYER_HEAD: if (slot == 38 && !decrementInv(player, numTemplates, MAX_TEMPLATES)) { @@ -269,7 +272,7 @@ public void onInventoryClick(InventoryClickEvent event) { } else if (slot == 42 && !incrementInv(player, numTemplates, MAX_TEMPLATES)) { return; } - openInventory(player, CreateInventory.Page.TEMPLATES, visibility); + openInventory(player, CreateInventory.Page.TEMPLATES, this.visibility, this.folder); break; default: return; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldLoaderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldLoaderImpl.java index d03f05dd..9a7fe1dc 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldLoaderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/util/WorldLoaderImpl.java @@ -73,8 +73,9 @@ public void load() { return; } - plugin.getLogger().info("*** Loading world \"" + buildWorld.getName() + "\" ***"); - World world = new BuildWorldCreatorImpl(plugin, buildWorld).generateBukkitWorld(); + String worldName = buildWorld.getName(); + plugin.getLogger().info("*** Loading world \"" + worldName + "\" ***"); + World world = new BuildWorldCreatorImpl(plugin, worldName).generateBukkitWorld(buildWorld); if (world == null) { return; } From ded1368bfa97734edb6250ecd3ca1690dc315f1c Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Thu, 19 Jun 2025 22:40:31 +0200 Subject: [PATCH 16/22] Perform world sorting on folders as well --- .../buildsystem/api/world/BuildWorld.java | 7 --- .../api/world/creation/BuildWorldCreator.java | 6 +-- .../api/world/display/Displayable.java | 7 +++ .../subcommand/worlds/InfoSubCommand.java | 2 +- .../PlaceholderApiExpansion.java | 2 +- .../inventory/DisplayablesInventory.java | 49 ++++++++++++++----- .../inventory/FolderContentInventory.java | 1 + .../player/settings/SettingsManager.java | 2 +- .../storage/yaml/YamlFolderStorage.java | 3 ++ .../storage/yaml/YamlWorldStorage.java | 2 +- .../buildsystem/world/BuildWorldImpl.java | 16 +++--- .../buildsystem/world/display/FolderImpl.java | 11 ++++- 12 files changed, 72 insertions(+), 36 deletions(-) diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java index ae0fe200..326e77df 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/BuildWorld.java @@ -77,13 +77,6 @@ public interface BuildWorld extends Displayable { */ WorldData getData(); - /** - * Gets the creation date of the world. - * - * @return The number of milliseconds that have passed since {@code January 1, 1970 UTC}, until the world was created. - */ - long getCreationDate(); - /** * Gets the custom chunk generator used to generate the world. * diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java index 5aaf1841..9f474446 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/BuildWorldCreator.java @@ -142,9 +142,9 @@ default World generateBukkitWorld(BuildWorld buildWorld) { /** * Generates the underlying Bukkit {@link World} and applies post-generation settings. * - * @param buildWorld The build world to generate. - * @param checkVersion If true, verify that the world's data version is compatible. - * @return The generated {@link World}, or null if generation failed. + * @param buildWorld The build world to generate + * @param checkVersion If true, verify that the world's data version is compatible + * @return The generated {@link World}, or {@code null} if generation failed. */ @Nullable World generateBukkitWorld(BuildWorld buildWorld, boolean checkVersion); diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java index d81470f5..818dd127 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Displayable.java @@ -47,6 +47,13 @@ public interface Displayable { */ String getDisplayName(Player player); + /** + * Gets the creation timestamp of the displayable. + * + * @return The number of milliseconds that have passed since {@code January 1, 1970 UTC}, until the displayable was created. + */ + long getCreation(); + /** * Gets the material to display this item with. * diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java index 0fcdb9af..2dcdbe2e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java @@ -75,7 +75,7 @@ public void execute(Player player, String[] args) { new AbstractMap.SimpleEntry<>("%project%", worldData.project().get()), new AbstractMap.SimpleEntry<>("%permission%", worldData.permission().get()), new AbstractMap.SimpleEntry<>("%time%", buildWorld.getWorldTime()), - new AbstractMap.SimpleEntry<>("%creation%", Messages.formatDate(buildWorld.getCreationDate())), + new AbstractMap.SimpleEntry<>("%creation%", Messages.formatDate(buildWorld.getCreation())), new AbstractMap.SimpleEntry<>("%physics%", worldData.physics().get()), new AbstractMap.SimpleEntry<>("%explosions%", worldData.explosions().get()), new AbstractMap.SimpleEntry<>("%mobai%", worldData.mobAi().get()), diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/expansion/placeholderapi/PlaceholderApiExpansion.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/expansion/placeholderapi/PlaceholderApiExpansion.java index 12fb6674..630c2097 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/expansion/placeholderapi/PlaceholderApiExpansion.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/expansion/placeholderapi/PlaceholderApiExpansion.java @@ -203,7 +203,7 @@ private String parseBuildWorldPlaceholder(Player player, String identifier) { case "buildersenabled": return String.valueOf(worldData.buildersEnabled().get()); case "creation": - return Messages.formatDate(buildWorld.getCreationDate()); + return Messages.formatDate(buildWorld.getCreation()); case "creator": return builders.hasCreator() ? builders.getCreator().getName() : "-"; case "creatorid": diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index 62298724..682d425a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -207,6 +207,7 @@ private void initializeInventories() { List displayables = new ArrayList<>(); displayables.addAll(folders); displayables.addAll(standaloneWorlds); + displayables.sort(createDisplayOrderComparator(worldDisplay.getWorldSort())); return displayables; } @@ -241,7 +242,6 @@ protected Collection filterWorlds(Collection buildWorlds return buildWorlds.stream() .filter(this::isWorldValidForDisplay) .filter(worldDisplay.getWorldFilter().apply()) - .sorted(createDisplayOrderComparator(worldDisplay.getWorldSort())) .collect(Collectors.toList()); } @@ -269,37 +269,62 @@ private boolean isWorldValidForDisplay(@NotNull BuildWorld buildWorld) { } /** - * Creates a comparator for sorting {@link BuildWorld}s based on the specified {@link WorldSort} order. + * Creates a comparator for sorting {@link Displayable}s based on the specified {@link WorldSort} order. * * @param worldSort The desired sorting order. * @return The comparator */ - private @NotNull Comparator createDisplayOrderComparator(@NotNull WorldSort worldSort) { - Comparator comparator; + @NotNull + protected Comparator createDisplayOrderComparator(@NotNull WorldSort worldSort) { + Comparator comparator; switch (worldSort) { case OLDEST_FIRST: - comparator = Comparator.comparingLong(BuildWorld::getCreationDate); + comparator = Comparator.comparingLong(Displayable::getCreation); break; case NEWEST_FIRST: - comparator = Comparator.comparingLong(BuildWorld::getCreationDate).reversed(); + comparator = Comparator.comparingLong(Displayable::getCreation).reversed(); break; case PROJECT_A_TO_Z: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)); + comparator = Comparator.comparing((Displayable displayable) -> { + if (displayable instanceof BuildWorld) { + return ((BuildWorld) displayable).getData().project().get().toLowerCase(Locale.ROOT); + } else { + return ""; + } + }); break; case PROJECT_Z_TO_A: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getData().project().get().toLowerCase(Locale.ROOT)).reversed(); + comparator = Comparator.comparing((Displayable displayable) -> { + if (displayable instanceof BuildWorld) { + return ((BuildWorld) displayable).getData().project().get().toLowerCase(Locale.ROOT); + } else { + return ""; + } + }).reversed(); break; case STATUS_NOT_STARTED: - comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()); + comparator = Comparator.comparingInt((Displayable displayable) -> { + if (displayable instanceof BuildWorld) { + return ((BuildWorld) displayable).getData().status().get().getStage(); + } else { + return BuildWorldStatus.FINISHED.getStage(); + } + }); break; case STATUS_FINISHED: - comparator = Comparator.comparingInt((BuildWorld buildWorld) -> buildWorld.getData().status().get().getStage()).reversed(); + comparator = Comparator.comparingInt((Displayable displayable) -> { + if (displayable instanceof BuildWorld) { + return ((BuildWorld) displayable).getData().status().get().getStage(); + } else { + return BuildWorldStatus.FINISHED.getStage(); + } + }).reversed(); break; case NAME_Z_TO_A: - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)).reversed(); + comparator = Comparator.comparing((Displayable displayable) -> displayable.getName().toLowerCase(Locale.ROOT)).reversed(); break; default: // NAME_A_TO_Z - comparator = Comparator.comparing((BuildWorld buildWorld) -> buildWorld.getName().toLowerCase(Locale.ROOT)); + comparator = Comparator.comparing((Displayable displayable) -> displayable.getName().toLowerCase(Locale.ROOT)); break; } return comparator; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java index 0b28bd05..fc9e57ad 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java @@ -80,6 +80,7 @@ public FolderContentInventory( List displayables = new ArrayList<>(); displayables.addAll(folders); displayables.addAll(buildWorlds); + displayables.sort(createDisplayOrderComparator(worldDisplay.getWorldSort())); return displayables; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsManager.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsManager.java index e8f79dab..7eed5c38 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsManager.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/player/settings/SettingsManager.java @@ -171,7 +171,7 @@ private String parseWorldInformation(Player player, BuildWorld buildWorld, Strin case "%creator%": return builders.hasCreator() ? builders.getCreator().getName() : "-"; case "%creation%": - return Messages.formatDate(buildWorld.getCreationDate()); + return Messages.formatDate(buildWorld.getCreation()); case "%lastedited%": return Messages.formatDate(worldData.lastEdited().get()); case "%lastloaded%": diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java index 4e366ef0..e2f3c10e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java @@ -92,6 +92,7 @@ public void save(Collection folders) { public @NotNull Map serializeFolder(Folder folder) { Map serializedFolder = new HashMap<>(); + serializedFolder.put("creation", folder.getCreation()); serializedFolder.put("category", folder.getCategory().name()); serializedFolder.put("parent", folder.hasParent() ? folder.getParent().getName() : null); serializedFolder.put("material", folder.getIcon().name()); @@ -138,6 +139,7 @@ public Collection load() { private Folder loadFolder(String folderName) { final String path = FOLDERS_KEY + "." + folderName; + long creation = config.getLong(path + ".creation", System.currentTimeMillis()); NavigatorCategory category = NavigatorCategory.valueOf(config.getString(path + ".category")); XMaterial defaultMaterial = XMaterial.CHEST; XMaterial material = XMaterial.matchXMaterial(config.getString(path + ".material", defaultMaterial.name())).orElse(defaultMaterial); @@ -147,6 +149,7 @@ private Folder loadFolder(String folderName) { return new FolderImpl( this, folderName, + creation, category, null, // Parent will be set in second pass material, diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java index 121406ee..b5e58f52 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java @@ -112,7 +112,7 @@ public void save(Collection buildWorlds) { } world.put("type", buildWorld.getType().name()); world.put("data", serializeWorldData(buildWorld.getData())); - world.put("date", buildWorld.getCreationDate()); + world.put("date", buildWorld.getCreation()); world.put("builders", serializeBuilders(builders.getAllBuilders())); if (buildWorld.getCustomGenerator() != null) { world.put("chunk-generator", buildWorld.getCustomGenerator().getName()); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java index 5078b0d0..9eec7df4 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/BuildWorldImpl.java @@ -62,7 +62,7 @@ public final class BuildWorldImpl implements BuildWorld { private final WorldDataImpl worldData; private final BuildersImpl builders; private final CustomGenerator customGenerator; - private final long creationDate; + private final long creation; private final WorldLoaderImpl worldLoader; private final WorldUnloaderImpl worldUnloader; @@ -71,7 +71,7 @@ public BuildWorldImpl( String name, Builder creator, BuildWorldType worldType, - long creationDate, + long creation, boolean privateWorld, CustomGenerator customGenerator ) { @@ -87,7 +87,7 @@ public BuildWorldImpl( ), creator, new ArrayList<>(), - creationDate, + creation, customGenerator ); } @@ -99,7 +99,7 @@ public BuildWorldImpl( WorldDataImpl worldData, Builder creator, List builders, - long creationDate, + long creation, CustomGenerator customGenerator ) { this.uuid = uuid; @@ -107,7 +107,7 @@ public BuildWorldImpl( this.worldType = worldType; this.worldData = worldData; this.builders = new BuildersImpl(creator, builders); - this.creationDate = creationDate; + this.creation = creation; this.customGenerator = customGenerator; this.worldLoader = WorldLoaderImpl.of(this); @@ -166,7 +166,7 @@ public List getLore(Player player) { new AbstractMap.SimpleEntry<>("%project%", worldData.project().get()), new AbstractMap.SimpleEntry<>("%permission%", worldData.permission().get()), new AbstractMap.SimpleEntry<>("%creator%", builders.hasCreator() ? builders.getCreator().getName() : "-"), - new AbstractMap.SimpleEntry<>("%creation%", Messages.formatDate(getCreationDate())), + new AbstractMap.SimpleEntry<>("%creation%", Messages.formatDate(getCreation())), new AbstractMap.SimpleEntry<>("%lastedited%", Messages.formatDate(worldData.lastEdited().get())), new AbstractMap.SimpleEntry<>("%lastloaded%", Messages.formatDate(worldData.lastLoaded().get())), new AbstractMap.SimpleEntry<>("%lastunloaded%", Messages.formatDate(worldData.lastUnloaded().get())) @@ -232,8 +232,8 @@ public WorldData getData() { } @Override - public long getCreationDate() { - return creationDate; + public long getCreation() { + return creation; } @Override diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java index a29d2671..2a0e0fbb 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java @@ -39,6 +39,7 @@ public class FolderImpl implements Folder { private final FolderStorageImpl folderStorage; private final String name; + private final long creation; private final NavigatorCategory category; private final List worlds; @@ -47,12 +48,13 @@ public class FolderImpl implements Folder { private String permission; public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, @Nullable Folder parent) { - this(folderStorage, name, category, parent, XMaterial.CHEST, "-", new ArrayList<>()); + this(folderStorage, name, System.currentTimeMillis(), category, parent, XMaterial.CHEST, "-", new ArrayList<>()); } - public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, Folder parent, XMaterial material, String permission, List worlds) { + public FolderImpl(FolderStorageImpl folderStorage, String name, long creation, NavigatorCategory category, Folder parent, XMaterial material, String permission, List worlds) { this.folderStorage = folderStorage; this.name = name; + this.creation = creation; this.category = category; this.parent = parent; this.worlds = worlds; @@ -156,6 +158,11 @@ public String getDisplayName(Player player) { ); } + @Override + public long getCreation() { + return creation; + } + @Override public List getLore(Player player) { return new ArrayList<>(Messages.getStringList("folder_item_lore", player, From a726f378f6eaf57cdedb5d8e173d84a11eb78280 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Thu, 19 Jun 2025 22:57:24 +0200 Subject: [PATCH 17/22] Allow folders to override world project --- .../buildsystem/api/world/display/Folder.java | 14 +++++ .../buildsystem/BuildSystemPlugin.java | 1 + .../de/eintosti/buildsystem/Messages.java | 4 +- .../subcommand/worlds/FolderSubCommand.java | 16 +++++ .../buildsystem/config/ConfigValues.java | 6 ++ .../inventory/DisplayablesInventory.java | 4 ++ .../storage/yaml/YamlFolderStorage.java | 3 + .../tabcomplete/WorldsTabComplete.java | 2 +- .../buildsystem/world/data/WorldDataImpl.java | 30 ++++++--- .../buildsystem/world/display/FolderImpl.java | 62 +++++++++++++------ .../src/main/resources/config.yml | 3 +- 11 files changed, 116 insertions(+), 29 deletions(-) diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java index de5b7eda..307f7d43 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java @@ -109,6 +109,20 @@ public interface Folder extends Displayable { */ void setPermission(String permission); + /** + * Gets the project description of the {@link Folder}. + * + * @return The project description + */ + String getProject(); + + /** + * Sets the project description of the {@link Folder}. + * + * @param project The project description + */ + void setProject(String project); + /** * Gets whether the folder is visible to the specified player. * diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java index 5da2a215..130cbac1 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java @@ -358,6 +358,7 @@ private int getPlayersWithNavigator(NavigatorType navigatorType) { } })); metrics.addCustomChart(new SimplePie("folder_override_permissions", () -> String.valueOf(configValues.isFolderOverridePermissions()))); + metrics.addCustomChart(new SimplePie("folder_override_projects", () -> String.valueOf(configValues.isFolderOverrideProjects()))); } private void registerExpansions() { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index b8c6acf6..e7cd3f4c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -263,7 +263,7 @@ public static void createMessageFile() { setMessage(sb, "worlds_edit_unknown_world", "%prefix% &cUnknown world."); setMessage(sb, "worlds_edit_error", "%prefix% &cError: Please try again."); addSpacer(sb, ""); - setMessage(sb, "worlds_folder_usage", "%prefix% &7Usage: &b/worlds folder []"); + setMessage(sb, "worlds_folder_usage", "%prefix% &7Usage: &b/worlds folder []"); setMessage(sb, "worlds_folder_unknown_folder", "%prefix% &cUnknown folder."); setMessage(sb, "worlds_folder_unknown_world", "%prefix% &cUnknown world."); setMessage(sb, "worlds_folder_world_already_in_folder", "%prefix% &c%world% is already contained within %folder%."); @@ -279,6 +279,7 @@ public static void createMessageFile() { setMessage(sb, "worlds_folder_not_empty", "%prefix% &cThe folder %folder% is not empty."); setMessage(sb, "worlds_folder_deleted", "%prefix% &7The folder &b%folder% &7was &asuccessfully &7deleted."); setMessage(sb, "worlds_folder_permission_set", "%prefix% &b%folder%&7's permission was successfully changed."); + setMessage(sb, "worlds_folder_project_set", "%prefix% &b%folder%&7's project was successfully changed."); setMessage(sb, "worlds_folder_item_set", "%prefix% &b%folder%&7's item was successfully changed."); addSpacer(sb, ""); setMessage(sb, "worlds_help_usage", "%prefix% &7Usage: &b/worlds help [page]"); @@ -472,6 +473,7 @@ public static void createMessageFile() { setMessage(sb, "folder_title", "&3» &8%folder%"); setMessage(sb, "folder_item_title", "&3&l%folder%"); setMessage(sb, "folder_item_lore", Arrays.asList( + "&7Project&8: &b%project%", "&7Permission&8: &b%permission%", "", "&7Worlds&8: &b%worlds%" diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java index 94adae2f..570f07b5 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java @@ -83,6 +83,11 @@ public void execute(Player player, String[] args) { break; } + case "setproject": { + handleProjectInput(player, folder); + break; + } + case "setitem": { handleIconChange(player, folder); break; @@ -157,6 +162,17 @@ private void handlePermissionInput(Player player, Folder folder) { }); } + private void handleProjectInput(Player player, Folder folder) { + new PlayerChatInput(this.plugin, player, "enter_world_project", input -> { + folder.setProject(input.trim()); + + XSound.ENTITY_PLAYER_LEVELUP.play(player); + Messages.sendMessage(player, "worlds_folder_project_set", + new AbstractMap.SimpleEntry<>("%folder%", folder.getName()) + ); + }); + } + private void handleIconChange(Player player, Folder folder) { ItemStack itemStack = player.getItemInHand(); if (itemStack.getType() == Material.AIR) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/config/ConfigValues.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/config/ConfigValues.java index e52dfb3d..23f67e22 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/config/ConfigValues.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/config/ConfigValues.java @@ -73,6 +73,7 @@ public class ConfigValues { private boolean saveFromDeath; private boolean teleportToMapSpawn; private boolean folderOverridePermissions; + private boolean folderOverrideProjects; private int sunriseTime; private int noonTime; @@ -167,6 +168,7 @@ public void setConfigValues() { // Folder this.folderOverridePermissions = config.getBoolean("folder.override-permissions", true); + this.folderOverrideProjects = config.getBoolean("folder.override-projects", false); } /** @@ -383,4 +385,8 @@ public String getInvalidNameCharacters() { public boolean isFolderOverridePermissions() { return folderOverridePermissions; } + + public boolean isFolderOverrideProjects() { + return folderOverrideProjects; + } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index 682d425a..198a3df0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -288,6 +288,8 @@ protected Comparator createDisplayOrderComparator(@NotNull WorldSor comparator = Comparator.comparing((Displayable displayable) -> { if (displayable instanceof BuildWorld) { return ((BuildWorld) displayable).getData().project().get().toLowerCase(Locale.ROOT); + } else if (displayable instanceof Folder) { + return ((Folder) displayable).getProject().toLowerCase(Locale.ROOT); } else { return ""; } @@ -297,6 +299,8 @@ protected Comparator createDisplayOrderComparator(@NotNull WorldSor comparator = Comparator.comparing((Displayable displayable) -> { if (displayable instanceof BuildWorld) { return ((BuildWorld) displayable).getData().project().get().toLowerCase(Locale.ROOT); + } else if (displayable instanceof Folder) { + return ((Folder) displayable).getProject().toLowerCase(Locale.ROOT); } else { return ""; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java index e2f3c10e..6fad40bf 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java @@ -97,6 +97,7 @@ public void save(Collection folders) { serializedFolder.put("parent", folder.hasParent() ? folder.getParent().getName() : null); serializedFolder.put("material", folder.getIcon().name()); serializedFolder.put("permission", folder.getPermission()); + serializedFolder.put("project", folder.getProject()); serializedFolder.put("worlds", folder.getWorldUUIDs().stream().map(UUID::toString).collect(Collectors.toList())); return serializedFolder; @@ -144,6 +145,7 @@ private Folder loadFolder(String folderName) { XMaterial defaultMaterial = XMaterial.CHEST; XMaterial material = XMaterial.matchXMaterial(config.getString(path + ".material", defaultMaterial.name())).orElse(defaultMaterial); String permission = config.getString(path + ".permission", "-"); + String project = config.getString(path + ".project", "-"); List worlds = config.getStringList(path + ".worlds").stream().map(UUID::fromString).collect(Collectors.toList()); return new FolderImpl( @@ -154,6 +156,7 @@ private Folder loadFolder(String folderName) { null, // Parent will be set in second pass material, permission, + project, worlds); } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java index 65202656..308e8888 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java @@ -203,7 +203,7 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman case "folder": { switch (args.length) { case 3: - Arrays.asList("add", "remove", "delete", "setPermission", "setItem").forEach(arg -> addArgument(arg, arg, arrayList)); + Arrays.asList("add", "remove", "delete", "setPermission", "setProject", "setItem").forEach(arg -> addArgument(args[2], arg, arrayList)); break; case 4: if (!args[2].equalsIgnoreCase("add") && !args[2].equalsIgnoreCase("remove")) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/data/WorldDataImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/data/WorldDataImpl.java index eb5d51cd..ceb7ab2f 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/data/WorldDataImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/data/WorldDataImpl.java @@ -18,6 +18,7 @@ package de.eintosti.buildsystem.world.data; import com.cryptomorin.xseries.XMaterial; +import com.google.common.base.Function; import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.WorldService; @@ -166,16 +167,25 @@ public Location getCustomSpawnLocation() { ); } + @Nullable + private Type getOverrideValue(Function valueProvider) { + WorldService worldService = JavaPlugin.getPlugin(BuildSystemPlugin.class).getWorldService(); + BuildWorld buildWorld = worldService.getWorldStorage().getBuildWorld(worldName); + if (buildWorld != null) { + Folder assignedFolder = worldService.getFolderStorage().getAssignedFolder(buildWorld); + if (assignedFolder != null) { + return new TypeImpl<>(valueProvider.apply(assignedFolder)); + } + } + return null; + } + @Override public Type permission() { if (configValues.isFolderOverridePermissions()) { - WorldService worldService = JavaPlugin.getPlugin(BuildSystemPlugin.class).getWorldService(); - BuildWorld buildWorld = worldService.getWorldStorage().getBuildWorld(worldName); - if (buildWorld != null) { - Folder assignedFolder = worldService.getFolderStorage().getAssignedFolder(buildWorld); - if (assignedFolder != null) { - return new TypeImpl<>(assignedFolder.getPermission()); - } + Type assignedFolderPermission = getOverrideValue(Folder::getPermission); + if (assignedFolderPermission != null) { + return assignedFolderPermission; } } return permission; @@ -183,6 +193,12 @@ public Type permission() { @Override public Type project() { + if (configValues.isFolderOverrideProjects()) { + Type assignedFolderProject = getOverrideValue(Folder::getProject); + if (assignedFolderProject != null) { + return assignedFolderProject; + } + } return project; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java index 2a0e0fbb..cc8156fd 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.UUID; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; @@ -46,12 +47,23 @@ public class FolderImpl implements Folder { private Folder parent; private XMaterial material; private String permission; + private String project; public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, @Nullable Folder parent) { - this(folderStorage, name, System.currentTimeMillis(), category, parent, XMaterial.CHEST, "-", new ArrayList<>()); - } - - public FolderImpl(FolderStorageImpl folderStorage, String name, long creation, NavigatorCategory category, Folder parent, XMaterial material, String permission, List worlds) { + this(folderStorage, name, System.currentTimeMillis(), category, parent, XMaterial.CHEST, "-", "-", new ArrayList<>()); + } + + public FolderImpl( + @NotNull FolderStorageImpl folderStorage, + @NotNull String name, + long creation, + @NotNull NavigatorCategory category, + @Nullable Folder parent, + @NotNull XMaterial material, + @NotNull String permission, + @NotNull String project, + @NotNull List<@NotNull UUID> worlds + ) { this.folderStorage = folderStorage; this.name = name; this.creation = creation; @@ -60,6 +72,7 @@ public FolderImpl(FolderStorageImpl folderStorage, String name, long creation, N this.worlds = worlds; this.material = material; this.permission = permission; + this.project = project; } @Override @@ -116,6 +129,18 @@ public int getWorldCount() { return this.worlds.size(); } + @Override + public String getDisplayName(Player player) { + return Messages.getString("folder_item_title", player, + new AbstractMap.SimpleEntry<>("%folder%", name) + ); + } + + @Override + public long getCreation() { + return creation; + } + @Override public XMaterial getIcon() { return this.material; @@ -136,6 +161,16 @@ public void setPermission(String permission) { this.permission = permission; } + @Override + public String getProject() { + return project; + } + + @Override + public void setProject(String project) { + this.project = project; + } + @Override public boolean canView(Player player) { // We can pass null as a world since we are only checking for bypass permissions @@ -148,25 +183,14 @@ public boolean canView(Player player) { return true; } - return player.hasPermission(permission); - } - - @Override - public String getDisplayName(Player player) { - return Messages.getString("folder_item_title", player, - new AbstractMap.SimpleEntry<>("%folder%", name) - ); - } - - @Override - public long getCreation() { - return creation; + return player.hasPermission(this.permission); } @Override public List getLore(Player player) { return new ArrayList<>(Messages.getStringList("folder_item_lore", player, - new AbstractMap.SimpleEntry<>("%permission%", permission), + new AbstractMap.SimpleEntry<>("%permission%", this.permission), + new AbstractMap.SimpleEntry<>("%project%", this.project), new AbstractMap.SimpleEntry<>("%worlds%", String.valueOf(getWorldCount()))) ); } @@ -189,4 +213,4 @@ public boolean equals(Object other) { public int hashCode() { return name.hashCode(); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/buildsystem-core/src/main/resources/config.yml b/buildsystem-core/src/main/resources/config.yml index 89108bee..145cac7a 100644 --- a/buildsystem-core/src/main/resources/config.yml +++ b/buildsystem-core/src/main/resources/config.yml @@ -63,4 +63,5 @@ world: - worth_the_end void-block: true folder: - override-permissions: true \ No newline at end of file + override-permissions: true + override-projects: false \ No newline at end of file From 5d0c3f99cf4df24a2fec9ff0dbaa8d9ab83b6b14 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Thu, 19 Jun 2025 22:59:46 +0200 Subject: [PATCH 18/22] Remove world from folder on deletion --- .../java/de/eintosti/buildsystem/world/WorldServiceImpl.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java index 500d26c4..0cf4ed40 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java @@ -269,6 +269,11 @@ public void deleteWorld(Player player, BuildWorld buildWorld) { return; } + Folder assignedFolder = this.folderStorage.getAssignedFolder(buildWorld); + if (assignedFolder != null) { + assignedFolder.removeWorld(buildWorld); + } + Messages.sendMessage(player, "worlds_delete_started", new AbstractMap.SimpleEntry<>("%world%", worldName)); removePlayersFromWorld(worldName, Messages.getString("worlds_delete_players_world", player)); Bukkit.getScheduler().runTaskLater(plugin, () -> { From 4d4115064641ec19a44a73ff60bbf8c4d3b1ada7 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 24 Jun 2025 09:59:39 +0200 Subject: [PATCH 19/22] Add permission checks to folder subcommands --- .../buildsystem/BuildSystemPlugin.java | 5 --- .../de/eintosti/buildsystem/Messages.java | 4 +++ .../buildsystem/command/BackCommand.java | 2 +- .../buildsystem/command/BlocksCommand.java | 2 +- .../buildsystem/command/BuildCommand.java | 4 +-- .../command/BuildSystemCommand.java | 2 +- .../buildsystem/command/ConfigCommand.java | 2 +- .../command/ExplosionsCommand.java | 2 +- .../buildsystem/command/GamemodeCommand.java | 4 +-- .../buildsystem/command/NoAICommand.java | 2 +- .../buildsystem/command/PhysicsCommand.java | 2 +- .../buildsystem/command/SettingsCommand.java | 2 +- .../buildsystem/command/SetupCommand.java | 2 +- .../buildsystem/command/SkullCommand.java | 2 +- .../buildsystem/command/SpeedCommand.java | 2 +- .../buildsystem/command/TimeCommand.java | 4 +-- .../buildsystem/command/TopCommand.java | 2 +- .../buildsystem/command/WorldsCommand.java | 2 +- .../worlds/AddBuilderSubCommand.java | 2 +- .../subcommand/worlds/BuildersSubCommand.java | 2 +- .../subcommand/worlds/DeleteSubCommand.java | 2 +- .../subcommand/worlds/EditSubCommand.java | 2 +- .../subcommand/worlds/FolderSubCommand.java | 32 +++++++++++++++++-- .../subcommand/worlds/HelpSubCommand.java | 2 +- .../worlds/ImportAllSubCommand.java | 2 +- .../subcommand/worlds/ImportSubCommand.java | 2 +- .../subcommand/worlds/InfoSubCommand.java | 2 +- .../subcommand/worlds/ItemSubCommand.java | 2 +- .../worlds/RemoveBuilderSubCommand.java | 2 +- .../worlds/RemoveSpawnSubCommand.java | 2 +- .../subcommand/worlds/RenameSubCommand.java | 2 +- .../worlds/SetCreatorSubCommand.java | 2 +- .../subcommand/worlds/SetItemSubCommand.java | 2 +- .../worlds/SetPermissionSubCommand.java | 2 +- .../worlds/SetProjectSubCommand.java | 2 +- .../subcommand/worlds/SetSpawnSubCommand.java | 2 +- .../worlds/SetStatusSubCommand.java | 2 +- .../subcommand/worlds/TeleportSubCommand.java | 2 +- .../subcommand/worlds/UnimportSubCommand.java | 2 +- .../listener/NavigatorListener.java | 2 +- .../world/modification/EditInventory.java | 2 +- 41 files changed, 75 insertions(+), 48 deletions(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java index 130cbac1..216bed67 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java @@ -112,7 +112,6 @@ import org.bstats.charts.SimplePie; import org.bukkit.Bukkit; import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicePriority; @@ -443,10 +442,6 @@ private void saveBuildConfig() { spawnManager.save(); } - public void sendPermissionMessage(CommandSender sender) { - Messages.sendMessage(sender, "no_permissions"); - } - /** * Reloads the config and config data. * diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java index e7cd3f4c..8e48bd0c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/Messages.java @@ -931,6 +931,10 @@ private static String getPrefix() { return MESSAGES.get("prefix"); } + public static void sendPermissionError(CommandSender sender) { + Messages.sendMessage(sender, "no_permissions"); + } + @SafeVarargs public static void sendMessage(CommandSender sender, String key, Map.Entry... placeholders) { Player player = sender instanceof Player ? (Player) sender : null; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BackCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BackCommand.java index 9d652cdf..306b9090 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BackCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BackCommand.java @@ -51,7 +51,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.back")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BlocksCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BlocksCommand.java index 48415c94..6d50ca3b 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BlocksCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BlocksCommand.java @@ -43,7 +43,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.blocks")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BuildCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BuildCommand.java index 2942961b..282e1338 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BuildCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BuildCommand.java @@ -53,7 +53,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.build")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } @@ -65,7 +65,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N case 1: { if (!player.hasPermission("buildsystem.build.other")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BuildSystemCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BuildSystemCommand.java index 86e0dc4a..c809307f 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BuildSystemCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/BuildSystemCommand.java @@ -48,7 +48,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.help.buildsystem")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/ConfigCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/ConfigCommand.java index fb103b7b..e2fbfb24 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/ConfigCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/ConfigCommand.java @@ -37,7 +37,7 @@ public ConfigCommand(BuildSystemPlugin plugin) { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) { if (!sender.hasPermission("buildsystem.config")) { - plugin.sendPermissionMessage(sender); + Messages.sendPermissionError(sender); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/ExplosionsCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/ExplosionsCommand.java index 64bc3082..545e5d62 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/ExplosionsCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/ExplosionsCommand.java @@ -54,7 +54,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N String worldName = args.length == 0 ? player.getWorld().getName() : args[0]; BuildWorld buildWorld = worldStorage.getBuildWorld(worldName); if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, "buildsystem.explosions")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/GamemodeCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/GamemodeCommand.java index 5e36e62c..33e0fcf1 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/GamemodeCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/GamemodeCommand.java @@ -100,7 +100,7 @@ private void sendUsageMessage(Player player) { private void setPlayerGamemode(Player player, GameMode gameMode, String gameModeName) { if (!player.hasPermission(String.format("buildsystem.gamemode.%s", gameMode.name().toLowerCase(Locale.ROOT)))) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } @@ -110,7 +110,7 @@ private void setPlayerGamemode(Player player, GameMode gameMode, String gameMode private void setTargetGamemode(Player player, String[] args, GameMode gameMode, String gameModeName) { if (!player.hasPermission(String.format("buildsystem.gamemode.%s.other", gameMode.name().toLowerCase(Locale.ROOT)))) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/NoAICommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/NoAICommand.java index 4b5d76de..705a54b6 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/NoAICommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/NoAICommand.java @@ -57,7 +57,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N String worldName = args.length == 0 ? player.getWorld().getName() : args[0]; BuildWorld buildWorld = worldStorage.getBuildWorld(worldName); if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, "buildsystem.noai")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/PhysicsCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/PhysicsCommand.java index ac511c8e..c621146d 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/PhysicsCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/PhysicsCommand.java @@ -54,7 +54,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N String worldName = args.length == 0 ? player.getWorld().getName() : args[0]; BuildWorld buildWorld = worldStorage.getBuildWorld(worldName); if (WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, "buildsystem.physics")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SettingsCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SettingsCommand.java index ac85f50b..c3293ebb 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SettingsCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SettingsCommand.java @@ -43,7 +43,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.settings")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SetupCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SetupCommand.java index 593ce33c..a8ad8a95 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SetupCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SetupCommand.java @@ -43,7 +43,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.setup")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SkullCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SkullCommand.java index 21f28b90..6cb8e652 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SkullCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SkullCommand.java @@ -46,7 +46,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.skull")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SpeedCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SpeedCommand.java index 596acd7a..a6a84be0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SpeedCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/SpeedCommand.java @@ -45,7 +45,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.speed")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/TimeCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/TimeCommand.java index cfb17450..47ac946c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/TimeCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/TimeCommand.java @@ -65,7 +65,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N switch (label.toLowerCase(Locale.ROOT)) { case "day": { if (!permissions.canPerformCommand(player, "buildsystem.day")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } @@ -84,7 +84,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N case "night": { if (!permissions.canPerformCommand(player, "buildsystem.night")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/TopCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/TopCommand.java index f13d0060..7528dd14 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/TopCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/TopCommand.java @@ -51,7 +51,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N Player player = (Player) sender; if (!player.hasPermission("buildsystem.top")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/WorldsCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/WorldsCommand.java index d38bfd14..b5210309 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/WorldsCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/WorldsCommand.java @@ -68,7 +68,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N if (args.length == 0) { if (!player.hasPermission("buildsystem.navigator")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return true; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/AddBuilderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/AddBuilderSubCommand.java index daaef187..e7c25d32 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/AddBuilderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/AddBuilderSubCommand.java @@ -50,7 +50,7 @@ public AddBuilderSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!permissions.canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/BuildersSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/BuildersSubCommand.java index 98e0a424..299a3467 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/BuildersSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/BuildersSubCommand.java @@ -39,7 +39,7 @@ public BuildersSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/DeleteSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/DeleteSubCommand.java index 3533e703..8cfc0300 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/DeleteSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/DeleteSubCommand.java @@ -39,7 +39,7 @@ public DeleteSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/EditSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/EditSubCommand.java index 2a4f2f5d..bd74697e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/EditSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/EditSubCommand.java @@ -41,7 +41,7 @@ public EditSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java index 570f07b5..f3aafbb4 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/FolderSubCommand.java @@ -56,8 +56,6 @@ public void execute(Player player, String[] args) { return; } - //TODO: Permissions check - String folderName = args[1]; String operation = args.length > 2 ? args[2].toLowerCase(Locale.ROOT) : ""; @@ -117,6 +115,11 @@ private void handleWorldFolderOperation(Player player, Folder folder, String ope switch (operation) { case "add": + if (!player.hasPermission("buildsystem.folder.add")) { + Messages.sendPermissionError(player); + return; + } + if (folder.containsWorld(buildWorld)) { Messages.sendMessage(player, "worlds_folder_world_already_in_folder", folderPlaceholder, worldPlaceholder); return; @@ -140,6 +143,11 @@ private void handleWorldFolderOperation(Player player, Folder folder, String ope break; case "remove": + if (!player.hasPermission("buildsystem.folder.remove")) { + Messages.sendPermissionError(player); + return; + } + if (!folder.containsWorld(buildWorld)) { Messages.sendMessage(player, "worlds_folder_world_not_in_folder", folderPlaceholder, worldPlaceholder); return; @@ -152,6 +160,11 @@ private void handleWorldFolderOperation(Player player, Folder folder, String ope } private void handlePermissionInput(Player player, Folder folder) { + if (!player.hasPermission("buildsystem.folder.setpermission")) { + Messages.sendPermissionError(player); + return; + } + new PlayerChatInput(this.plugin, player, "enter_world_permission", input -> { folder.setPermission(input.trim()); @@ -163,6 +176,11 @@ private void handlePermissionInput(Player player, Folder folder) { } private void handleProjectInput(Player player, Folder folder) { + if (!player.hasPermission("buildsystem.folder.setproject")) { + Messages.sendPermissionError(player); + return; + } + new PlayerChatInput(this.plugin, player, "enter_world_project", input -> { folder.setProject(input.trim()); @@ -174,6 +192,11 @@ private void handleProjectInput(Player player, Folder folder) { } private void handleIconChange(Player player, Folder folder) { + if (!player.hasPermission("buildsystem.folder.setitem")) { + Messages.sendPermissionError(player); + return; + } + ItemStack itemStack = player.getItemInHand(); if (itemStack.getType() == Material.AIR) { Messages.sendMessage(player, "worlds_setitem_hand_empty"); @@ -187,6 +210,11 @@ private void handleIconChange(Player player, Folder folder) { } private void handleDeletion(Player player, Folder folder) { + if (!player.hasPermission("buildsystem.folder.delete")) { + Messages.sendPermissionError(player); + return; + } + if (folder.getWorldCount() > 0) { Messages.sendMessage(player, "worlds_folder_not_empty", new AbstractMap.SimpleEntry<>("%folder%", folder.getName()) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/HelpSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/HelpSubCommand.java index f371950a..a5dc8e77 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/HelpSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/HelpSubCommand.java @@ -40,7 +40,7 @@ public HelpSubCommand(BuildSystemPlugin plugin) { @Override public void execute(Player player, String[] args) { if (!hasPermission(player)) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportAllSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportAllSubCommand.java index 8e0ce9a0..769d29c9 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportAllSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportAllSubCommand.java @@ -44,7 +44,7 @@ public ImportAllSubCommand(BuildSystemPlugin plugin) { @Override public void execute(Player player, String[] args) { if (!hasPermission(player)) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportSubCommand.java index 2e4be2f4..d43ffd0f 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ImportSubCommand.java @@ -49,7 +49,7 @@ public ImportSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!hasPermission(player)) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java index 2dcdbe2e..e988a5d0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/InfoSubCommand.java @@ -44,7 +44,7 @@ public InfoSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ItemSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ItemSubCommand.java index 7a977882..c89dbbdd 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ItemSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/ItemSubCommand.java @@ -37,7 +37,7 @@ public ItemSubCommand(BuildSystemPlugin plugin) { @Override public void execute(Player player, String[] args) { if (!hasPermission(player)) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveBuilderSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveBuilderSubCommand.java index 50838596..ef70936c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveBuilderSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveBuilderSubCommand.java @@ -49,7 +49,7 @@ public RemoveBuilderSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!permissions.canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveSpawnSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveSpawnSubCommand.java index f61e2b4b..979ad0ef 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveSpawnSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RemoveSpawnSubCommand.java @@ -39,7 +39,7 @@ public RemoveSpawnSubCommand(BuildSystemPlugin plugin) { public void execute(Player player, String[] args) { BuildWorld buildWorld = plugin.getWorldService().getWorldStorage().getBuildWorld(player.getWorld()); if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RenameSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RenameSubCommand.java index 7e6dbca2..fbbd38eb 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RenameSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/RenameSubCommand.java @@ -41,7 +41,7 @@ public RenameSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetCreatorSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetCreatorSubCommand.java index 532582c7..c8a53b72 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetCreatorSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetCreatorSubCommand.java @@ -44,7 +44,7 @@ public SetCreatorSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetItemSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetItemSubCommand.java index 97225e4e..4254342a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetItemSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetItemSubCommand.java @@ -43,7 +43,7 @@ public SetItemSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetPermissionSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetPermissionSubCommand.java index 8c7f92a9..baceb47f 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetPermissionSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetPermissionSubCommand.java @@ -42,7 +42,7 @@ public SetPermissionSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetProjectSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetProjectSubCommand.java index 2a247365..0129968c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetProjectSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetProjectSubCommand.java @@ -42,7 +42,7 @@ public SetProjectSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java index f3f1061c..288a6649 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java @@ -40,7 +40,7 @@ public SetSpawnSubCommand(BuildSystemPlugin plugin) { public void execute(Player player, String[] args) { BuildWorld buildWorld = plugin.getWorldService().getWorldStorage().getBuildWorld(player.getWorld()); if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetStatusSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetStatusSubCommand.java index 1f43038a..57af6dd3 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetStatusSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetStatusSubCommand.java @@ -39,7 +39,7 @@ public SetStatusSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/TeleportSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/TeleportSubCommand.java index 217c2906..8685e9dc 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/TeleportSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/TeleportSubCommand.java @@ -38,7 +38,7 @@ public TeleportSubCommand(BuildSystemPlugin plugin) { @Override public void execute(Player player, String[] args) { if (!hasPermission(player)) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/UnimportSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/UnimportSubCommand.java index ab82cd24..1bf5f44e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/UnimportSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/UnimportSubCommand.java @@ -40,7 +40,7 @@ public UnimportSubCommand(BuildSystemPlugin plugin, String worldName) { @Override public void execute(Player player, String[] args) { if (!WorldPermissionsImpl.of(buildWorld).canPerformCommand(player, getArgument().getPermission())) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java index e013f602..2777754b 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/listener/NavigatorListener.java @@ -116,7 +116,7 @@ public void manageNavigatorItemInteraction(PlayerInteractEvent event) { } if (!player.hasPermission("buildsystem.navigator.item")) { - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java index b2d0a638..b90f6cfc 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/modification/EditInventory.java @@ -425,7 +425,7 @@ private boolean hasPermission(Player player, String permission) { return true; } player.closeInventory(); - plugin.sendPermissionMessage(player); + Messages.sendPermissionError(player); XSound.ENTITY_ITEM_BREAK.play(player); return false; } From b13e4bf5178cce8d530e2cf72537e20306209746 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 24 Jun 2025 10:21:11 +0200 Subject: [PATCH 20/22] Fix `/worlds import` tab complete suggesting already imported worlds --- .../de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java index 308e8888..cb975b79 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java @@ -150,7 +150,7 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman return false; } - return worldStorage.worldExists(name); + return !worldStorage.worldExists(name); }); if (directories == null || directories.length == 0) { From 2bc0366fe15c1c3ed190901468ce69950756c5fb Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 24 Jun 2025 10:22:24 +0200 Subject: [PATCH 21/22] Store folder creator This might be used in the future, better start saving it now --- .../api/storage/FolderStorage.java | 7 +- .../buildsystem/api/world/display/Folder.java | 8 ++ .../inventory/DisplayablesInventory.java | 3 +- .../inventory/FolderContentInventory.java | 3 +- .../storage/FolderStorageImpl.java | 9 ++- .../storage/yaml/YamlFolderStorage.java | 75 ++++++++++--------- .../storage/yaml/YamlPlayerStorage.java | 68 ++++++++--------- .../storage/yaml/YamlWorldStorage.java | 68 ++++++++--------- .../buildsystem/world/display/FolderImpl.java | 13 +++- 9 files changed, 138 insertions(+), 116 deletions(-) diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java index 9ef64479..71b28fce 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/storage/FolderStorage.java @@ -18,6 +18,7 @@ package de.eintosti.buildsystem.api.storage; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import java.util.Collection; @@ -63,8 +64,9 @@ public interface FolderStorage extends Storage { * * @param folderName The name folder to create * @param category The category in which the folder should be displayed + * @param creator The builder who created the folder */ - Folder createFolder(String folderName, NavigatorCategory category); + Folder createFolder(String folderName, NavigatorCategory category, Builder creator); /** * Creates a new nested {@link Folder} with the given name. @@ -72,8 +74,9 @@ public interface FolderStorage extends Storage { * @param folderName The name folder to create * @param category The category in which the folder should be displayed * @param parent The parent folder, or {@code null} if this is a top-level folder + * @param creator The builder who created the folder */ - Folder createFolder(String folderName, NavigatorCategory category, @Nullable Folder parent); + Folder createFolder(String folderName, NavigatorCategory category, @Nullable Folder parent, Builder creator); /** * Removes the {@link Folder} with the given name. diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java index 307f7d43..3a74fda5 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/display/Folder.java @@ -18,6 +18,7 @@ package de.eintosti.buildsystem.api.world.display; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.builder.Builder; import java.util.List; import java.util.UUID; import org.bukkit.entity.Player; @@ -29,6 +30,13 @@ */ public interface Folder extends Displayable { + /** + * Gets the {@link Builder} who created the folder. + * + * @return The creator of the folder + */ + Builder getCreator(); + /** * Gets the category in which the {@link Folder} is displayed. * diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java index 198a3df0..5687defb 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/DisplayablesInventory.java @@ -11,6 +11,7 @@ import de.eintosti.buildsystem.api.navigator.settings.WorldSort; import de.eintosti.buildsystem.api.player.settings.Settings; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; import de.eintosti.buildsystem.api.world.data.WorldData; @@ -445,7 +446,7 @@ protected void beginWorldCreation() { } protected Folder createFolder(String folderName) { - return this.folderStorage.createFolder(folderName, this.category); + return this.folderStorage.createFolder(folderName, this.category, Builder.of(this.player)); } /** diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java index fc9e57ad..91fe62db 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/navigator/inventory/FolderContentInventory.java @@ -21,6 +21,7 @@ import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.navigator.settings.WorldDisplay; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.data.BuildWorldStatus; import de.eintosti.buildsystem.api.world.data.Visibility; import de.eintosti.buildsystem.api.world.display.Displayable; @@ -108,7 +109,7 @@ protected void beginWorldCreation() { @Override protected Folder createFolder(String folderName) { - return this.folderStorage.createFolder(folderName, this.category, this.folder); + return this.folderStorage.createFolder(folderName, this.category, this.folder, Builder.of(this.player)); } @Override diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java index e43639bf..4468cf9e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/FolderStorageImpl.java @@ -20,6 +20,7 @@ import de.eintosti.buildsystem.BuildSystemPlugin; import de.eintosti.buildsystem.api.storage.FolderStorage; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.world.display.FolderImpl; @@ -61,13 +62,13 @@ public Collection getFolders() { } @Override - public Folder createFolder(String folderName, NavigatorCategory category) { - return createFolder(folderName, category, null); + public Folder createFolder(String folderName, NavigatorCategory category, Builder creator) { + return createFolder(folderName, category, null, creator); } @Override - public Folder createFolder(String folderName, NavigatorCategory category, @Nullable Folder parent) { - Folder folder = new FolderImpl(this, folderName, category, parent); + public Folder createFolder(String folderName, NavigatorCategory category, @Nullable Folder parent, Builder creator) { + Folder folder = new FolderImpl(this, folderName, category, parent, creator); foldersByName.put(folderName, folder); return folder; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java index 6fad40bf..8f624e26 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlFolderStorage.java @@ -19,6 +19,7 @@ import com.cryptomorin.xseries.XMaterial; import de.eintosti.buildsystem.BuildSystemPlugin; +import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.storage.FolderStorageImpl; @@ -28,8 +29,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.function.Function; @@ -52,31 +55,6 @@ public YamlFolderStorage(BuildSystemPlugin plugin) { super(plugin); } - private void loadFile() { - this.file = new File(plugin.getDataFolder(), "folders.yml"); - this.config = YamlConfiguration.loadConfiguration(file); - - if (!file.exists()) { - config.options().copyDefaults(true); - saveFile(); - return; - } - - try { - config.load(file); - } catch (IOException | InvalidConfigurationException e) { - logger.log(Level.SEVERE, "Could not load folders.yml file", e); - } - } - - private void saveFile() { - try { - config.save(file); - } catch (IOException e) { - logger.log(Level.SEVERE, "Could not save folders.yml file", e); - } - } - @Override public void save(Folder folder) { config.set(FOLDERS_KEY + "." + folder.getName(), serializeFolder(folder)); @@ -89,9 +67,18 @@ public void save(Collection folders) { saveFile(); } + private void saveFile() { + try { + config.save(file); + } catch (IOException e) { + logger.log(Level.SEVERE, "Could not save folders.yml file", e); + } + } + public @NotNull Map serializeFolder(Folder folder) { Map serializedFolder = new HashMap<>(); + serializedFolder.put("creator", folder.getCreator().toString()); serializedFolder.put("creation", folder.getCreation()); serializedFolder.put("category", folder.getCategory().name()); serializedFolder.put("parent", folder.hasParent() ? folder.getParent().getName() : null); @@ -105,17 +92,7 @@ public void save(Collection folders) { @Override public Collection load() { - loadFile(); - - ConfigurationSection section = config.getConfigurationSection(FOLDERS_KEY); - if (section == null) { - return new ArrayList<>(); - } - - Set folders = section.getKeys(false); - if (folders.isEmpty()) { - return new ArrayList<>(); - } + Set folders = loadFolderKeys(); // First pass: Create all folders without parent references Map loadedFolders = folders.stream() @@ -137,9 +114,34 @@ public Collection load() { return new ArrayList<>(loadedFolders.values()); } + private Set loadFolderKeys() { + this.file = new File(plugin.getDataFolder(), "folders.yml"); + this.config = YamlConfiguration.loadConfiguration(file); + + if (!file.exists()) { + config.options().copyDefaults(true); + saveFile(); + return new HashSet<>(); + } + + try { + config.load(file); + } catch (IOException | InvalidConfigurationException e) { + logger.log(Level.SEVERE, "Could not load folders.yml file", e); + } + + ConfigurationSection section = config.getConfigurationSection(FOLDERS_KEY); + if (section == null) { + return new HashSet<>(); + } + + return section.getKeys(false); + } + private Folder loadFolder(String folderName) { final String path = FOLDERS_KEY + "." + folderName; + Builder creator = Objects.requireNonNull(Builder.deserialize(path + ".creator"), "Creator cannot be null for folder: " + folderName); long creation = config.getLong(path + ".creation", System.currentTimeMillis()); NavigatorCategory category = NavigatorCategory.valueOf(config.getString(path + ".category")); XMaterial defaultMaterial = XMaterial.CHEST; @@ -154,6 +156,7 @@ private Folder loadFolder(String folderName) { creation, category, null, // Parent will be set in second pass + creator, material, permission, project, diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlPlayerStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlPlayerStorage.java index 415e35d8..76c29079 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlPlayerStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlPlayerStorage.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -60,31 +61,6 @@ public YamlPlayerStorage(BuildSystemPlugin plugin) { super(plugin); } - private void loadFile() { - this.file = new File(plugin.getDataFolder(), "players.yml"); - this.config = YamlConfiguration.loadConfiguration(file); - - if (!file.exists()) { - config.options().copyDefaults(true); - saveFile(); - return; - } - - try { - config.load(file); - } catch (IOException | InvalidConfigurationException e) { - logger.log(Level.SEVERE, "Could not load players.yml file", e); - } - } - - private void saveFile() { - try { - config.save(file); - } catch (IOException e) { - logger.log(Level.SEVERE, "Could not save players.yml file", e); - } - } - @Override public void save(BuildPlayer buildPlayer) { config.set(PLAYERS_KEY + "." + buildPlayer.getUniqueId().toString(), serializePlayer(buildPlayer)); @@ -97,6 +73,14 @@ public void save(Collection players) { saveFile(); } + private void saveFile() { + try { + config.save(file); + } catch (IOException e) { + logger.log(Level.SEVERE, "Could not save players.yml file", e); + } + } + public @NotNull Map serializePlayer(BuildPlayer player) { Map serialized = new HashMap<>(); @@ -146,21 +130,35 @@ public void save(Collection players) { @Override public Collection load() { - loadFile(); + Set players = loadPlayerKeys(); - ConfigurationSection section = config.getConfigurationSection(PLAYERS_KEY); - if (section == null) { - return new ArrayList<>(); + return players.stream() + .map(this::loadPlayer) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private Set loadPlayerKeys() { + this.file = new File(plugin.getDataFolder(), "players.yml"); + this.config = YamlConfiguration.loadConfiguration(file); + + if (!file.exists()) { + config.options().copyDefaults(true); + saveFile(); + return new HashSet<>(); } - Set folders = section.getKeys(false); - if (folders.isEmpty()) { - return new ArrayList<>(); + try { + config.load(file); + } catch (IOException | InvalidConfigurationException e) { + logger.log(Level.SEVERE, "Could not load players.yml file", e); } - return folders.stream() - .map(this::loadPlayer) - .collect(Collectors.toCollection(ArrayList::new)); + ConfigurationSection section = config.getConfigurationSection(PLAYERS_KEY); + if (section == null) { + return new HashSet<>(); + } + + return section.getKeys(false); } private BuildPlayer loadPlayer(String playerUuid) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java index b5e58f52..191a364e 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -65,31 +66,6 @@ public YamlWorldStorage(BuildSystemPlugin plugin) { super(plugin); } - private void loadFile() { - this.file = new File(plugin.getDataFolder(), "worlds.yml"); - this.config = YamlConfiguration.loadConfiguration(file); - - if (!file.exists()) { - config.options().copyDefaults(true); - saveFile(); - return; - } - - try { - config.load(file); - } catch (IOException | InvalidConfigurationException e) { - logger.log(Level.SEVERE, "Could not load worlds.yml file", e); - } - } - - private void saveFile() { - try { - config.save(file); - } catch (IOException e) { - logger.log(Level.SEVERE, "Could not save worlds.yml file", e); - } - } - @Override public void save(BuildWorld buildWorld) { config.set(WORLDS_KEY + "." + buildWorld.getName(), serializeWorld(buildWorld)); @@ -102,6 +78,14 @@ public void save(Collection buildWorlds) { saveFile(); } + private void saveFile() { + try { + config.save(file); + } catch (IOException e) { + logger.log(Level.SEVERE, "Could not save worlds.yml file", e); + } + } + public @NotNull Map serializeWorld(BuildWorld buildWorld) { Map world = new HashMap<>(); @@ -137,21 +121,35 @@ private String serializeBuilders(Collection builders) { @Override public Collection load() { - loadFile(); + Set worlds = loadWorldKeys(); - ConfigurationSection section = config.getConfigurationSection(WORLDS_KEY); - if (section == null) { - return new ArrayList<>(); + return worlds.stream() + .map(this::loadWorld) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private Set loadWorldKeys() { + this.file = new File(plugin.getDataFolder(), "worlds.yml"); + this.config = YamlConfiguration.loadConfiguration(file); + + if (!file.exists()) { + config.options().copyDefaults(true); + saveFile(); + return new HashSet<>(); } - Set worlds = section.getKeys(false); - if (worlds.isEmpty()) { - return new ArrayList<>(); + try { + config.load(file); + } catch (IOException | InvalidConfigurationException e) { + logger.log(Level.SEVERE, "Could not load worlds.yml file", e); } - return worlds.stream() - .map(this::loadWorld) - .collect(Collectors.toCollection(ArrayList::new)); + ConfigurationSection section = config.getConfigurationSection(WORLDS_KEY); + if (section == null) { + return new HashSet<>(); + } + + return section.getKeys(false); } private BuildWorldImpl loadWorld(String worldName) { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java index cc8156fd..749905e9 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/display/FolderImpl.java @@ -20,6 +20,7 @@ import com.cryptomorin.xseries.XMaterial; import de.eintosti.buildsystem.Messages; import de.eintosti.buildsystem.api.world.BuildWorld; +import de.eintosti.buildsystem.api.world.builder.Builder; import de.eintosti.buildsystem.api.world.display.Folder; import de.eintosti.buildsystem.api.world.display.NavigatorCategory; import de.eintosti.buildsystem.api.world.util.WorldPermissions; @@ -40,6 +41,7 @@ public class FolderImpl implements Folder { private final FolderStorageImpl folderStorage; private final String name; + private final Builder creator; private final long creation; private final NavigatorCategory category; private final List worlds; @@ -49,8 +51,8 @@ public class FolderImpl implements Folder { private String permission; private String project; - public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, @Nullable Folder parent) { - this(folderStorage, name, System.currentTimeMillis(), category, parent, XMaterial.CHEST, "-", "-", new ArrayList<>()); + public FolderImpl(FolderStorageImpl folderStorage, String name, NavigatorCategory category, @Nullable Folder parent, Builder creator) { + this(folderStorage, name, System.currentTimeMillis(), category, parent, creator, XMaterial.CHEST, "-", "-", new ArrayList<>()); } public FolderImpl( @@ -59,6 +61,7 @@ public FolderImpl( long creation, @NotNull NavigatorCategory category, @Nullable Folder parent, + @NotNull Builder creator, @NotNull XMaterial material, @NotNull String permission, @NotNull String project, @@ -69,12 +72,18 @@ public FolderImpl( this.creation = creation; this.category = category; this.parent = parent; + this.creator = creator; this.worlds = worlds; this.material = material; this.permission = permission; this.project = project; } + @Override + public Builder getCreator() { + return this.creator; + } + @Override public String getName() { return this.name; From 3bd5bbeb0695c6597ed6768064248b34179f4758 Mon Sep 17 00:00:00 2001 From: Thomas Meaney Date: Tue, 24 Jun 2025 10:50:41 +0200 Subject: [PATCH 22/22] Only suggest folder subcommands which the player has permission for --- .../buildsystem/tabcomplete/WorldsTabComplete.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java index cb975b79..49ed2bd3 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/WorldsTabComplete.java @@ -203,7 +203,17 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman case "folder": { switch (args.length) { case 3: - Arrays.asList("add", "remove", "delete", "setPermission", "setProject", "setItem").forEach(arg -> addArgument(args[2], arg, arrayList)); + Map subCommands = new HashMap() {{ + put("add", "buildsystem.folder.add"); + put("remove", "buildsystem.folder.remove"); + put("delete", "buildsystem.folder.delete"); + put("setPermission", "buildsystem.folder.setpermission"); + put("setProject", "buildsystem.folder.setproject"); + put("setItem", "buildsystem.folder.setitem"); + }}; + subCommands.entrySet().stream() + .filter(entry -> player.hasPermission(entry.getKey())) + .forEach(entry -> addArgument(args[2], entry.getKey(), arrayList)); break; case 4: if (!args[2].equalsIgnoreCase("add") && !args[2].equalsIgnoreCase("remove")) {