From 69f7a6012064d79e4a9bc3cbfa4dfb6b814a8844 Mon Sep 17 00:00:00 2001 From: Xelara Networks Date: Mon, 8 Jun 2026 00:03:55 -0400 Subject: [PATCH] first commit --- README.md | 0 pom.xml | 47 + .../com/bitnix/myworlds/MyWorldsPlugin.java | 1096 +++++++++++++++++ .../com/bitnix/myworlds/NamespacedKeys.java | 16 + src/main/resources/config.yml | 152 +++ src/main/resources/plugin.yml | 20 + target/MyWorlds.jar | Bin 0 -> 22685 bytes .../myworlds/MyWorldsPlugin$GuiContext.class | Bin 0 -> 820 bytes .../myworlds/MyWorldsPlugin$LimitEntry.class | Bin 0 -> 582 bytes .../com/bitnix/myworlds/MyWorldsPlugin.class | Bin 0 -> 36397 bytes .../com/bitnix/myworlds/NamespacedKeys.class | Bin 0 -> 642 bytes target/classes/config.yml | 152 +++ target/classes/plugin.yml | 20 + target/maven-archiver/pom.properties | 5 + .../compile/default-compile/createdFiles.lst | 4 + .../compile/default-compile/inputFiles.lst | 2 + 16 files changed, 1514 insertions(+) create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/com/bitnix/myworlds/MyWorldsPlugin.java create mode 100644 src/main/java/com/bitnix/myworlds/NamespacedKeys.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/plugin.yml create mode 100644 target/MyWorlds.jar create mode 100644 target/classes/com/bitnix/myworlds/MyWorldsPlugin$GuiContext.class create mode 100644 target/classes/com/bitnix/myworlds/MyWorldsPlugin$LimitEntry.class create mode 100644 target/classes/com/bitnix/myworlds/MyWorldsPlugin.class create mode 100644 target/classes/com/bitnix/myworlds/NamespacedKeys.class create mode 100644 target/classes/config.yml create mode 100644 target/classes/plugin.yml create mode 100644 target/maven-archiver/pom.properties create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..986a728 --- /dev/null +++ b/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + com.bitnix + MyWorlds + 1.0 + jar + + MyWorlds + + + 21 + UTF-8 + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + + + + io.papermc.paper + paper-api + 1.21.8-R0.1-SNAPSHOT + provided + + + + + MyWorlds + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + + + + + diff --git a/src/main/java/com/bitnix/myworlds/MyWorldsPlugin.java b/src/main/java/com/bitnix/myworlds/MyWorldsPlugin.java new file mode 100644 index 0000000..7f0c7ab --- /dev/null +++ b/src/main/java/com/bitnix/myworlds/MyWorldsPlugin.java @@ -0,0 +1,1096 @@ +package com.bitnix.myworlds; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.WorldBorder; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +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.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public final class MyWorldsPlugin extends JavaPlugin implements Listener { + + private File dataFile; + private FileConfiguration dataConfig; + + private String msgNoPermission; + private String msgPlayerOnly; + private String msgReloaded; + private String msgNoWorlds; + private String msgWorldLimitReached; + private String msgNoWorldLimit; + private String msgInvalidWorldType; + private String msgWorldCreating; + private String msgWorldCreated; + private String msgWorldFailed; + private String msgWorldNotFound; + private String msgTeleported; + private String msgOnlyOwnWorlds; + private String msgNoAccessWorld; + private String msgMultiverseMissing; + private String msgPlayerNotFound; + private String msgInvitedPlayer; + private String msgUninvitedPlayer; + private String msgYouWereInvited; + private String msgAlreadyInvited; + private String msgNotInvited; + private String msgPvpEnabled; + private String msgPvpDisabled; + private String msgInvalidPvpState; + private String msgWorldDeleted; + private String msgDeleteFailed; + private String msgConfirmDelete; + + private boolean quickTeleportSingleWorld; + private String worldNameFormat; + private int worldBorderSize; + private String defaultGamemode; + private int noLimitPermissionDefault; + + private String normalCommand; + private String flatCommand; + private String amplifiedCommand; + private String deleteCommand; + + private boolean allowNormal; + private boolean allowFlat; + private boolean allowAmplified; + + private String mainGuiTitle; + private int mainGuiSize; + private String listGuiTitle; + private int listGuiSize; + private String manageGuiTitle; + private int manageGuiSize; + + private final List permissionLimits = new ArrayList<>(); + private final Map openMenus = new HashMap<>(); + private final Map deleteConfirmations = new HashMap<>(); + + @Override + public void onEnable() { + saveDefaultConfig(); + NamespacedKeys.init(this); + createDataFile(); + loadSettings(); + + getServer().getPluginManager().registerEvents(this, this); + getLogger().info("MyWorlds enabled."); + } + + @Override + public void onDisable() { + openMenus.clear(); + deleteConfirmations.clear(); + saveDataConfig(); + } + + private void createDataFile() { + dataFile = new File(getDataFolder(), "data.yml"); + + if (!getDataFolder().exists()) { + getDataFolder().mkdirs(); + } + + if (!dataFile.exists()) { + try { + dataFile.createNewFile(); + } catch (IOException e) { + getLogger().severe("Could not create data.yml"); + e.printStackTrace(); + } + } + + dataConfig = YamlConfiguration.loadConfiguration(dataFile); + } + + private void saveDataConfig() { + if (dataConfig == null || dataFile == null) { + return; + } + + try { + dataConfig.save(dataFile); + } catch (IOException e) { + getLogger().severe("Could not save data.yml"); + e.printStackTrace(); + } + } + + private void loadSettings() { + reloadConfig(); + FileConfiguration config = getConfig(); + + quickTeleportSingleWorld = config.getBoolean("settings.quick-teleport-single-world", false); + worldNameFormat = config.getString("settings.world-name-format", "mw_%player%_%number%"); + worldBorderSize = config.getInt("settings.world-border-size", 10000); + defaultGamemode = config.getString("settings.default-gamemode", "SURVIVAL"); + noLimitPermissionDefault = config.getInt("settings.no-limit-permission-default", 0); + + normalCommand = config.getString("multiverse.normal-command", "mv create %world% normal"); + flatCommand = config.getString("multiverse.flat-command", "mv create %world% normal -t flat"); + amplifiedCommand = config.getString("multiverse.amplified-command", "mv create %world% normal -a true"); + deleteCommand = config.getString("multiverse.delete-command", "mv delete %world%"); + + allowNormal = config.getBoolean("world-types.normal", true); + allowFlat = config.getBoolean("world-types.flat", true); + allowAmplified = config.getBoolean("world-types.amplified", true); + + msgNoPermission = color(config.getString("messages.no-permission", "&cYou do not have permission.")); + msgPlayerOnly = color(config.getString("messages.player-only", "&cOnly players can use this command.")); + msgReloaded = color(config.getString("messages.reloaded", "&aMyWorlds config reloaded.")); + msgNoWorlds = color(config.getString("messages.no-worlds", "&cYou do not own any worlds yet.")); + msgWorldLimitReached = color(config.getString("messages.world-limit-reached", "&cYou have reached your world limit of &e%limit%&c.")); + msgNoWorldLimit = color(config.getString("messages.no-world-limit", "&cYou are not allowed to create any worlds.")); + msgInvalidWorldType = color(config.getString("messages.invalid-world-type", "&cInvalid world type.")); + msgWorldCreating = color(config.getString("messages.world-creating", "&eCreating your world: &f%world%&e...")); + msgWorldCreated = color(config.getString("messages.world-created", "&aWorld created: &e%world%&a.")); + msgWorldFailed = color(config.getString("messages.world-failed", "&cFailed to create world.")); + msgWorldNotFound = color(config.getString("messages.world-not-found", "&cThat world could not be found.")); + msgTeleported = color(config.getString("messages.teleported", "&aTeleported to &e%world%&a.")); + msgOnlyOwnWorlds = color(config.getString("messages.only-own-worlds", "&cYou can only manage your own worlds.")); + msgNoAccessWorld = color(config.getString("messages.no-access-world", "&cYou do not have access to that world.")); + msgMultiverseMissing = color(config.getString("messages.multiverse-missing", "&cMultiverse-Core was not found or is not enabled.")); + msgPlayerNotFound = color(config.getString("messages.player-not-found", "&cThat player is not online.")); + msgInvitedPlayer = color(config.getString("messages.invited-player", "&aInvited &e%player% &ato world &e%world%&a.")); + msgUninvitedPlayer = color(config.getString("messages.uninvited-player", "&e%player% &chas been removed from world &e%world%&c.")); + msgYouWereInvited = color(config.getString("messages.you-were-invited", "&aYou were invited to world &e%world% &aby &e%owner%&a.")); + msgAlreadyInvited = color(config.getString("messages.already-invited", "&e%player% &cis already invited to &e%world%&c.")); + msgNotInvited = color(config.getString("messages.not-invited", "&e%player% &cis not invited to &e%world%&c.")); + msgPvpEnabled = color(config.getString("messages.pvp-enabled", "&aPvP enabled for &e%world%&a.")); + msgPvpDisabled = color(config.getString("messages.pvp-disabled", "&cPvP disabled for &e%world%&c.")); + msgInvalidPvpState = color(config.getString("messages.invalid-pvp-state", "&cUse on/off.")); + msgWorldDeleted = color(config.getString("messages.world-deleted", "&cWorld deleted: &e%world%&c.")); + msgDeleteFailed = color(config.getString("messages.delete-failed", "&cFailed to delete world &e%world%&c.")); + msgConfirmDelete = color(config.getString("messages.confirm-delete", "&cClick again to confirm deletion of &e%world%&c.")); + + mainGuiTitle = color(config.getString("gui.main.title", "&8My Worlds")); + mainGuiSize = config.getInt("gui.main.size", 27); + listGuiTitle = color(config.getString("gui.list.title", "&8Your Worlds")); + listGuiSize = config.getInt("gui.list.size", 54); + manageGuiTitle = color(config.getString("gui.manage.title", "&8Manage World")); + manageGuiSize = config.getInt("gui.manage.size", 27); + + permissionLimits.clear(); + List> rawLimits = config.getMapList("limits"); + for (Map raw : rawLimits) { + Object permissionObj = raw.get("permission"); + Object amountObj = raw.get("amount"); + + if (permissionObj == null || amountObj == null) { + continue; + } + + String permission = String.valueOf(permissionObj).trim(); + int amount; + + try { + amount = Integer.parseInt(String.valueOf(amountObj)); + } catch (NumberFormatException e) { + continue; + } + + if (!permission.isBlank() && amount >= 0) { + permissionLimits.add(new LimitEntry(permission, amount)); + } + } + } + + private boolean hasMultiverse() { + Plugin mv = Bukkit.getPluginManager().getPlugin("Multiverse-Core"); + return mv != null && mv.isEnabled(); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { + if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { + if (!sender.hasPermission("myworlds.admin")) { + sender.sendMessage(msgNoPermission); + return true; + } + + loadSettings(); + dataConfig = YamlConfiguration.loadConfiguration(dataFile); + sender.sendMessage(msgReloaded); + return true; + } + + sender.sendMessage(msgPlayerOnly); + return true; + } + + if (!player.hasPermission("myworlds.use")) { + player.sendMessage(msgNoPermission); + return true; + } + + if (args.length == 0) { + openMainGui(player); + return true; + } + + switch (args[0].toLowerCase()) { + case "help" -> sendHelp(player); + case "list" -> sendWorldList(player); + case "generate" -> { + if (args.length < 2) { + openMainGui(player); + return true; + } + generateWorld(player, args[1].toLowerCase()); + } + case "invite" -> { + if (!player.hasPermission("myworlds.invite")) { + player.sendMessage(msgNoPermission); + return true; + } + if (args.length < 3) { + player.sendMessage(color("&eUsage: /myworlds invite ")); + return true; + } + handleInvite(player, args[1], args[2]); + } + case "uninvite" -> { + if (!player.hasPermission("myworlds.invite")) { + player.sendMessage(msgNoPermission); + return true; + } + if (args.length < 3) { + player.sendMessage(color("&eUsage: /myworlds uninvite ")); + return true; + } + handleUninvite(player, args[1], args[2]); + } + case "pvp" -> { + if (!player.hasPermission("myworlds.pvp")) { + player.sendMessage(msgNoPermission); + return true; + } + if (args.length < 3) { + player.sendMessage(color("&eUsage: /myworlds pvp ")); + return true; + } + handlePvpToggle(player, args[1], args[2]); + } + case "reload" -> { + if (!player.hasPermission("myworlds.admin")) { + player.sendMessage(msgNoPermission); + return true; + } + loadSettings(); + dataConfig = YamlConfiguration.loadConfiguration(dataFile); + player.sendMessage(msgReloaded); + } + default -> openMainGui(player); + } + + return true; + } + + private void sendHelp(Player player) { + player.sendMessage(color("&6MyWorlds Help")); + player.sendMessage(color("&e/myworlds &7- Open menu")); + player.sendMessage(color("&e/myworlds help &7- Show help")); + player.sendMessage(color("&e/myworlds list &7- List your worlds")); + player.sendMessage(color("&e/myworlds generate &7- Generate a world")); + player.sendMessage(color("&e/myworlds invite &7- Invite a player")); + player.sendMessage(color("&e/myworlds uninvite &7- Remove invite")); + player.sendMessage(color("&e/myworlds pvp &7- Toggle world PvP")); + if (player.hasPermission("myworlds.admin")) { + player.sendMessage(color("&e/myworlds reload &7- Reload config")); + } + } + + private void sendWorldList(Player player) { + List owned = getOwnedWorlds(player.getUniqueId()); + List invited = getAccessibleInvitedWorlds(player.getUniqueId()); + + if (owned.isEmpty() && invited.isEmpty()) { + player.sendMessage(msgNoWorlds); + return; + } + + if (!owned.isEmpty()) { + player.sendMessage(color("&6Your Owned Worlds:")); + for (String world : owned) { + UUID owner = getOwnerOfWorld(world); + String type = owner != null ? getWorldType(owner, world) : "unknown"; + player.sendMessage(color("&e- &f" + world + " &7(" + type + ")")); + } + } + + if (!invited.isEmpty()) { + player.sendMessage(color("&6Worlds You Are Invited To:")); + for (String world : invited) { + player.sendMessage(color("&e- &f" + world)); + } + } + } + + private void openMainGui(Player player) { + Inventory inv = Bukkit.createInventory(null, normalizeSize(mainGuiSize), mainGuiTitle); + + setConfiguredItem(inv, "generate-normal", player, "GENERATE_NORMAL"); + setConfiguredItem(inv, "generate-flat", player, "GENERATE_FLAT"); + setConfiguredItem(inv, "generate-amplified", player, "GENERATE_AMPLIFIED"); + setConfiguredItem(inv, "list-worlds", player, "LIST_WORLDS"); + setConfiguredItem(inv, "info", player, "INFO"); + + openMenus.put(player.getUniqueId(), new GuiContext("MAIN", new HashMap<>())); + player.openInventory(inv); + } + + private void openListGui(Player player) { + Inventory inv = Bukkit.createInventory(null, normalizeSize(listGuiSize), listGuiTitle); + Map slotWorldMap = new HashMap<>(); + + int slot = 0; + + for (String world : getOwnedWorlds(player.getUniqueId())) { + if (slot >= inv.getSize()) { + break; + } + if (isReservedListSlot(slot, inv.getSize())) { + slot++; + } + if (slot >= inv.getSize()) { + break; + } + + inv.setItem(slot, buildWorldItem(world, true)); + slotWorldMap.put(slot, world); + slot++; + } + + for (String world : getAccessibleInvitedWorlds(player.getUniqueId())) { + if (slot >= inv.getSize()) { + break; + } + if (isReservedListSlot(slot, inv.getSize())) { + slot++; + } + if (slot >= inv.getSize()) { + break; + } + + inv.setItem(slot, buildWorldItem(world, false)); + slotWorldMap.put(slot, world); + slot++; + } + + setListBackButton(inv); + + openMenus.put(player.getUniqueId(), new GuiContext("LIST", slotWorldMap)); + player.openInventory(inv); + } + + private void openManageGui(Player player, String worldName) { + if (!ownsWorld(player.getUniqueId(), worldName)) { + player.sendMessage(msgOnlyOwnWorlds); + return; + } + + Inventory inv = Bukkit.createInventory(null, normalizeSize(manageGuiSize), manageGuiTitle + " - " + worldName); + + setManageItem(inv, "teleport", "TELEPORT:" + worldName, worldName); + setManageItem(inv, "toggle-pvp", "TOGGLE_PVP:" + worldName, worldName); + setManageItem(inv, "invited-info", "INFO:" + worldName, worldName); + setManageItem(inv, "delete-world", "DELETE_WORLD:" + worldName, worldName); + setManageItem(inv, "back", "BACK_TO_LIST", worldName); + + openMenus.put(player.getUniqueId(), new GuiContext("MANAGE", new HashMap<>())); + player.openInventory(inv); + } + + private boolean isReservedListSlot(int slot, int inventorySize) { + ConfigurationSection section = getConfig().getConfigurationSection("gui.list.back-button"); + if (section == null) { + return false; + } + + int backSlot = section.getInt("slot", inventorySize - 1); + return slot == backSlot; + } + + private void setListBackButton(Inventory inv) { + ConfigurationSection section = getConfig().getConfigurationSection("gui.list.back-button"); + if (section == null) { + return; + } + + Material material = Material.matchMaterial(section.getString("material", "ARROW")); + if (material == null) { + material = Material.ARROW; + } + + int slot = section.getInt("slot", inv.getSize() - 1); + if (slot < 0 || slot >= inv.getSize()) { + return; + } + + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + + if (meta != null) { + meta.setDisplayName(color(section.getString("name", "&cBack"))); + + List lore = new ArrayList<>(); + for (String line : section.getStringList("lore")) { + lore.add(color(line)); + } + meta.setLore(lore); + meta.getPersistentDataContainer().set(NamespacedKeys.ACTION_KEY, PersistentDataType.STRING, "BACK_TO_MAIN"); + item.setItemMeta(meta); + } + + inv.setItem(slot, item); + } + + private ItemStack buildWorldItem(String worldName, boolean owned) { + Material material = owned ? Material.GRASS_BLOCK : Material.CHEST; + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + + if (meta != null) { + UUID owner = getOwnerOfWorld(worldName); + String type = owner != null ? getWorldType(owner, worldName) : "unknown"; + boolean pvp = owner != null && isWorldPvpEnabled(owner, worldName); + + List lore = new ArrayList<>(); + lore.add(color("&7Type: &e" + type)); + lore.add(color("&7PvP: &e" + (pvp ? "ON" : "OFF"))); + lore.add(color(owned ? "&7Click to manage this world." : "&7Click to teleport to this world.")); + + meta.setDisplayName(color((owned ? "&a" : "&b") + worldName)); + meta.setLore(lore); + meta.getPersistentDataContainer().set( + NamespacedKeys.ACTION_KEY, + PersistentDataType.STRING, + owned ? "OPEN_MANAGE:" + worldName : "TELEPORT:" + worldName + ); + item.setItemMeta(meta); + } + + return item; + } + + private void setConfiguredItem(Inventory inv, String key, Player player, String action) { + ConfigurationSection section = getConfig().getConfigurationSection("gui.main.items." + key); + if (section == null) { + return; + } + + String materialName = section.getString("material", "STONE"); + int slot = section.getInt("slot", 0); + String name = section.getString("name", "&fItem"); + List lore = section.getStringList("lore"); + + Material material = Material.matchMaterial(materialName); + if (material == null) { + material = Material.STONE; + } + + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + + if (meta != null) { + meta.setDisplayName(color(name)); + + List processedLore = new ArrayList<>(); + int owned = getOwnedWorlds(player.getUniqueId()).size(); + int limit = getWorldLimit(player); + + for (String line : lore) { + processedLore.add(color(line + .replace("%owned%", String.valueOf(owned)) + .replace("%limit%", String.valueOf(limit)))); + } + + meta.setLore(processedLore); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); + meta.getPersistentDataContainer().set(NamespacedKeys.ACTION_KEY, PersistentDataType.STRING, action); + item.setItemMeta(meta); + } + + if (slot >= 0 && slot < inv.getSize()) { + inv.setItem(slot, item); + } + } + + private void setManageItem(Inventory inv, String key, String action, String worldName) { + ConfigurationSection section = getConfig().getConfigurationSection("gui.manage.items." + key); + if (section == null) { + return; + } + + String materialName = section.getString("material", "STONE"); + int slot = section.getInt("slot", 0); + String name = section.getString("name", "&fItem"); + List lore = section.getStringList("lore"); + + Material material = Material.matchMaterial(materialName); + if (material == null) { + material = Material.STONE; + } + + UUID owner = getOwnerOfWorld(worldName); + List invites = owner != null ? getInvitedPlayers(owner, worldName) : new ArrayList<>(); + String invitesText = invites.isEmpty() ? "None" : String.join(", ", invites); + + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + + if (meta != null) { + meta.setDisplayName(color(name)); + + List processedLore = new ArrayList<>(); + for (String line : lore) { + processedLore.add(color(line.replace("%invites%", invitesText))); + } + + meta.setLore(processedLore); + meta.getPersistentDataContainer().set(NamespacedKeys.ACTION_KEY, PersistentDataType.STRING, action); + item.setItemMeta(meta); + } + + if (slot >= 0 && slot < inv.getSize()) { + inv.setItem(slot, item); + } + } + + private void generateWorld(Player player, String type) { + if (!hasMultiverse()) { + player.sendMessage(msgMultiverseMissing); + return; + } + + if (!isTypeAllowed(type)) { + player.sendMessage(msgInvalidWorldType); + return; + } + + List ownedWorlds = getOwnedWorlds(player.getUniqueId()); + int limit = getWorldLimit(player); + + if (limit <= 0) { + player.sendMessage(msgNoWorldLimit); + return; + } + + if (ownedWorlds.size() >= limit) { + player.sendMessage(msgWorldLimitReached.replace("%limit%", String.valueOf(limit))); + return; + } + + String worldName = getNextWorldName(player, ownedWorlds.size() + 1); + player.sendMessage(msgWorldCreating.replace("%world%", worldName)); + + String command = switch (type) { + case "normal" -> normalCommand; + case "flat" -> flatCommand; + case "amplified" -> amplifiedCommand; + default -> null; + }; + + if (command == null) { + player.sendMessage(msgInvalidWorldType); + return; + } + + boolean success = Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command.replace("%world%", worldName)); + + Bukkit.getScheduler().runTaskLater(this, () -> { + World world = Bukkit.getWorld(worldName); + + if (!success || world == null) { + player.sendMessage(msgWorldFailed); + return; + } + + registerOwnedWorld(player.getUniqueId(), worldName, type); + setWorldPvp(player.getUniqueId(), worldName, true); + applyWorldSettings(world, player.getUniqueId(), worldName); + teleportToWorld(player, worldName); + player.sendMessage(msgWorldCreated.replace("%world%", worldName)); + saveDataConfig(); + }, 40L); + } + + private void handleInvite(Player owner, String targetName, String worldName) { + if (!ownsWorld(owner.getUniqueId(), worldName)) { + owner.sendMessage(msgOnlyOwnWorlds); + return; + } + + Player target = Bukkit.getPlayerExact(targetName); + if (target == null) { + owner.sendMessage(msgPlayerNotFound); + return; + } + + if (isInvited(owner.getUniqueId(), worldName, target.getUniqueId())) { + owner.sendMessage(msgAlreadyInvited + .replace("%player%", target.getName()) + .replace("%world%", worldName)); + return; + } + + dataConfig.set("players." + owner.getUniqueId() + ".worlds." + worldName + ".invites." + target.getUniqueId(), target.getName()); + saveDataConfig(); + + owner.sendMessage(msgInvitedPlayer + .replace("%player%", target.getName()) + .replace("%world%", worldName)); + + target.sendMessage(msgYouWereInvited + .replace("%world%", worldName) + .replace("%owner%", owner.getName())); + } + + private void handleUninvite(Player owner, String targetName, String worldName) { + if (!ownsWorld(owner.getUniqueId(), worldName)) { + owner.sendMessage(msgOnlyOwnWorlds); + return; + } + + UUID targetUuid = getInvitedUuidByName(owner.getUniqueId(), worldName, targetName); + if (targetUuid == null) { + owner.sendMessage(msgNotInvited + .replace("%player%", targetName) + .replace("%world%", worldName)); + return; + } + + dataConfig.set("players." + owner.getUniqueId() + ".worlds." + worldName + ".invites." + targetUuid, null); + saveDataConfig(); + + owner.sendMessage(msgUninvitedPlayer + .replace("%player%", targetName) + .replace("%world%", worldName)); + } + + private void handlePvpToggle(Player owner, String worldName, String state) { + if (!ownsWorld(owner.getUniqueId(), worldName)) { + owner.sendMessage(msgOnlyOwnWorlds); + return; + } + + boolean pvp; + if (state.equalsIgnoreCase("on")) { + pvp = true; + } else if (state.equalsIgnoreCase("off")) { + pvp = false; + } else { + owner.sendMessage(msgInvalidPvpState); + return; + } + + setWorldPvp(owner.getUniqueId(), worldName, pvp); + World world = Bukkit.getWorld(worldName); + if (world != null) { + world.setPVP(pvp); + } + saveDataConfig(); + + owner.sendMessage((pvp ? msgPvpEnabled : msgPvpDisabled).replace("%world%", worldName)); + } + + private void handleDeleteWorld(Player owner, String worldName) { + if (!ownsWorld(owner.getUniqueId(), worldName)) { + owner.sendMessage(msgOnlyOwnWorlds); + return; + } + + long now = System.currentTimeMillis(); + long key = Objects.hash(owner.getUniqueId(), worldName); + + Long lastConfirm = deleteConfirmations.get(owner.getUniqueId()); + if (lastConfirm == null || lastConfirm != key) { + deleteConfirmations.put(owner.getUniqueId(), key); + owner.sendMessage(msgConfirmDelete.replace("%world%", worldName)); + return; + } + + deleteConfirmations.remove(owner.getUniqueId()); + + boolean success = Bukkit.dispatchCommand(Bukkit.getConsoleSender(), deleteCommand.replace("%world%", worldName)); + + Bukkit.getScheduler().runTaskLater(this, () -> { + dataConfig.set("players." + owner.getUniqueId() + ".worlds." + worldName, null); + saveDataConfig(); + + World world = Bukkit.getWorld(worldName); + if (world != null) { + Bukkit.unloadWorld(world, false); + } + + if (success) { + owner.sendMessage(msgWorldDeleted.replace("%world%", worldName)); + } else { + owner.sendMessage(msgDeleteFailed.replace("%world%", worldName)); + } + + openListGui(owner); + }, 20L); + } + + private void applyWorldSettings(World world, UUID owner, String worldName) { + WorldBorder border = world.getWorldBorder(); + border.setCenter(0.0, 0.0); + border.setSize(worldBorderSize); + + try { + GameMode.valueOf(defaultGamemode.toUpperCase()); + } catch (IllegalArgumentException ignored) { + } + + world.setGameRule(org.bukkit.GameRule.DO_DAYLIGHT_CYCLE, true); + world.setGameRule(org.bukkit.GameRule.DO_MOB_SPAWNING, true); + world.setGameRule(org.bukkit.GameRule.KEEP_INVENTORY, false); + world.setPVP(isWorldPvpEnabled(owner, worldName)); + + int y = world.getHighestBlockYAt(0, 0) + 1; + world.setSpawnLocation(0, y, 0); + } + + private void teleportToWorld(Player player, String worldName) { + if (!hasAccessToWorld(player.getUniqueId(), worldName)) { + player.sendMessage(msgNoAccessWorld); + return; + } + + World world = Bukkit.getWorld(worldName); + if (world == null) { + player.sendMessage(msgWorldNotFound); + return; + } + + player.teleport(world.getSpawnLocation()); + player.sendMessage(msgTeleported.replace("%world%", worldName)); + } + + private boolean hasAccessToWorld(UUID playerUuid, String worldName) { + UUID owner = getOwnerOfWorld(worldName); + if (owner == null) { + return false; + } + + if (owner.equals(playerUuid)) { + return true; + } + + return isInvited(owner, worldName, playerUuid); + } + + private boolean isTypeAllowed(String type) { + return switch (type.toLowerCase()) { + case "normal" -> allowNormal; + case "flat" -> allowFlat; + case "amplified" -> allowAmplified; + default -> false; + }; + } + + private int getWorldLimit(Player player) { + int highest = noLimitPermissionDefault; + + for (LimitEntry entry : permissionLimits) { + if (player.hasPermission(entry.permission)) { + highest = Math.max(highest, entry.amount); + } + } + + return highest; + } + + private String getNextWorldName(Player player, int number) { + return worldNameFormat + .replace("%player%", sanitizeName(player.getName().toLowerCase())) + .replace("%number%", String.valueOf(number)); + } + + private String sanitizeName(String input) { + return input.replaceAll("[^a-zA-Z0-9_\\-]", ""); + } + + private List getOwnedWorlds(UUID uuid) { + ConfigurationSection section = dataConfig.getConfigurationSection("players." + uuid + ".worlds"); + if (section == null) { + return new ArrayList<>(); + } + return new ArrayList<>(section.getKeys(false)); + } + + private List getAccessibleInvitedWorlds(UUID playerUuid) { + List worlds = new ArrayList<>(); + ConfigurationSection playersSection = dataConfig.getConfigurationSection("players"); + if (playersSection == null) { + return worlds; + } + + for (String ownerKey : playersSection.getKeys(false)) { + ConfigurationSection worldsSection = playersSection.getConfigurationSection(ownerKey + ".worlds"); + if (worldsSection == null) { + continue; + } + + for (String worldName : worldsSection.getKeys(false)) { + if (dataConfig.contains("players." + ownerKey + ".worlds." + worldName + ".invites." + playerUuid)) { + worlds.add(worldName); + } + } + } + + return worlds; + } + + private String getWorldType(UUID uuid, String worldName) { + return dataConfig.getString("players." + uuid + ".worlds." + worldName + ".type", "unknown"); + } + + private boolean ownsWorld(UUID uuid, String worldName) { + return dataConfig.contains("players." + uuid + ".worlds." + worldName); + } + + private void registerOwnedWorld(UUID uuid, String worldName, String type) { + dataConfig.set("players." + uuid + ".worlds." + worldName + ".type", type); + dataConfig.set("players." + uuid + ".worlds." + worldName + ".created-at", System.currentTimeMillis()); + } + + private void setWorldPvp(UUID uuid, String worldName, boolean enabled) { + dataConfig.set("players." + uuid + ".worlds." + worldName + ".pvp", enabled); + } + + private boolean isWorldPvpEnabled(UUID uuid, String worldName) { + return dataConfig.getBoolean("players." + uuid + ".worlds." + worldName + ".pvp", true); + } + + private boolean isInvited(UUID ownerUuid, String worldName, UUID targetUuid) { + return dataConfig.contains("players." + ownerUuid + ".worlds." + worldName + ".invites." + targetUuid); + } + + private UUID getInvitedUuidByName(UUID ownerUuid, String worldName, String playerName) { + ConfigurationSection section = dataConfig.getConfigurationSection("players." + ownerUuid + ".worlds." + worldName + ".invites"); + if (section == null) { + return null; + } + + for (String uuidString : section.getKeys(false)) { + String storedName = section.getString(uuidString, ""); + if (storedName.equalsIgnoreCase(playerName)) { + try { + return UUID.fromString(uuidString); + } catch (IllegalArgumentException ignored) { + } + } + } + + return null; + } + + private List getInvitedPlayers(UUID ownerUuid, String worldName) { + ConfigurationSection section = dataConfig.getConfigurationSection("players." + ownerUuid + ".worlds." + worldName + ".invites"); + if (section == null) { + return new ArrayList<>(); + } + + List names = new ArrayList<>(); + for (String uuid : section.getKeys(false)) { + names.add(section.getString(uuid, uuid)); + } + return names; + } + + private UUID getOwnerOfWorld(String worldName) { + ConfigurationSection playersSection = dataConfig.getConfigurationSection("players"); + if (playersSection == null) { + return null; + } + + for (String ownerKey : playersSection.getKeys(false)) { + if (dataConfig.contains("players." + ownerKey + ".worlds." + worldName)) { + try { + return UUID.fromString(ownerKey); + } catch (IllegalArgumentException ignored) { + return null; + } + } + } + + return null; + } + + private int normalizeSize(int size) { + if (size < 9) { + return 9; + } + if (size > 54) { + return 54; + } + return ((size + 8) / 9) * 9; + } + + private String color(String input) { + return input == null ? "" : ChatColor.translateAlternateColorCodes('&', input); + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player player)) { + return; + } + + GuiContext context = openMenus.get(player.getUniqueId()); + if (context == null) { + return; + } + + String title = event.getView().getTitle(); + if (!title.startsWith(mainGuiTitle) && !title.startsWith(listGuiTitle) && !title.startsWith(manageGuiTitle)) { + return; + } + + event.setCancelled(true); + + if (event.getCurrentItem() == null || !event.getCurrentItem().hasItemMeta()) { + return; + } + + ItemMeta meta = event.getCurrentItem().getItemMeta(); + if (meta == null) { + return; + } + + String action = meta.getPersistentDataContainer().get(NamespacedKeys.ACTION_KEY, PersistentDataType.STRING); + if (action == null) { + return; + } + + if (action.equals("GENERATE_NORMAL")) { + generateWorld(player, "normal"); + return; + } + + if (action.equals("GENERATE_FLAT")) { + generateWorld(player, "flat"); + return; + } + + if (action.equals("GENERATE_AMPLIFIED")) { + generateWorld(player, "amplified"); + return; + } + + if (action.equals("LIST_WORLDS")) { + openListGui(player); + return; + } + + if (action.equals("BACK_TO_MAIN")) { + openMainGui(player); + return; + } + + if (action.equals("BACK_TO_LIST")) { + openListGui(player); + return; + } + + if (action.startsWith("OPEN_MANAGE:")) { + openManageGui(player, action.substring("OPEN_MANAGE:".length())); + return; + } + + if (action.startsWith("TELEPORT:")) { + teleportToWorld(player, action.substring("TELEPORT:".length())); + player.closeInventory(); + return; + } + + if (action.startsWith("TOGGLE_PVP:")) { + String worldName = action.substring("TOGGLE_PVP:".length()); + + if (!player.hasPermission("myworlds.pvp")) { + player.sendMessage(msgNoPermission); + return; + } + + if (!ownsWorld(player.getUniqueId(), worldName)) { + player.sendMessage(msgOnlyOwnWorlds); + return; + } + + boolean newState = !isWorldPvpEnabled(player.getUniqueId(), worldName); + setWorldPvp(player.getUniqueId(), worldName, newState); + + World world = Bukkit.getWorld(worldName); + if (world != null) { + world.setPVP(newState); + } + + saveDataConfig(); + player.sendMessage((newState ? msgPvpEnabled : msgPvpDisabled).replace("%world%", worldName)); + openManageGui(player, worldName); + return; + } + + if (action.startsWith("DELETE_WORLD:")) { + handleDeleteWorld(player, action.substring("DELETE_WORLD:".length())); + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (event.getPlayer() instanceof Player player) { + Bukkit.getScheduler().runTaskLater(this, () -> { + String currentTitle = player.getOpenInventory().getTitle(); + if (!currentTitle.startsWith(mainGuiTitle) && + !currentTitle.startsWith(listGuiTitle) && + !currentTitle.startsWith(manageGuiTitle)) { + openMenus.remove(player.getUniqueId()); + } + }, 1L); + } + } + + private static final class GuiContext { + private final String menuType; + private final Map slotWorldMap; + + private GuiContext(String menuType, Map slotWorldMap) { + this.menuType = menuType; + this.slotWorldMap = slotWorldMap; + } + } + + private static final class LimitEntry { + private final String permission; + private final int amount; + + private LimitEntry(String permission, int amount) { + this.permission = permission; + this.amount = amount; + } + } +} diff --git a/src/main/java/com/bitnix/myworlds/NamespacedKeys.java b/src/main/java/com/bitnix/myworlds/NamespacedKeys.java new file mode 100644 index 0000000..9731dcd --- /dev/null +++ b/src/main/java/com/bitnix/myworlds/NamespacedKeys.java @@ -0,0 +1,16 @@ +package com.bitnix.myworlds; + +import org.bukkit.NamespacedKey; +import org.bukkit.plugin.java.JavaPlugin; + +public final class NamespacedKeys { + + public static NamespacedKey ACTION_KEY; + + private NamespacedKeys() { + } + + public static void init(JavaPlugin plugin) { + ACTION_KEY = new NamespacedKey(plugin, "action"); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..19643dc --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,152 @@ +settings: + quick-teleport-single-world: false + world-name-format: "mw_%player%_%number%" + world-border-size: 10000 + default-gamemode: SURVIVAL + no-limit-permission-default: 0 + +multiverse: + normal-command: "mv create %world% normal" + flat-command: "mv create %world% normal -t flat" + amplified-command: "mv create %world% normal -a true" + delete-command: "mv delete %world%" + +limits: + - permission: "myworlds.limit.1" + amount: 1 + - permission: "myworlds.limit.2" + amount: 2 + - permission: "myworlds.limit.3" + amount: 3 + - permission: "myworlds.limit.5" + amount: 5 + +world-types: + normal: true + flat: true + amplified: true + +messages: + no-permission: "&cYou do not have permission." + player-only: "&cOnly players can use this command." + reloaded: "&aMyWorlds config reloaded." + no-worlds: "&cYou do not own any worlds yet." + world-limit-reached: "&cYou have reached your world limit of &e%limit%&c." + no-world-limit: "&cYou are not allowed to create any worlds." + invalid-world-type: "&cInvalid world type." + world-creating: "&eCreating your world: &f%world%&e..." + world-created: "&aWorld created: &e%world%&a." + world-failed: "&cFailed to create world." + world-not-found: "&cThat world could not be found." + teleported: "&aTeleported to &e%world%&a." + only-own-worlds: "&cYou can only manage your own worlds." + no-access-world: "&cYou do not have access to that world." + multiverse-missing: "&cMultiverse-Core was not found or is not enabled." + player-not-found: "&cThat player is not online." + invited-player: "&aInvited &e%player% &ato world &e%world%&a." + uninvited-player: "&e%player% &chas been removed from world &e%world%&c." + you-were-invited: "&aYou were invited to world &e%world% &aby &e%owner%&a." + already-invited: "&e%player% &cis already invited to &e%world%&c." + not-invited: "&e%player% &cis not invited to &e%world%&c." + pvp-enabled: "&aPvP enabled for &e%world%&a." + pvp-disabled: "&cPvP disabled for &e%world%&c." + invalid-pvp-state: "&cUse on/off." + world-deleted: "&cWorld deleted: &e%world%&c." + delete-failed: "&cFailed to delete world &e%world%&c." + confirm-delete: "&cClick again to confirm deletion of &e%world%&c." + world-info-owner: "&7Owner: &e%owner%" + world-info-type: "&7Type: &e%type%" + world-info-pvp: "&7PvP: &e%pvp%" + world-info-invites: "&7Invited: &e%invites%" + +gui: + main: + title: "&8My Worlds" + size: 27 + items: + generate-normal: + material: GRASS_BLOCK + slot: 10 + name: "&aGenerate Normal World" + lore: + - "&7Create a standard world." + + generate-flat: + material: DIRT + slot: 12 + name: "&eGenerate Flat World" + lore: + - "&7Create a flat world." + + generate-amplified: + material: STONE + slot: 14 + name: "&bGenerate Amplified World" + lore: + - "&7Create an amplified world." + + list-worlds: + material: CHEST + slot: 16 + name: "&fYour Worlds" + lore: + - "&7Click to view your worlds." + + info: + material: BOOK + slot: 22 + name: "&6World Info" + lore: + - "&7Owned: &e%owned%" + - "&7Limit: &e%limit%" + + list: + title: "&8Your Worlds" + size: 54 + back-button: + material: ARROW + slot: 53 + name: "&cBack" + lore: + - "&7Return to main menu." + + manage: + title: "&8Manage World" + size: 27 + items: + teleport: + material: ENDER_PEARL + slot: 11 + name: "&aTeleport" + lore: + - "&7Teleport to this world." + + toggle-pvp: + material: DIAMOND_SWORD + slot: 13 + name: "&cToggle PvP" + lore: + - "&7Toggle PvP for this world." + + invited-info: + material: PLAYER_HEAD + slot: 15 + name: "&eInvites" + lore: + - "&7Invited players:" + - "&f%invites%" + + delete-world: + material: BARRIER + slot: 22 + name: "&4Delete World" + lore: + - "&7Delete this world." + - "&cThis cannot be undone." + + back: + material: ARROW + slot: 26 + name: "&cBack" + lore: + - "&7Return to world list." diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..0ea501b --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,20 @@ +name: MyWorlds +version: 1.1 +main: com.bitnix.myworlds.MyWorldsPlugin +api-version: '1.21' +author: bitnix +description: GUI-based personal world manager using Multiverse-Core. +commands: + myworlds: + description: Open your worlds GUI or manage your worlds + usage: /myworlds [help|list|generate|invite|uninvite|pvp|reload] + aliases: [myworld, mw] +permissions: + myworlds.use: + default: false + myworlds.admin: + default: op + myworlds.invite: + default: false + myworlds.pvp: + default: false diff --git a/target/MyWorlds.jar b/target/MyWorlds.jar new file mode 100644 index 0000000000000000000000000000000000000000..02d8a1c4c32c9a92670c4829375fc18ae8ace83d GIT binary patch literal 22685 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 WQ7?1qs^kzLL|x|W;iU-z!v6ry&(Sjg literal 0 HcmV?d00001 diff --git a/target/classes/com/bitnix/myworlds/MyWorldsPlugin$GuiContext.class b/target/classes/com/bitnix/myworlds/MyWorldsPlugin$GuiContext.class new file mode 100644 index 0000000000000000000000000000000000000000..6dc764cbadec958ecd9c8633756721e267fbf6a3 GIT binary patch literal 820 zcmb7CT~FIE6g^H2ZL^H9^0C1-28ajffb40~mI`UAG=^5#q@wQGEmmfhIw)~~{;WM9 zq&@5h@T1VKLnNqpV7&NVpKG6UuaB>;|NI5;39Bv~RI1<}CNRk`eJoBz5DOgz-}=XL zU>GJ_N-ML&P+8yT)loye3fIFEcnn*EWEk|7(ds-Hj?R)aKFWgq(T^he9_NwLYrDB> zC)&t!;;Id$&JRZ?lA+PLmFk*Q=_s`1QyzRwGk95?n1W|toG|z|r*os?fPj#p-c^wn zCQpg${dl!?hkB2cBbkPGpi*szNY>Wx9D&8=xK0F8+wx_jB+p)+pNz=Qxw&NZ7>d<#{(6HZzvW7Jo49`lGQEdV=!xC1o_z$)o%$@)M literal 0 HcmV?d00001 diff --git a/target/classes/com/bitnix/myworlds/MyWorldsPlugin$LimitEntry.class b/target/classes/com/bitnix/myworlds/MyWorldsPlugin$LimitEntry.class new file mode 100644 index 0000000000000000000000000000000000000000..ebae9d48d1fad8629227117117a65730dd3bcfbb GIT binary patch literal 582 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