diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java index c639524..cccfd90 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java @@ -5,27 +5,47 @@ import fr.pandacube.lib.paper.json.PaperJson; import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager; import org.bukkit.plugin.Plugin; +/** + * Main class for pandalib-paper. + */ public class PandaLibPaper { private static Plugin plugin; - + + /** + * Method to call in plugin's {@link Plugin#onLoad()} method. + * @param plugin the plugin instance. + */ public static void onLoad(Plugin plugin) { PandaLibPaper.plugin = plugin; PaperJson.init(); } + /** + * Method to call in plugin's {@link Plugin#onEnable()} method. + */ public static void onEnable() { PerformanceAnalysisManager.getInstance(); // initialize ServerStopEvent.init(); } + + /** + * Method to call in plugin's {@link Plugin#onDisable()} method. + */ public static void disable() { - PerformanceAnalysisManager.getInstance().cancelInternalBossBar(); + PerformanceAnalysisManager.getInstance().deinit(); } - + + /** + * Gets the plugin instance. + * @return the plugin instance provided with {@link #onLoad(Plugin)}. + */ public static Plugin getPlugin() { return plugin; } + private PandaLibPaper() {} + } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupConfig.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupConfig.java index f685d0f..57d3367 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupConfig.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupConfig.java @@ -12,6 +12,11 @@ import java.util.List; @SuppressWarnings("CanBeFinal") public class PaperBackupConfig { + /** + * Creates a new Paper backup config. + */ + public PaperBackupConfig() {} + /** * Set to true to enable worlds backup. * Defaults to true. diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupManager.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupManager.java index 7a4d144..50f6268 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupManager.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupManager.java @@ -130,12 +130,12 @@ public class PaperBackupManager extends BackupManager implements Listener { private final Set dirtyForSave = new HashSet<>(); @EventHandler(priority = EventPriority.MONITOR) - public void onWorldLoad(WorldLoadEvent event) { + void onWorldLoad(WorldLoadEvent event) { initWorldProcess(event.getWorld().getName()); } @EventHandler(priority = EventPriority.MONITOR) - public void onWorldSave(WorldSaveEvent event) { + void onWorldSave(WorldSaveEvent event) { if (event.getWorld().getLoadedChunks().length > 0 || dirtyForSave.contains(event.getWorld().getName())) { compressWorlds.get(event.getWorld().getName()).setDirtyAfterSave(); @@ -148,18 +148,18 @@ public class PaperBackupManager extends BackupManager implements Listener { @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) { + void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) { dirtyForSave.add(event.getFrom().getName()); dirtyForSave.add(event.getPlayer().getWorld().getName()); } @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerJoin(PlayerJoinEvent event) { + void onPlayerJoin(PlayerJoinEvent event) { dirtyForSave.add(event.getPlayer().getWorld().getName()); } @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerQuit(PlayerQuitEvent event) { + void onPlayerQuit(PlayerQuitEvent event) { dirtyForSave.add(event.getPlayer().getWorld().getName()); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupProcess.java index df8a466..71a20c8 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupProcess.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupProcess.java @@ -10,11 +10,19 @@ import net.kyori.adventure.bossbar.BossBar.Color; import net.kyori.adventure.bossbar.BossBar.Overlay; import org.bukkit.Bukkit; +/** + * A backup process with specific logic around Paper server. + */ public abstract class PaperBackupProcess extends BackupProcess { private BossBar bossBar; - + + /** + * Instantiates a new backup process. + * @param bm the associated backup manager. + * @param id the process identifier. + */ protected PaperBackupProcess(PaperBackupManager bm, String id) { super(bm, id); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorkdirProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorkdirProcess.java index 01c5155..332b3f2 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorkdirProcess.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorkdirProcess.java @@ -3,8 +3,15 @@ package fr.pandacube.lib.paper.backup; import java.io.File; import java.util.function.BiPredicate; +/** + * A backup process with specific logic around Paper server working directory. + */ public class PaperWorkdirProcess extends PaperBackupProcess { - + + /** + * Instantiates a new backup process for the paper server working directory. + * @param bm the associated backup manager. + */ protected PaperWorkdirProcess(PaperBackupManager bm) { super(bm, "workdir"); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorldProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorldProcess.java index 237664a..df7d3f0 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorldProcess.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorldProcess.java @@ -1,9 +1,9 @@ package fr.pandacube.lib.paper.backup; +import fr.pandacube.lib.chat.LegacyChatFormat; import fr.pandacube.lib.paper.scheduler.SchedulerUtil; import fr.pandacube.lib.paper.world.WorldUtil; import fr.pandacube.lib.util.log.Log; -import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; import org.bukkit.World; @@ -11,14 +11,22 @@ import java.io.File; import java.text.DateFormat; import java.util.Date; +/** + * A backup process with specific logic around Paper server world. + */ public class PaperWorldProcess extends PaperBackupProcess { private final String worldName; - private boolean autoSave = true; - - protected PaperWorldProcess(PaperBackupManager bm, final String n) { - super(bm, "worlds/" + n); - worldName = n; + private boolean autoSave = true; + + /** + * Instantiates a new backup process for a world. + * @param bm the associated backup manager. + * @param worldName the name of the world. + */ + protected PaperWorldProcess(PaperBackupManager bm, final String worldName) { + super(bm, "worlds/" + worldName); + this.worldName = worldName; } private World getWorld() { @@ -62,11 +70,11 @@ public class PaperWorldProcess extends PaperBackupProcess { public void displayNextSchedule() { if (hasNextScheduled()) { - Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is dirty. Next backup on " + Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " is dirty. Next backup on " + DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext()))); } else { - Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is clean. Next backup not scheduled."); + Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " is clean. Next backup not scheduled."); } } @@ -80,7 +88,7 @@ public class PaperWorldProcess extends PaperBackupProcess { public void setDirtyAfterSave() { if (!isDirty()) { // don't set dirty if it is already setDirtySinceNow(); - Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " was saved and is now dirty. Next backup on " + Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " was saved and is now dirty. Next backup on " + DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG) .format(new Date(getNext())) ); diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java index 9b232d2..af5d598 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java @@ -57,10 +57,21 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand vanillaPaperDispatcher = null; + /** + * Gets the Brigadier dispatcher provided by paper API during {@link LifecycleEvents#COMMANDS}. + *

+ * This Dispatcher is not the vanilla one. Instead, Paper implementation wraps the vanilla one to handle proper registration + * of commands from plugins. + * @return the Brigadier dispatcher. + */ public static CommandDispatcher getVanillaPaperDispatcher() { return vanillaPaperDispatcher; } + /** + * Gets the root node of the dispatcher from {@link #getVanillaPaperDispatcher()}. + * @return the root node, or null if {@link #getVanillaPaperDispatcher()} is also null. + */ public static RootCommandNode getRootNode() { return vanillaPaperDispatcher == null ? null : vanillaPaperDispatcher.getRoot(); } @@ -135,6 +146,9 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand cmdNodeClass = Reflect.ofClass(CommandNode.class); + @SuppressWarnings("unchecked") CommandNode unwrappedNode = (CommandNode) cmdNodeClass.field("unwrappedCached").getValue(originalNode); if (unwrappedNode != null) { cmdNodeClass.field("modifier").setValue(unwrappedNode, cmdNodeClass.field("modifier").getValue(originalNode)); @@ -333,6 +349,10 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand getRegisteredAliases() { return Set.copyOf(registeredAliases); } @@ -379,12 +399,15 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand hasPermission(String permission) { return wrapper -> getCommandSender(wrapper).hasPermission(permission); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/event/ServerStopEvent.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/event/ServerStopEvent.java index a3ca473..12ef26c 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/event/ServerStopEvent.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/event/ServerStopEvent.java @@ -10,28 +10,38 @@ import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.ServerEvent; import org.jetbrains.annotations.NotNull; +/** + * Fired at the beginning of the server stop process. + * More specifically, this event is called when the first plugin is disabling ({@link PluginDisableEvent}) while + * {@link Bukkit#isStopping()} returns true. + *

+ * This event can be useful when a plugin want to execute stuff on server stop as soon as possible in the process, + * but not when the plugin itself is disabling (because some part of the Bukkit API is not usable at that moment). + */ public class ServerStopEvent extends ServerEvent { private static final HandlerList handlers = new HandlerList(); - @NotNull - @Override - public HandlerList getHandlers() { - return handlers; - } - + /** + * Gets the handler list of the event. + * @return the handler list of the event. + */ @NotNull public static HandlerList getHandlerList() { return handlers; } - - - private static boolean hasTriggered = false; + private static boolean isInit = false; + /** + * Register the event used to detect the server stop. + */ public static void init() { + if (isInit) + return; + BukkitEvent.register(new Listener() { @EventHandler(priority = EventPriority.LOWEST) @@ -45,7 +55,25 @@ public class ServerStopEvent extends ServerEvent { } }); + + isInit = true; } + + + + private ServerStopEvent() {} + + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + + + + } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java index 2dfbfa3..05799f7 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java @@ -37,13 +37,21 @@ public class GUIHotBar implements Listener { private final int defaultSlot; private final List currentPlayers = new ArrayList<>(); - + + /** + * Setup a new gui hot bar. You should not instantiate more than one hot bar. + * @param defaultSlot the default slot (currently held item) when the player joins the hot bar. + */ public GUIHotBar(int defaultSlot) { this.defaultSlot = Math.max(0, Math.min(8, defaultSlot)); BukkitEvent.register(this); } + /** + * Disables this hot bar. + * @param clearPlayerMenuItems if the items of this hot bar should be removed from the players inventories. + */ public void disable(boolean clearPlayerMenuItems) { removeAllPlayers(clearPlayerMenuItems); @@ -53,9 +61,10 @@ public class GUIHotBar implements Listener { /** * Add the item to this hot bar menu. if there is already players hooked to this hot bar, the item will be directly added to * their inventories. - * @param i the item stack + * @param i the item stack. * @param setter code executed to put the item in the inventory. Additionally, check for permission before doing the addition. * @param run the Runnable to run when the user right-click on the item in the hot bar. + * @return itself for daisy-chaining. */ public GUIHotBar addItem(ItemStack i, BiConsumer setter, Consumer run) { itemsAndSetters.put(i, setter); @@ -71,6 +80,7 @@ public class GUIHotBar implements Listener { * Add the hot bar elements to this player, or update them if applicable. *
* The player is automatically removed when they quit. You can remove it before by calling {@link #removePlayer(Player, boolean)}. + * @param p the player to add. */ public void addPlayer(Player p) { if (!currentPlayers.contains(p)) @@ -85,6 +95,7 @@ public class GUIHotBar implements Listener { /** * Detach this player from this hot bar manager and removes the managed items from the players inventory. + * @param p the player to remove. */ public void removePlayer(Player p) { removePlayer(p, true); @@ -92,6 +103,8 @@ public class GUIHotBar implements Listener { /** * Detach this player from this hot bar manager and optionally removes the managed items from the players inventory. + * @param p the player to remove. + * @param clearMenuItems if the items from this hot bar should be removed from the player inventory. */ public void removePlayer(Player p, boolean clearMenuItems) { if (!currentPlayers.contains(p)) @@ -106,18 +119,28 @@ public class GUIHotBar implements Listener { currentPlayers.remove(p); } - - + + /** + * Tells if the provided player is attached to this hot bar. + * @param p the player to check. + * @return true if the player is attached, false otherwise. + */ public boolean containsPlayer(Player p) { return currentPlayers.contains(p); } - - - + + + /** + * Detach all players from this hot bar. + */ public void removeAllPlayers() { removeAllPlayers(true); } + /** + * Detach all players from this hot bar. + * @param clearMenuItems if the items from this hot bar should be removed from the player inventory. + */ public void removeAllPlayers(boolean clearMenuItems) { for (Player p : new ArrayList<>(currentPlayers)) removePlayer(p, clearMenuItems); @@ -127,7 +150,7 @@ public class GUIHotBar implements Listener { - public void addItemToPlayer(Player p, ItemStack is) { + private void addItemToPlayer(Player p, ItemStack is) { if (!itemsAndSetters.containsKey(is)) throw new IllegalArgumentException("The provided ItemStack is not registered in this GUIHotBar"); if (!currentPlayers.contains(p)) @@ -135,7 +158,7 @@ public class GUIHotBar implements Listener { itemsAndSetters.get(is).accept(p.getInventory(), is.clone()); } - public void removeItemFromPlayer(Player p, ItemStack is) { + private void removeItemFromPlayer(Player p, ItemStack is) { p.getInventory().remove(is); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/DummyPlayerInventory.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/DummyPlayerInventory.java new file mode 100644 index 0000000..a963ecf --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/DummyPlayerInventory.java @@ -0,0 +1,191 @@ +package fr.pandacube.lib.paper.inventory; + +import com.google.common.base.Preconditions; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Dummy implementation of a player inventory. + */ +public class DummyPlayerInventory extends InventoryWrapper implements PlayerInventory { + + private int heldItemSlot; + + /** + * Creates a dummy player inventory. + * @param base the inventory itself. + * @param heldItemSlot the currently held item slot, from 0 to 8. + */ + public DummyPlayerInventory(Inventory base, int heldItemSlot) { + super(base); + if (base.getSize() < 41) + throw new IllegalArgumentException("base inventory should have a size of 41 (" + base.getSize() + " given)."); + if (heldItemSlot < 0 || heldItemSlot > 8) + throw new IllegalArgumentException("heldItemSlot should be between 0 and 8 inclusive."); + this.heldItemSlot = heldItemSlot; + } + + @Override + public @Nullable ItemStack @NotNull [] getStorageContents() { + return Arrays.copyOfRange(getContents(), 0, 36); + } + + @Override + public @Nullable ItemStack @NotNull [] getArmorContents() { + return Arrays.copyOfRange(getContents(), 36, 40); + } + + @Override + public void setArmorContents(@Nullable ItemStack[] items) { + this.setSlots(items, 36, 4); + } + + @Override + public @Nullable ItemStack @NotNull [] getExtraContents() { + return Arrays.copyOfRange(getContents(), 40, getSize()); + } + + @Override + public void setExtraContents(@Nullable ItemStack[] items) { + this.setSlots(items, 40, getSize() - 40); + } + + private void setSlots(ItemStack[] items, int baseSlot, int length) { + if (items == null) { + items = new ItemStack[length]; + } + Preconditions.checkArgument(items.length <= length, "items.length must be < %s", length); + + for (int i = 0; i < length; i++) { + if (i >= items.length) { + this.setItem(baseSlot + i, null); + } else { + this.setItem(baseSlot + i, items[i]); + } + } + } + + @Override + public ItemStack getHelmet() { + return getItem(39); + } + + @Override + public void setHelmet(@Nullable ItemStack helmet) { + setItem(39, helmet); + } + + @Override + public ItemStack getChestplate() { + return getItem(38); + } + + @Override + public void setChestplate(@Nullable ItemStack chestplate) { + setItem(38, chestplate); + } + + @Override + public ItemStack getLeggings() { + return getItem(37); + } + + @Override + public void setLeggings(@Nullable ItemStack leggings) { + setItem(37, leggings); + } + + @Override + public ItemStack getBoots() { + return getItem(36); + } + + @Override + public void setBoots(@Nullable ItemStack boots) { + setItem(36, boots); + } + + @Override + public void setItem(EquipmentSlot slot, ItemStack item) { + Preconditions.checkArgument(slot != null, "slot must not be null"); + + switch (slot) { + case HAND -> this.setItemInMainHand(item); + case OFF_HAND -> this.setItemInOffHand(item); + case FEET -> this.setBoots(item); + case LEGS -> this.setLeggings(item); + case CHEST -> this.setChestplate(item); + case HEAD -> this.setHelmet(item); + default -> throw new IllegalArgumentException("Not implemented. This is a bug"); + } + } + + @Override + public @NotNull ItemStack getItem(@NotNull EquipmentSlot slot) { + return switch (slot) { + case HAND -> this.getItemInMainHand(); + case OFF_HAND -> this.getItemInOffHand(); + case FEET -> Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(Material.AIR)); + case LEGS -> Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(Material.AIR)); + case CHEST -> Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(Material.AIR)); + case HEAD -> Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(Material.AIR)); + case BODY -> new ItemStack(Material.AIR); // for horses/wolves armor + }; + } + + @Override + public @NotNull ItemStack getItemInMainHand() { + return Objects.requireNonNullElse(getItem(heldItemSlot), new ItemStack(Material.AIR)); + } + + @Override + public void setItemInMainHand(@Nullable ItemStack item) { + setItem(heldItemSlot, item); + } + + @Override + public @NotNull ItemStack getItemInOffHand() { + return Objects.requireNonNullElse(getItem(40), new ItemStack(Material.AIR)); + } + + @Override + public void setItemInOffHand(@Nullable ItemStack item) { + setItem(40, item); + } + + @Override + public @NotNull ItemStack getItemInHand() { + return getItemInMainHand(); + } + + @Override + public void setItemInHand(@Nullable ItemStack stack) { + setItemInMainHand(stack); + } + + @Override + public int getHeldItemSlot() { + return heldItemSlot; + } + + @Override + public void setHeldItemSlot(int slot) { + if (slot < 0 || slot > 8) + throw new IllegalArgumentException("Slot is not between 0 and 8 inclusive"); + heldItemSlot = slot; + } + + @Override + public @Nullable HumanEntity getHolder() { + return null; + } +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/InventoryWrapper.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/InventoryWrapper.java similarity index 91% rename from pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/InventoryWrapper.java rename to pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/InventoryWrapper.java index e7085c4..35e6658 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/InventoryWrapper.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/InventoryWrapper.java @@ -1,4 +1,4 @@ -package fr.pandacube.lib.paper.util; +package fr.pandacube.lib.paper.inventory; import org.bukkit.Location; import org.bukkit.Material; @@ -13,12 +13,22 @@ import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.List; import java.util.ListIterator; +import java.util.Objects; +/** + * Wrapper for an {@link Inventory}. + * Can be overridden to add specific behaviour to some methods. + */ public class InventoryWrapper implements Inventory { private final Inventory base; + /** + * Creates a wrapper for the provided inventory. + * @param base the wrapped inventory. Cannot be null. + * @throws NullPointerException if the base inventory is null. + */ public InventoryWrapper(Inventory base) { - this.base = base; + this.base = Objects.requireNonNull(base, "base inventory cannot be null."); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ItemStackBuilder.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/ItemStackBuilder.java similarity index 58% rename from pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ItemStackBuilder.java rename to pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/ItemStackBuilder.java index 941dd52..45dab58 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ItemStackBuilder.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/inventory/ItemStackBuilder.java @@ -1,4 +1,4 @@ -package fr.pandacube.lib.paper.util; +package fr.pandacube.lib.paper.inventory; import com.google.common.collect.Streams; import fr.pandacube.lib.chat.Chat; @@ -18,6 +18,9 @@ import java.util.function.Consumer; import static fr.pandacube.lib.chat.ChatStatic.chatComponent; +/** + * A builder for {@link ItemStack}. + */ public class ItemStackBuilder { /** @@ -77,10 +80,22 @@ public class ItemStackBuilder { return (cachedMeta != null) ? cachedMeta : (cachedMeta = stack.getItemMeta()); } + /** + * Runs the provided updater on the {@link ItemMeta} instance of the built stack. + * @param metaUpdater the updater that will modify the meta. + * @return itself. + */ public ItemStackBuilder meta(Consumer metaUpdater) { return meta(metaUpdater, ItemMeta.class); } + /** + * Runs the provided updater on the {@link ItemMeta} instance of the built stack. + * @param metaUpdater the updater that will modify the meta. + * @param metaType the type of the meta instance. + * @param the type of item meta. + * @return itself. + */ public ItemStackBuilder meta(Consumer metaUpdater, Class metaType) { stack.editMeta(metaType, m -> { metaUpdater.accept(m); @@ -88,28 +103,52 @@ public class ItemStackBuilder { }); return this; } - + + /** + * Sets the amount of the built stack. + * @param a the new amount. + * @return itself. + */ public ItemStackBuilder amount(int a) { stack.setAmount(a); return this; } + /** + * Sets the display name of the item, directly passing to {@link ItemMeta#displayName(Component)}. + * @param displayName the new display name. Can be null to unset. + * @return itself. + */ public ItemStackBuilder rawDisplayName(Component displayName) { return meta(m -> m.displayName(displayName)); } - + + /** + * Sets the display name of the item, filtering to make default italic to false. + * @param displayName the new display name. Can be null to unset. + * @return itself. + */ public ItemStackBuilder displayName(ComponentLike displayName) { - if (displayName != null) { - return rawDisplayName(Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent()); - } - else - return rawDisplayName(null); + return rawDisplayName(displayName != null + ? Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent() + : null); } - + + /** + * Sets the lore of the item, directly passing to {@link ItemMeta#lore(List)}. + * @param lore the new lore. Can be null to unset. + * @return itself. + */ public ItemStackBuilder rawLore(List lore) { return meta(m -> m.lore(lore)); } - + + + /** + * Sets the lore of the item, filtering to make default italic to false. + * @param lore the new lore. Can be null to unset. + * @return itself. + */ public ItemStackBuilder lore(List lore) { if (lore != null) { return rawLore(lore.stream() @@ -119,7 +158,12 @@ public class ItemStackBuilder { else return rawLore(Collections.emptyList()); } - + + /** + * Adds new lore lines to the existing lore of the item. + * @param lores the added lore lines. + * @return itself. + */ public ItemStackBuilder addLoreAfter(List lores) { if (lores != null) { List baseLore = getOrInitMeta().lore(); @@ -135,7 +179,12 @@ public class ItemStackBuilder { else return this; } - + + /** + * Adds new lore lines to the existing lore of the item. + * @param lores the added lore lines. + * @return itself. + */ public ItemStackBuilder addLoreAfter(ComponentLike... lores) { if (lores == null || lores.length == 0) return this; @@ -143,69 +192,120 @@ public class ItemStackBuilder { } /** + * Enchant the item. * Supports unsafe enchants. + * @param enchantment the enchantment. + * @param level the enchant level. + * @return itself. */ public ItemStackBuilder enchant(Enchantment enchantment, int level) { return meta(m -> m.addEnchant(enchantment, level, true)); } + /** + * Adds the provided flags to the item. + * @param flags he flags to add. + * @return itself. + */ public ItemStackBuilder flags(ItemFlag... flags) { return flags(true, flags); } + /** + * Adds or removes the provided flags to the item. + * @param add true to add, false to remove. + * @param flags he flags to add. + * @return itself. + */ public ItemStackBuilder flags(boolean add, ItemFlag... flags) { return add ? meta(m -> m.addItemFlags(flags)) : meta(m -> m.removeItemFlags(flags)); } + /** + * Hides the enchants from the tooltip of the item. + * Will set the {@link ItemFlag#HIDE_ENCHANTS} flag of the item. + * @return itself. + */ public ItemStackBuilder hideEnchants() { return hideEnchants(true); } + /** + * Sets or unsets the {@link ItemFlag#HIDE_ENCHANTS} flag of the item. + * @param hide true to hide, false to show. + * @return itself. + */ public ItemStackBuilder hideEnchants(boolean hide) { return flags(hide, ItemFlag.HIDE_ENCHANTS); } + /** + * Hides the attributes from the tooltip of the item. + * Will set the {@link ItemFlag#HIDE_ATTRIBUTES} flag of the item. + * @return itself. + */ public ItemStackBuilder hideAttributes() { return hideAttributes(true); } + /** + * Sets or unsets the {@link ItemFlag#HIDE_ATTRIBUTES} flag of the item. + * @param hide true to hide, false to show. + * @return itself. + */ public ItemStackBuilder hideAttributes(boolean hide) { return flags(hide, ItemFlag.HIDE_ATTRIBUTES); } + /** + * Apply the enchantment glint to the item, event if it's not enchant. + * @return itself. + */ public ItemStackBuilder fakeEnchant() { return fakeEnchant(true); } + /** + * Sets the enchantment glint override to the item. + * @param apply true to enforce the enchantment glint, false to set to default. + * @return itself. + */ public ItemStackBuilder fakeEnchant(boolean apply) { - if (apply) { - enchant(Enchantment.UNBREAKING, 1); - return hideEnchants(); - } - return this; + return meta(m -> m.setEnchantmentGlintOverride(apply ? true : null)); } + /** + * Sets this item as unbreakable. + * @return itself. + */ public ItemStackBuilder unbreakable() { return unbreakable(true); } + /** + * Sets the unbreakable status of this item. + * @param unbreakable the unbreakable status. + * @return itself. + */ public ItemStackBuilder unbreakable(boolean unbreakable) { return meta(m -> m.setUnbreakable(unbreakable)); } - + + /** + * Sets the damage value of this item. + * @param d the new damage value. + * @return itself. + */ public ItemStackBuilder damage(int d) { return meta(m -> m.setDamage(d), Damageable.class); } - - - - - - - - - + + + /** + * Build the {@link ItemStack}. + * @return the build item stack. + */ public ItemStack build() { return stack; } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/PaperJson.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/PaperJson.java index 1d5d07a..692036f 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/PaperJson.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/PaperJson.java @@ -14,4 +14,7 @@ public class PaperJson { Json.registerTypeAdapterFactory(ItemStackAdapter.FACTORY); Json.registerTypeAdapterFactory(ConfigurationSerializableAdapter.FACTORY); } + + + private PaperJson() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/PerformanceAnalysisManager.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/PerformanceAnalysisManager.java index 83e9af5..8412bd9 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/PerformanceAnalysisManager.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/PerformanceAnalysisManager.java @@ -21,7 +21,6 @@ import net.kyori.adventure.bossbar.BossBar.Color; import net.kyori.adventure.bossbar.BossBar.Overlay; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; @@ -44,10 +43,17 @@ import static fr.pandacube.lib.chat.ChatStatic.infoText; import static fr.pandacube.lib.chat.ChatStatic.successText; import static fr.pandacube.lib.chat.ChatStatic.text; +/** + * Various tools to supervise the JVM RAM and the CPU usage of the main server thread. + */ public class PerformanceAnalysisManager implements Listener { private static PerformanceAnalysisManager instance; + /** + * Gets the instance of {@link PerformanceAnalysisManager}. + * @return the instance of {@link PerformanceAnalysisManager}. + */ public static synchronized PerformanceAnalysisManager getInstance() { if (instance == null) instance = new PerformanceAnalysisManager(); @@ -76,14 +82,22 @@ public class PerformanceAnalysisManager implements Listener { private final LinkedList interTPSDurations = new LinkedList<>(); - - + /** + * The boss bar that shows in real time the CPU performance of the main server thread. + */ public final AutoUpdatedBossBar tpsBar; + + /** + * The boss bar that shows in real time the JVM RAM usage. + */ public final AutoUpdatedBossBar memoryBar; private final List barPlayers = new ArrayList<>(); private final List relatedBossBars = new ArrayList<>(); - - + + + /** + * The gradient of color covering the common range of TPS values. + */ public final ChatColorGradient tps1sGradient = new ChatColorGradient() .add(0, NamedTextColor.BLACK) .add(1, NamedTextColor.DARK_RED) @@ -94,23 +108,7 @@ public class PerformanceAnalysisManager implements Listener { .add(21, PandaTheme.CHAT_GREEN_1_NORMAL) .add(26, NamedTextColor.BLUE); - - public final ChatColorGradient tps10sGradient = new ChatColorGradient() - .add(0, NamedTextColor.DARK_RED) - .add(5, NamedTextColor.RED) - .add(10, NamedTextColor.GOLD) - .add(14, NamedTextColor.YELLOW) - .add(19, PandaTheme.CHAT_GREEN_1_NORMAL); - - - public final ChatColorGradient tps1mGradient = new ChatColorGradient() - .add(0, NamedTextColor.DARK_RED) - .add(8, NamedTextColor.RED) - .add(14, NamedTextColor.GOLD) - .add(17, NamedTextColor.YELLOW) - .add(19, PandaTheme.CHAT_GREEN_1_NORMAL); - - public final ChatColorGradient memoryUsageGradient = new ChatColorGradient() + private final ChatColorGradient memoryUsageGradient = new ChatColorGradient() .add(.60f, PandaTheme.CHAT_GREEN_1_NORMAL) .add(.70f, NamedTextColor.YELLOW) .add(.80f, NamedTextColor.GOLD) @@ -132,10 +130,19 @@ public class PerformanceAnalysisManager implements Listener { } + /** + * Tells if the provided players is seeing the performance boss bars. + * @param p the player to verify. + * @return true if the provided players is seeing the performance boss bars, false otherwise. + */ public boolean barsContainsPlayer(Player p) { return barPlayers.contains(p); } - + + /** + * Shows the performance boss bars to the provided player. + * @param p the player. + */ public synchronized void addPlayerToBars(Player p) { barPlayers.add(p); p.showBossBar(tpsBar.bar); @@ -143,7 +150,11 @@ public class PerformanceAnalysisManager implements Listener { for (BossBar bar : relatedBossBars) p.showBossBar(bar); } - + + /** + * Hides the performance boss bars from the provided player. + * @param p the player. + */ public synchronized void removePlayerToBars(Player p) { p.hideBossBar(tpsBar.bar); p.hideBossBar(memoryBar.bar); @@ -151,7 +162,11 @@ public class PerformanceAnalysisManager implements Listener { p.hideBossBar(bar); barPlayers.remove(p); } - + + /** + * Show an additional boss bar to the players currently seeing the performance ones. + * @param bar the new bar to show. + */ public synchronized void addBossBar(BossBar bar) { if (relatedBossBars.contains(bar)) return; @@ -159,7 +174,11 @@ public class PerformanceAnalysisManager implements Listener { for (Player p : barPlayers) p.showBossBar(bar); } - + + /** + * Hides an additional boss bar from the players currently seeing the performance ones. + * @param bar the additional bar to hide. + */ public synchronized void removeBossBar(BossBar bar) { if (!relatedBossBars.contains(bar)) return; @@ -167,8 +186,11 @@ public class PerformanceAnalysisManager implements Listener { for (Player p : barPlayers) p.hideBossBar(bar); } - - public synchronized void cancelInternalBossBar() { + + /** + * De-initialize the performance analyzer. + */ + public synchronized void deinit() { tpsBar.cancel(); memoryBar.cancel(); } @@ -178,7 +200,7 @@ public class PerformanceAnalysisManager implements Listener { @EventHandler - public synchronized void onTickStart(ServerTickStartEvent event) { + synchronized void onTickStart(ServerTickStartEvent event) { tickStartNanoTime = System.nanoTime(); tickStartCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0; @@ -186,7 +208,7 @@ public class PerformanceAnalysisManager implements Listener { } @EventHandler - public synchronized void onTickEnd(ServerTickEndEvent event) { + synchronized void onTickEnd(ServerTickEndEvent event) { tickEndNanoTime = System.nanoTime(); long tickEndCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0; @@ -213,7 +235,7 @@ public class PerformanceAnalysisManager implements Listener { @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { + void onPlayerJoin(PlayerJoinEvent event) { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { @SuppressWarnings("unchecked") AbstractPlayerManager playerManager = (AbstractPlayerManager) AbstractPlayerManager.getInstance(); @@ -233,7 +255,7 @@ public class PerformanceAnalysisManager implements Listener { @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) { + void onPlayerQuit(PlayerQuitEvent event) { removePlayerToBars(event.getPlayer()); } @@ -375,28 +397,34 @@ public class PerformanceAnalysisManager implements Listener { } private Chat alteredTPSTitle = null; - + + /** + * Temporary change the title of the TPS boss bar. + * @param title the title override. null to restore to the normal TPS title. + */ public synchronized void setAlteredTPSTitle(Chat title) { alteredTPSTitle = title; } - - - - - - - - // special case where the getTPS method always returns a whole number when retrieving the TPS for 1 sec + + + + + + + + /** + * Gets the number of tick in the last second. + * @return the number of tick in the last second. + */ public int getTPS1s() { return (int) getTPS(1_000); } /** - * * @param nbTicks number of ticks when the avg value is computed from history * @return the avg number of TPS in the interval */ - public synchronized float getAvgNano(List data, int nbTicks) { + private synchronized float getAvgNano(List data, int nbTicks) { if (data.isEmpty()) return 0; @@ -410,7 +438,7 @@ public class PerformanceAnalysisManager implements Listener { } /** - * + * Gets the average number of tick per second in the n last milliseconds. * @param nbMillis number of milliseconds when the avg TPS is computed from history * @return the avg number of TPS in the interval */ @@ -429,8 +457,12 @@ public class PerformanceAnalysisManager implements Listener { return count * (1000 / (float) nbMillis); } - - + + + /** + * Gets the history of TPS performance. + * @return an array of TPS values from the last minute. The value at 0 is in the last second (current second on the clock - 1), the value at index 1 is now - 2, ... + */ public synchronized int[] getTPSHistory() { int[] history = new int[60]; @@ -448,15 +480,22 @@ public class PerformanceAnalysisManager implements Listener { } - + /** + * Gets the current server's target tick rate. + * Usually 20 but the server can be configured to tick at a different rate. + * @return the current server's target tick rate. + */ public static int getTargetTickRate() { return Math.round(Bukkit.getServerTickManager().getTickRate()); } - - - - - + + + /** + * Runs the garbage collector on the server. + * Depending on the server load and the used memory, this can freeze the server for a second. + * @param sender the command sender that triggers the garbase collector. Can be null (the report will be sent to the + * console) + */ public static void gc(CommandSender sender) { long t1 = System.currentTimeMillis(); long alloc1 = Runtime.getRuntime().totalMemory(); @@ -478,7 +517,7 @@ public class PerformanceAnalysisManager implements Listener { Log.info(finalMessage.getLegacyText()); } - public static String displayRound10(double val) { + private static String displayRound10(double val) { long v = (long) Math.ceil(val * 10); return "" + (v / 10f); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java index 7aaf900..1541cde 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java @@ -1,11 +1,10 @@ package fr.pandacube.lib.paper.players; -import fr.pandacube.lib.paper.reflect.util.PrimaryWorlds; +import fr.pandacube.lib.paper.world.PrimaryWorlds; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.NbtIo; -import fr.pandacube.lib.paper.util.PlayerDataWrapper; -import fr.pandacube.lib.paper.util.PlayerDataWrapper.PlayerDataLoadException; +import fr.pandacube.lib.paper.players.PlayerDataWrapper.PlayerDataLoadException; import fr.pandacube.lib.paper.world.WorldUtil; import fr.pandacube.lib.players.standalone.AbstractOffPlayer; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; @@ -116,26 +115,31 @@ public interface PaperOffPlayer extends AbstractOffPlayer { * Player config */ + @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation @Override default String getConfig(String key) throws Exception { return PaperPlayerConfigStorage.get(getUniqueId(), key); } + @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation @Override default String getConfig(String key, String deflt) throws Exception { return PaperPlayerConfigStorage.get(getUniqueId(), key, deflt); } + @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation @Override default void setConfig(String key, String value) throws Exception { PaperPlayerConfigStorage.set(getUniqueId(), key, value); } + @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation @Override default void updateConfig(String key, String deflt, UnaryOperator updater) throws Exception { PaperPlayerConfigStorage.update(getUniqueId(), key, deflt, updater); } + @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation @Override default void unsetConfig(String key) throws Exception { PaperPlayerConfigStorage.unset(getUniqueId(), key); diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java index 438af7b..5acad47 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java @@ -3,7 +3,7 @@ package fr.pandacube.lib.paper.players; import com.destroystokyo.paper.ClientOption; import com.destroystokyo.paper.ClientOption.ChatVisibility; import com.destroystokyo.paper.SkinParts; -import fr.pandacube.lib.paper.players.PlayerNonPersistentConfig.Expiration; +import fr.pandacube.lib.paper.players.PlayerNonPersistentConfig.ExpirationPolicy; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftPlayer; import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; @@ -304,18 +304,39 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer * Player config */ + /** + * Gets the non-persistent value of the provided configuration key of this player. + * @param key the configuration key. + * @return the value of the configuration, or null if the configuration is not set. + */ default String getNonPersistentConfig(String key) { return PlayerNonPersistentConfig.getData(getUniqueId(), key); } + /** + * Gets the non-persistent value of the provided configuration key of this player. + * @param key the configuration key. + * @param deflt the default value if the configuration is not set. + * @return the value of the configuration, or {@code deflt} if the configuration is not set. + */ default String getNonPersistentConfig(String key, String deflt) { return PlayerNonPersistentConfig.getData(getUniqueId(), key); } - default void setNonPersistentConfig(String key, String value, Expiration expiration) { - PlayerNonPersistentConfig.setData(getUniqueId(), key, value, expiration); + /** + * Sets the non-persistent value of the provided configuration key for this player. + * @param key the configuration key to set. + * @param value the new value. + * @param expirationPolicy the expiration policy. + */ + default void setNonPersistentConfig(String key, String value, ExpirationPolicy expirationPolicy) { + PlayerNonPersistentConfig.setData(getUniqueId(), key, value, expirationPolicy); } + /** + * Unsets the non-persistent value of the provided configuration key for this player. + * @param key the configuration key to update. + */ default void unsetNonPersistentConfig(String key) { PlayerNonPersistentConfig.unsetData(getUniqueId(), key); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperPlayerConfigStorage.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperPlayerConfigStorage.java index 3ab74d6..7339811 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperPlayerConfigStorage.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperPlayerConfigStorage.java @@ -16,6 +16,10 @@ import java.util.UUID; import java.util.function.UnaryOperator; import java.util.stream.Collectors; +/** + * Provides rudimentary player data storage using a file in the plugin configuration. + * The file is loaded on the first access, and is auto-saved if needed every 30 seconds. + */ public class PaperPlayerConfigStorage { static final File storageFile = new File(PandaLibPaper.getPlugin().getDataFolder(), "playerdata.yml"); @@ -77,6 +81,8 @@ public class PaperPlayerConfigStorage { private static synchronized void save() { + if (!changed) + return; YamlConfiguration config = new YamlConfiguration(); for (UUID pId : playerSortedData.keySet()) { String pIdStr = pId.toString(); @@ -109,11 +115,17 @@ public class PaperPlayerConfigStorage { } - public static synchronized void set(UUID player, String key, String newValue) { + /** + * Sets the value of the provided configuration key for the player. + * @param player the player. + * @param key the configuration key to set. + * @param value the new value. + */ + public static synchronized void set(UUID player, String key, String value) { initIfNeeded(); ConfigKey cKey = new ConfigKey(player, key); ConfigEntry e = data.get(cKey); - if (e != null && newValue == null) { // delete + if (e != null && value == null) { // delete data.remove(cKey); if (playerSortedData.containsKey(player)) playerSortedData.get(player).remove(e); @@ -121,50 +133,91 @@ public class PaperPlayerConfigStorage { keySortedData.get(key).remove(e); changed = true; } - else if (e == null && newValue != null) { // create - create(player, key, newValue); + else if (e == null && value != null) { // create + create(player, key, value); changed = true; } - else if (e != null && !newValue.equals(e.value)) { // update - e.value = newValue; + else if (e != null && !value.equals(e.value)) { // update + e.value = value; changed = true; } } + /** + * Gets the value of the provided configuration key of the player. + * @param player the player. + * @param key the configuration key. + * @return the value of the configuration, or null if the configuration is not set. + */ public static synchronized String get(UUID player, String key) { initIfNeeded(); ConfigEntry e = data.get(new ConfigKey(player, key)); return e != null ? e.value : null; } - public static String get(UUID p, String k, String deflt) { - String value = get(p, k); + /** + * Gets the value of the provided configuration key of the player. + * @param player the player. + * @param key the configuration key. + * @param deflt the default value if the configuration is not set. + * @return the value of the configuration, or {@code deflt} if the configuration is not set. + */ + public static String get(UUID player, String key, String deflt) { + String value = get(player, key); return value == null ? deflt : value; } - public static synchronized void update(UUID p, String k, String deflt, UnaryOperator updater) { - String oldValue = get(p, k, deflt); - set(p, k, updater.apply(oldValue)); + /** + * Updates the value of the provided configuration key for the player, using the provided updater. + * @param player the player. + * @param key the configuration key to update. + * @param deflt the default value to use if the configuration is not already set. + * @param updater the unary operator to use to update th value. The old value is used as the parameter of the updater, + * and it returns the new value of the configuration. + */ + public static synchronized void update(UUID player, String key, String deflt, UnaryOperator updater) { + String oldValue = get(player, key, deflt); + set(player, key, updater.apply(oldValue)); } - public static void unset(UUID p, String k) { - set(p, k, null); + /** + * Unsets the value of the provided configuration key for the player. + * @param player the player. + * @param key the configuration key to update. + */ + public static void unset(UUID player, String key) { + set(player, key, null); } - - public static LinkedHashSet getAllFromPlayer(UUID p) { + /** + * Gets all the config key-value pairs of the provided player. + * @param player the player. + * @return all the config key-value pairs of the provided player. + */ + public static LinkedHashSet getAllFromPlayer(UUID player) { initIfNeeded(); - return new LinkedHashSet<>(playerSortedData.getOrDefault(p, new LinkedHashSet<>())); + return new LinkedHashSet<>(playerSortedData.getOrDefault(player, new LinkedHashSet<>())); } + /** + * Gets all the config key-value pairs of all players that have the provided key. + * @param key the key. + * @return all the config key-value pairs of all players that have the provided key. + */ public static LinkedHashSet getAllWithKeys(String key) { initIfNeeded(); return new LinkedHashSet<>(keySortedData.getOrDefault(key, new LinkedHashSet<>())); } - public static LinkedHashSet getAllWithKeyValue(String k, String v) { + /** + * Gets all the config key-value pairs of all players that have the provided key AND value. + * @param key the key. + * @param v the value. + * @return all the config key-value pairs of all players that have the provided key AND value. + */ + public static LinkedHashSet getAllWithKeyValue(String key, String v) { initIfNeeded(); - return getAllWithKeys(k).stream() + return getAllWithKeys(key).stream() .filter(c -> c.value.equals(v)) .collect(Collectors.toCollection(LinkedHashSet::new)); } @@ -173,25 +226,46 @@ public class PaperPlayerConfigStorage { private record ConfigKey(UUID playerId, String key) { } + /** + * Class holding the playerId-key-value triplet. + */ public static class ConfigEntry { private final UUID playerId; private final String key; private String value; + /** + * Creates a new {@link ConfigEntry}. + * @param playerId the player id. + * @param key the key. + * @param value the value. + */ private ConfigEntry(UUID playerId, String key, String value) { this.playerId = playerId; this.key = key; this.value = value; } + /** + * Gets the player id. + * @return the player id. + */ public UUID getPlayerId() { return playerId; } + /** + * Gets the config key. + * @return the config key. + */ public String getKey() { return key; } + /** + * Gets the config value. + * @return the config value. + */ public String getValue() { return value; } @@ -208,4 +282,7 @@ public class PaperPlayerConfigStorage { && Objects.equals(key, o.key); } } + + + private PaperPlayerConfigStorage() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/PlayerDataWrapper.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PlayerDataWrapper.java similarity index 52% rename from pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/PlayerDataWrapper.java rename to pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PlayerDataWrapper.java index 10a6910..c72a554 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/PlayerDataWrapper.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PlayerDataWrapper.java @@ -1,39 +1,33 @@ -package fr.pandacube.lib.paper.util; +package fr.pandacube.lib.paper.players; -import com.google.common.base.Preconditions; +import fr.pandacube.lib.paper.inventory.DummyPlayerInventory; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftItemStack; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.ListTag; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.Tag; +import fr.pandacube.lib.paper.util.ExperienceUtil; import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.HumanEntity; import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.Arrays; import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.TreeMap; import java.util.UUID; import java.util.function.IntUnaryOperator; /** - * A wrapper to easily manipulate vanilla player data. + * A wrapper to easily manipulate the player data file. * - * @param data The data as they are stored in the player file. + * @param data The NBT data structure as it is stored in the player file. */ public record PlayerDataWrapper(CompoundTag data) { /** * Creates a new wrapper for the provided player data. - * @param data the data to wrap. + * @param data the NBT data to wrap. */ public PlayerDataWrapper(CompoundTag data) { this.data = data == null ? new CompoundTag() : data; @@ -41,8 +35,9 @@ public record PlayerDataWrapper(CompoundTag data) { /** * Gets a snapshot of the inventory of this player. - * If modified, call the {@link #setInventory(PlayerInventory)} to update the data. - * @return the player inventory + * If the inventory is modified, the {@link #setInventory(PlayerInventory)} method should be called to update the + * data in this wrapper. + * @return the player inventory. */ public PlayerInventory getInventory() { return new DummyPlayerInventory( @@ -67,6 +62,11 @@ public record PlayerDataWrapper(CompoundTag data) { throw new IllegalArgumentException("Unrecognized NBT player inventory slot " + nbtSlot); } + /** + * Sets the player inventory to the content of the provided one. + * The internal data of this wrapper will be updated. + * @param inv the inventory to store in this player data in place of the old one. + */ public void setInventory(PlayerInventory inv) { setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot); setHeldItemSlot(inv.getHeldItemSlot()); @@ -86,10 +86,21 @@ public record PlayerDataWrapper(CompoundTag data) { } + /** + * Gets a snapshot of the enderchest of this player. + * If the enderchest is modified, the {@link #setEnderChest(Inventory)} method should be called to update the + * data in this wrapper. + * @return the player enderchest. + */ public Inventory getEnderChest() { return getBukkitInventory("EnderItems", InventoryType.ENDER_CHEST, IntUnaryOperator.identity()); } + /** + * Sets the player enderchest to the content of the provided one. + * The internal data of this wrapper will be updated. + * @param inv the enderchest content to store in this player data in place of the old enderchest. + */ public void setEnderChest(Inventory inv) { setBukkitInventory("EnderItems", inv, IntUnaryOperator.identity()); } @@ -171,6 +182,10 @@ public record PlayerDataWrapper(CompoundTag data) { } + /** + * Gets the score of the player, as stored in the data with the key {@code Score}. + * @return the value of Score. + */ public int getScore() { if (!data.contains("Score")) return 0; @@ -178,17 +193,29 @@ public record PlayerDataWrapper(CompoundTag data) { } + /** + * Sets the score of the player, as stored in the data with the key {@code Score}. + * @param score the value of Score to set. + */ public void setScore(int score) { data.putInt("Score", score); } + /** + * Gets the total experience of the player, as stored in the data with the key {@code XpTotal}. + * @return the value of XpTotal. + */ public int getTotalExperience() { if (!data.contains("XpTotal")) return 0; return data.getInt("XpTotal"); } + /** + * Sets the total experience of the player, as stored in the data with the key {@code XpTotal}. + * @param xp the value of XpTotal to set. + */ public void setTotalExperience(int xp) { data.putInt("XpTotal", xp); double levelAndExp = ExperienceUtil.getLevelFromExp(xp); @@ -198,178 +225,11 @@ public record PlayerDataWrapper(CompoundTag data) { data.putFloat("XpP", (float) expProgress); } - - private static class DummyPlayerInventory extends InventoryWrapper implements PlayerInventory { - - private int heldItemSlot; - - public DummyPlayerInventory(Inventory base, int heldItemSlot) { - super(base); - this.heldItemSlot = heldItemSlot; - } - - @Override - public @Nullable ItemStack @NotNull [] getStorageContents() { - return Arrays.copyOfRange(getContents(), 0, 36); - } - - @Override - public @Nullable ItemStack @NotNull [] getArmorContents() { - return Arrays.copyOfRange(getContents(), 36, 40); - } - - @Override - public void setArmorContents(@Nullable ItemStack[] items) { - this.setSlots(items, 36, 4); - } - - @Override - public @Nullable ItemStack @NotNull [] getExtraContents() { - return Arrays.copyOfRange(getContents(), 40, getSize()); - } - - @Override - public void setExtraContents(@Nullable ItemStack[] items) { - this.setSlots(items, 40, getSize() - 40); - } - - private void setSlots(ItemStack[] items, int baseSlot, int length) { - if (items == null) { - items = new ItemStack[length]; - } - Preconditions.checkArgument(items.length <= length, "items.length must be < %s", length); - - for (int i = 0; i < length; i++) { - if (i >= items.length) { - this.setItem(baseSlot + i, null); - } else { - this.setItem(baseSlot + i, items[i]); - } - } - } - - @Override - public ItemStack getHelmet() { - return getItem(39); - } - - @Override - public void setHelmet(@Nullable ItemStack helmet) { - setItem(39, helmet); - } - - @Override - public ItemStack getChestplate() { - return getItem(38); - } - - @Override - public void setChestplate(@Nullable ItemStack chestplate) { - setItem(38, chestplate); - } - - @Override - public ItemStack getLeggings() { - return getItem(37); - } - - @Override - public void setLeggings(@Nullable ItemStack leggings) { - setItem(37, leggings); - } - - @Override - public ItemStack getBoots() { - return getItem(36); - } - - @Override - public void setBoots(@Nullable ItemStack boots) { - setItem(36, boots); - } - - @Override - public void setItem(EquipmentSlot slot, ItemStack item) { - Preconditions.checkArgument(slot != null, "slot must not be null"); - - switch (slot) { - case HAND -> this.setItemInMainHand(item); - case OFF_HAND -> this.setItemInOffHand(item); - case FEET -> this.setBoots(item); - case LEGS -> this.setLeggings(item); - case CHEST -> this.setChestplate(item); - case HEAD -> this.setHelmet(item); - default -> throw new IllegalArgumentException("Not implemented. This is a bug"); - } - } - - @Override - public @NotNull ItemStack getItem(@NotNull EquipmentSlot slot) { - return switch (slot) { - case HAND -> this.getItemInMainHand(); - case OFF_HAND -> this.getItemInOffHand(); - case FEET -> Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(Material.AIR)); - case LEGS -> Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(Material.AIR)); - case CHEST -> Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(Material.AIR)); - case HEAD -> Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(Material.AIR)); - case BODY -> new ItemStack(Material.AIR); // for horses/wolves armor - }; - } - - @Override - public @NotNull ItemStack getItemInMainHand() { - return Objects.requireNonNullElse(getItem(heldItemSlot), new ItemStack(Material.AIR)); - } - - @Override - public void setItemInMainHand(@Nullable ItemStack item) { - setItem(heldItemSlot, item); - } - - @Override - public @NotNull ItemStack getItemInOffHand() { - return Objects.requireNonNullElse(getItem(40), new ItemStack(Material.AIR)); - } - - @Override - public void setItemInOffHand(@Nullable ItemStack item) { - setItem(40, item); - } - - @Override - public @NotNull ItemStack getItemInHand() { - return getItemInMainHand(); - } - - @Override - public void setItemInHand(@Nullable ItemStack stack) { - setItemInMainHand(stack); - } - - @Override - public int getHeldItemSlot() { - return heldItemSlot; - } - - @Override - public void setHeldItemSlot(int slot) { - if (slot < 0 || slot > 8) - throw new IllegalArgumentException("Slot is not between 0 and 8 inclusive"); - heldItemSlot = slot; - } - - @Override - public @Nullable HumanEntity getHolder() { - return null; - } - } - - - - - + /** + * Thrown to indicate that an error occurred while loading the data of the player from the file. + */ public static class PlayerDataLoadException extends RuntimeException { - public PlayerDataLoadException(String playerName, UUID playerId, Throwable cause) { + /* package */ PlayerDataLoadException(String playerName, UUID playerId, Throwable cause) { super("Unable to load data of player " + playerName + " (" + playerId + ")", cause); } } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PlayerNonPersistentConfig.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PlayerNonPersistentConfig.java index 086997c..3335ff1 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PlayerNonPersistentConfig.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PlayerNonPersistentConfig.java @@ -12,6 +12,9 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; +/** + * Handles the player related configuration that is not persisted to disk. + */ public class PlayerNonPersistentConfig { private static final Map> data = new HashMap<>(); @@ -22,34 +25,58 @@ public class PlayerNonPersistentConfig { } - public static void setData(UUID playerId, String key, String value, Expiration expiration) { - data.computeIfAbsent(Objects.requireNonNull(playerId, "playerId"), pp -> new HashMap<>()) + /** + * Sets the value of the provided configuration key for the player. + * @param player the player. + * @param key the configuration key to set. + * @param value the new value. + * @param expirationPolicy the expiration policy for this config. If the config key already exists for this player. the expiration will be overridden. + */ + public static void setData(UUID player, String key, String value, ExpirationPolicy expirationPolicy) { + data.computeIfAbsent(Objects.requireNonNull(player, "playerId"), pp -> new HashMap<>()) .put(Objects.requireNonNull(key, "key"), new ConfigEntry(Objects.requireNonNull(value, "value"), - Objects.requireNonNull(expiration, "expiration") + Objects.requireNonNull(expirationPolicy, "expiration") ) ); } - public static void unsetData(UUID playerId, String key) { - data.getOrDefault(Objects.requireNonNull(playerId, "playerId"), new HashMap<>()) + /** + * Unsets the value of the provided configuration key for the player. + * @param player the player. + * @param key the configuration key to update. + */ + public static void unsetData(UUID player, String key) { + data.getOrDefault(Objects.requireNonNull(player, "playerId"), new HashMap<>()) .remove(Objects.requireNonNull(key, "key")); } - public static String getData(UUID playerId, String key) { - Map playerData = data.getOrDefault(Objects.requireNonNull(playerId, "playerId"), new HashMap<>()); + /** + * Gets the value of the provided configuration key of the player. + * @param player the player. + * @param key the configuration key. + * @return the value of the configuration, or {@code deflt} if the configuration is not set. + */ + public static String getData(UUID player, String key) { + Map playerData = data.getOrDefault(Objects.requireNonNull(player, "playerId"), new HashMap<>()); ConfigEntry ce = playerData.get(Objects.requireNonNull(key, "key")); if (ce == null) return null; - if (!ce.expiration.valid(playerId, key)) { + if (!ce.expirationPolicy.valid(player, key)) { playerData.remove(key); return null; } return ce.value; } - public static boolean isDataSet(UUID playerId, String key) { - return getData(playerId, key) != null; + /** + * Tells if the provided config key is set for the player. + * @param player the player. + * @param key the configuration key. + * @return true if the value is set, false otherwise. + */ + public static boolean isDataSet(UUID player, String key) { + return getData(player, key) != null; } @@ -63,34 +90,76 @@ public class PlayerNonPersistentConfig { - private record ConfigEntry(String value, Expiration expiration) { } + private record ConfigEntry(String value, ExpirationPolicy expirationPolicy) { } + /** + * Super class for all expiration policies. + */ + public static abstract class ExpirationPolicy { + /** + * Creates an expiration policy. + */ + public ExpirationPolicy() {} - - public static abstract class Expiration { + /** + * Tests if the associated configuration is still valid (not expired). + * @param player the player. + * @param key the configuration key. + * @return true if the associated configuration is still valid, false otherwise. + */ abstract boolean valid(UUID player, String key); } - public static class ExpiresLogout extends Expiration { + /** + * Expiration policy for a config that expires when the player logs out. + */ + public static class ExpiresLogout extends ExpirationPolicy { + /** + * Creates a logout expiration policy. + */ + public ExpiresLogout() { + super(); + } + + @Override protected boolean valid(UUID player, String key) { return Bukkit.getPlayer(player) != null; // should not be call if player reconnects because it is removed on player quit } } - public static class ExpiresTick extends Expiration { + /** + * Expiration policy for a config that expires after a certain amount of game tick. + */ + public static class ExpiresTick extends ExpirationPolicy { final long expirationTick; + /** + * Creates a delay expiration policy. + * @param expirationDelayTick the number of tick after which the config will expire. If 0, will expire immediately ; 1 to expire on the next tick. + */ public ExpiresTick(long expirationDelayTick) { expirationTick = tick + expirationDelayTick; } + @Override protected boolean valid(UUID player, String key) { return tick < expirationTick; } } - public static class ExpiresServerStop extends Expiration { + /** + * Expiration policy for a config that expires when the server stops. + */ + public static class ExpiresServerStop extends ExpirationPolicy { + /** + * Creates a server stop expiration policy. + */ + public ExpiresServerStop() { + super(); + } + + @Override protected boolean valid(UUID player, String key) { return true; } @@ -103,7 +172,7 @@ public class PlayerNonPersistentConfig { private static class ConfigListeners implements Listener { - public ConfigListeners() { + private ConfigListeners() { Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin()); } @@ -111,7 +180,7 @@ public class PlayerNonPersistentConfig { public void onPlayerQuit(PlayerQuitEvent event) { data.getOrDefault(event.getPlayer().getUniqueId(), new HashMap<>()) .entrySet() - .removeIf(e -> e.getValue().expiration instanceof ExpiresLogout); + .removeIf(e -> e.getValue().expirationPolicy instanceof ExpiresLogout); } @EventHandler @@ -119,4 +188,9 @@ public class PlayerNonPersistentConfig { tick++; } } + + + + + private PlayerNonPersistentConfig() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/OBCReflect.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/OBCReflect.java index 3afa13f..1b0d790 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/OBCReflect.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/OBCReflect.java @@ -23,4 +23,6 @@ public class OBCReflect { return Reflect.ofClass(CRAFTBUKKIT_PACKAGE + "." + obcClass); } + private OBCReflect() { } + } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/PandalibPaperReflect.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/PandalibPaperReflect.java index 1c3a282..62951dd 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/PandalibPaperReflect.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/PandalibPaperReflect.java @@ -226,4 +226,6 @@ public class PandalibPaperReflect { thAcc.throwCaught(); } + + private PandalibPaperReflect() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/util/WorldSaveUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/util/WorldSaveUtil.java index fd80bff..fc0de98 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/util/WorldSaveUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/util/WorldSaveUtil.java @@ -1,21 +1,24 @@ package fr.pandacube.lib.paper.reflect.util; -import org.bukkit.Bukkit; -import org.bukkit.World; - import fr.pandacube.lib.chat.Chat; import fr.pandacube.lib.chat.ChatConfig.PandaTheme; import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftWorld; -import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ChunkMap; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerLevel; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.util.ProgressListener; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; +import org.bukkit.Bukkit; +import org.bukkit.World; +/** + * Provides static methods to save worlds presumably in a better way than Bukkit provides (better flushing, more released RAM). + */ public class WorldSaveUtil { - - private static ChunkMap getChunkMap(World w) { - return ReflectWrapper.wrapTyped(w, CraftWorld.class).getHandle().getChunkSource().chunkMap; - } - + + /** + * Save the provided world using the NMS {@link ServerLevel#save(ProgressListener, boolean, boolean)} method. + * @param w the world to save. + */ public static void nmsSaveFlush(World w) { PerformanceAnalysisManager.getInstance().setAlteredTPSTitle( Chat.text("Sauvegarde map ").color(PandaTheme.CHAT_BROWN_2_SAT).thenData(w.getName()).thenText(" ...") @@ -27,9 +30,14 @@ public class WorldSaveUtil { PerformanceAnalysisManager.getInstance().setAlteredTPSTitle(null); } } - + + /** + * Save all the loaded worlds, using {@link #nmsSaveFlush(World)}. + */ public static void nmsSaveAllFlush() { Bukkit.getWorlds().forEach(WorldSaveUtil::nmsSaveFlush); } + + private WorldSaveUtil() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/scheduler/SchedulerUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/scheduler/SchedulerUtil.java index 6ecf4fb..26cc6a7 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/scheduler/SchedulerUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/scheduler/SchedulerUtil.java @@ -69,6 +69,6 @@ public class SchedulerUtil { } - + private SchedulerUtil() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AutoUpdatedBossBar.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AutoUpdatedBossBar.java index 0f08f68..7ed80d4 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AutoUpdatedBossBar.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/AutoUpdatedBossBar.java @@ -1,9 +1,11 @@ package fr.pandacube.lib.paper.util; -import java.util.Timer; -import java.util.TimerTask; -import java.util.function.Predicate; - +import fr.pandacube.lib.chat.Chat; +import fr.pandacube.lib.paper.PandaLibPaper; +import fr.pandacube.lib.util.log.Log; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.bossbar.BossBar.Color; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -14,26 +16,37 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; -import fr.pandacube.lib.chat.Chat; -import fr.pandacube.lib.util.log.Log; -import fr.pandacube.lib.paper.PandaLibPaper; -import net.kyori.adventure.bossbar.BossBar; -import net.kyori.adventure.bossbar.BossBar.Color; -import net.kyori.adventure.text.Component; +import java.util.Timer; +import java.util.TimerTask; +import java.util.function.Predicate; +/** + * A {@link BossBar} capable of automatically updating itself by registering a {@link BukkitTask} or using a {@link Timer}. + */ public class AutoUpdatedBossBar implements Listener { - + + /** + * The boss bar itself. + */ public final BossBar bar; + /** + * the function executed to update the boss bar. + */ public final BarUpdater updater; - + private Timer timer = null; private BukkitTask bukkitTask = null; private boolean scheduled = false; - private boolean followPlayerList = false; + private LoginLogoutListener followPlayerList = null; private Predicate playerCondition = null; - + + /** + * Wraps the provided boss bar into a new instance of {@link AutoUpdatedBossBar}. + * @param bar the boss bar itself. + * @param updater the updater that will be run to update the boss bar. + */ public AutoUpdatedBossBar(BossBar bar, BarUpdater updater) { this.bar = bar; this.updater = updater; @@ -66,10 +79,9 @@ public class AutoUpdatedBossBar implements Listener { } /** - * Schedule the update of this boss bar with synchronisation with the main Thread of the - * current Minecraft server (follow the tick count progress). + * Schedule the update of this boss bar with synchronisation with the ticking of this Minecraft server. * The underlying method called is {@link BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)}. - * The updater is executed by the Server Thread. + * The updater is executed by the main Server Thread. * @param tickDelay number of server tick before running the first update of this boss bar * @param tickPeriod number of server tick between each call of the updater */ @@ -90,55 +102,65 @@ public class AutoUpdatedBossBar implements Listener { }, tickDelay, tickPeriod); } - - - + + + /** + * Auto-update the boss bar on player join and quit. + * @param condition an additional test that if it's true on player join/quit, will actually run the update. Can be null. + */ public synchronized void followLoginLogout(Predicate condition) { playerCondition = condition; - if (followPlayerList) + if (followPlayerList != null) return; - followPlayerList = true; - BukkitEvent.register(this); - Bukkit.getServer().getOnlinePlayers().forEach(p -> onPlayerJoin(new PlayerJoinEvent(p, Component.text("")))); + followPlayerList = new LoginLogoutListener(); + BukkitEvent.register(followPlayerList); + Bukkit.getServer().getOnlinePlayers().forEach(p -> followPlayerList.onPlayerJoin(new PlayerJoinEvent(p, Component.text("")))); } - + + /** + * Cancel the auto-update on player join and quit. + */ public synchronized void unfollowPlayerList() { - if (!followPlayerList) + if (followPlayerList == null) return; - followPlayerList = false; playerCondition = null; - PlayerJoinEvent.getHandlerList().unregister(this); - PlayerQuitEvent.getHandlerList().unregister(this); + BukkitEvent.unregister(followPlayerList); + followPlayerList = null; } - @EventHandler(priority=EventPriority.MONITOR) - public synchronized void onPlayerJoin(PlayerJoinEvent event) { - if (!followPlayerList) - return; - if (playerCondition != null && !playerCondition.test(event.getPlayer())) - return; - synchronized (bar) { - event.getPlayer().showBossBar(bar); + private class LoginLogoutListener implements Listener { + + @EventHandler(priority=EventPriority.MONITOR) + public void onPlayerJoin(PlayerJoinEvent event) { + if (playerCondition != null && !playerCondition.test(event.getPlayer())) + return; + synchronized (bar) { + event.getPlayer().showBossBar(bar); + } } - } - - @EventHandler(priority=EventPriority.HIGH) - public synchronized void onPlayerQuit(PlayerQuitEvent event) { - if (!followPlayerList) - return; - synchronized (bar) { - event.getPlayer().hideBossBar(bar); + + @EventHandler(priority=EventPriority.HIGH) + public void onPlayerQuit(PlayerQuitEvent event) { + synchronized (bar) { + event.getPlayer().hideBossBar(bar); + } } } + + /** + * Hides this boss bar from all players. + */ public void removeAll() { synchronized (bar) { for (Player p : Bukkit.getOnlinePlayers()) p.hideBossBar(bar); } } - - + + /** + * Cancel any auto-updating of this boss bar. + */ public synchronized void cancel() { if (!scheduled) return; @@ -151,13 +173,19 @@ public class AutoUpdatedBossBar implements Listener { bukkitTask.cancel(); bukkitTask = null; } - + unfollowPlayerList(); } - - - + + + /** + * Functional interface taking an instance of {@link AutoUpdatedBossBar} to update it. Returns nothing. + */ @FunctionalInterface public interface BarUpdater { + /** + * Updates the boss bar. + * @param bar the auto-updated boss bar instance. + */ void update(AutoUpdatedBossBar bar); } @@ -165,6 +193,7 @@ public class AutoUpdatedBossBar implements Listener { /** * Utility method to update the title of the boss bar without unnecessary packet. + * @param title the new title. */ public void setTitle(Chat title) { synchronized (bar) { @@ -174,6 +203,7 @@ public class AutoUpdatedBossBar implements Listener { /** * Utility method to update the color of the boss bar without unnecessary packet. + * @param color the new color. */ public void setColor(Color color) { synchronized (bar) { @@ -183,6 +213,7 @@ public class AutoUpdatedBossBar implements Listener { /** * Utility method to update the progress of the boss bar without unnecessary packet. + * @param progress the new progress value. */ public void setProgress(double progress) { synchronized (bar) { diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/BukkitChatColorUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/BukkitChatColorUtil.java deleted file mode 100644 index 441952d..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/BukkitChatColorUtil.java +++ /dev/null @@ -1,72 +0,0 @@ -package fr.pandacube.lib.paper.util; - -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; -import org.bukkit.ChatColor; -import org.bukkit.DyeColor; - -/** - * Utility class around chat coloring. - */ -public class BukkitChatColorUtil { - - - - - /** - * Returns the {@link TextColor} that is visually the closest from the provided {@link DyeColor} when used on a sign. - * @param dye the provided dye color - * @return the closest chat color from {@code dye} - */ - public static TextColor fromDyeToSignColor(DyeColor dye) { - // following code invalid due to text color on sign does not use rgb value of dye color. - //org.bukkit.Color col = dye.getColor(); - //return ChatColor.of(new Color(col.asRGB())); - - // the color values are extracted from IG screenshot of dyed text signs. - return switch (dye) { - case BLACK -> TextColor.fromHexString("#000000"); - case RED -> TextColor.fromHexString("#650000"); - case GREEN -> TextColor.fromHexString("#006500"); - case BROWN -> TextColor.fromHexString("#361B07"); - case BLUE -> TextColor.fromHexString("#000065"); - case PURPLE -> TextColor.fromHexString("#3F0C5F"); - case CYAN -> TextColor.fromHexString("#006565"); - case LIGHT_GRAY -> TextColor.fromHexString("#535353"); - case GRAY -> TextColor.fromHexString("#323232"); - case PINK -> TextColor.fromHexString("#652947"); - case LIME -> TextColor.fromHexString("#4B6500"); - case YELLOW -> TextColor.fromHexString("#656500"); - case LIGHT_BLUE -> TextColor.fromHexString("#3C4B51"); - case MAGENTA -> TextColor.fromHexString("#650065"); - case ORANGE -> TextColor.fromHexString("#65280C"); - case WHITE -> TextColor.fromHexString("#656565"); - }; - } - - - @SuppressWarnings("deprecation") - public static ChatColor toBukkit(net.md_5.bungee.api.ChatColor color) { - return ChatColor.valueOf(color.getName().toUpperCase()); - } - - @SuppressWarnings("deprecation") - public static ChatColor toBukkit(TextColor color) { - return toBukkit(NamedTextColor.nearestTo(color)); - } - - @SuppressWarnings("deprecation") - public static ChatColor toBukkit(NamedTextColor color) { - return ChatColor.valueOf(color.toString().toUpperCase()); - } - - @SuppressWarnings("deprecation") - public static NamedTextColor toAdventure(ChatColor color) { - return NamedTextColor.NAMES.value(color.name().toLowerCase()); - } - - public static NamedTextColor toAdventure(net.md_5.bungee.api.ChatColor color) { - return NamedTextColor.NAMES.value(color.getName()); - } - -} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java index 74e3953..e28f207 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/BukkitEvent.java @@ -16,45 +16,102 @@ import org.bukkit.scheduler.BukkitTask; import fr.pandacube.lib.paper.PandaLibPaper; import fr.pandacube.lib.reflect.Reflect; +/** + * Utility class to more concisely handle Bukkit Events + */ public class BukkitEvent { - + + /** + * Register a single event executor. + *

+ * The priority if the event executor is {@link EventPriority#NORMAL}. + * Does not ignore cancelled events. + * @param eventClass the class of the event to listen to. + * @param eventExecutor the executor. + * @return the {@link Listener} instance, that is in our case the provided executor. + * @param the event type. + */ public static Listener register(Class eventClass, EventListener eventExecutor) { return register(eventClass, eventExecutor, EventPriority.NORMAL, false); } - + + /** + * Register a single event executor. + *

+ * Does not ignore cancelled events. + * @param eventClass the class of the event to listen to. + * @param eventExecutor the executor. + * @param priority the event priority. + * @return the {@link Listener} instance, that is in our case the provided executor. + * @param the event type. + */ public static Listener register(Class eventClass, EventListener eventExecutor, EventPriority priority) { return register(eventClass, eventExecutor, priority, false); } - + + /** + * Register a single event executor. + *

+ * The priority if the event executor is {@link EventPriority#NORMAL}. + * @param eventClass the class of the event to listen to. + * @param eventExecutor the executor. + * @param ignoreCancelled whether to pass cancelled events or not. + * @return the {@link Listener} instance, that is in our case the provided executor. + * @param the event type. + */ public static Listener register(Class eventClass, EventListener eventExecutor, boolean ignoreCancelled) { return register(eventClass, eventExecutor, EventPriority.NORMAL, ignoreCancelled); } - + + /** + * Register a single event executor. + * @param eventClass the class of the event to listen to. + * @param eventExecutor the executor. + * @param priority the event priority. + * @param ignoreCancelled whether to pass cancelled events or not. + * @return the {@link Listener} instance, that is in our case the provided executor. + * @param the event type. + */ public static Listener register(Class eventClass, EventListener eventExecutor, EventPriority priority, boolean ignoreCancelled) { Bukkit.getPluginManager().registerEvent(eventClass, eventExecutor, priority, eventExecutor, PandaLibPaper.getPlugin(), ignoreCancelled); return eventExecutor; } - + + /** + * Register a listener. + * This is equivalent to calling {@code Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin());} + * @param l the listener. + */ public static void register(Listener l) { Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin()); } - - - + + + /** + * Unregister a listener + * @param listener the listener to unregister. + */ public static void unregister(Listener listener) { HandlerList.unregisterAll(listener); } - + /** + * Lists all existing subclasses of {@link Event}. + * @return a list of all existing subclasses of {@link Event}. + */ public static List> getAllEventClasses() { List> classes = Reflect.ofClass(Event.class).getAllSubclasses(false); classes.removeIf(e -> getHandlerList(e) == null); return classes; } - - - + + + /** + * Gets the handlerList of the provided Event class. + * @param type the event class. + * @return the handlerList. + */ // method retrieved from OB.plugin.SimplePluginManager#getEventListeners public static HandlerList getHandlerList(Class type) { try { @@ -81,11 +138,18 @@ public class BukkitEvent { return null; } } - - - + + + /** + * An single executor event listener. Used for the {@link #register(Class, EventListener)} static method and the other variants. + * @param the event type. + */ public interface EventListener extends Listener, EventExecutor { - + + /** + * The event handler. + * @param event the event. + */ void onEvent(E event); @SuppressWarnings("unchecked") @@ -99,12 +163,17 @@ public class BukkitEvent { * Abstract implementation of {@link EventListener} that ensure as good as it can, * that it is the last listener called to handle the event. * - * @param the type of the event + * @param the type of the event. */ public static abstract class EnforcedLastListener implements EventListener { private final Class eventClass; private final boolean ignoreCancelled; - + + /** + * Creates a new {@link EnforcedLastListener}. + * @param eventClass the event to listen. + * @param ignoreCancelled whether to pass cancelled events or not. + */ public EnforcedLastListener(Class eventClass, boolean ignoreCancelled) { this.eventClass = eventClass; this.ignoreCancelled = ignoreCancelled; @@ -143,6 +212,9 @@ public class BukkitEvent { } } } + + + private BukkitEvent() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ColorUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ColorUtil.java index 18211dc..7970114 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ColorUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ColorUtil.java @@ -3,8 +3,13 @@ package fr.pandacube.lib.paper.util; import java.util.ArrayList; import java.util.List; +import net.kyori.adventure.text.format.TextColor; import org.bukkit.Color; +import org.bukkit.DyeColor; +/** + * Color related utility class. + */ public class ColorUtil { /* @@ -13,9 +18,10 @@ public class ColorUtil { private static final List rainbowColors = new ArrayList<>(); /** - * Get a rainbow color + * Gets a rainbow color. * @param variation from 0 (include) to 1 (exclude). * 0 is red, 1/6 is yellow, 2/6 is green, 3/6 is cyan, 4/6 is blue, 5/6 is magenta + * @return the computed rainbow color. */ public static Color getRainbowColor(double variation) { synchronized (rainbowColors) { @@ -34,16 +40,42 @@ public class ColorUtil { return rainbowColors.get((int)(variation * rainbowColors.size())); } + + + + /** + * Returns the {@link TextColor} that is visually the closest from the provided {@link DyeColor} when used on a sign. + * @param dye the provided dye color + * @return the closest chat color from {@code dye} + */ + public static TextColor getTextColorOfDyedSign(DyeColor dye) { + // following code invalid due to text color on sign does not use rgb value of dye color. + //org.bukkit.Color col = dye.getColor(); + //return ChatColor.of(new Color(col.asRGB())); + + // the color values are extracted from IG screenshot of dyed text signs. + return switch (dye) { + case BLACK -> TextColor.fromHexString("#000000"); + case RED -> TextColor.fromHexString("#650000"); + case GREEN -> TextColor.fromHexString("#006500"); + case BROWN -> TextColor.fromHexString("#361B07"); + case BLUE -> TextColor.fromHexString("#000065"); + case PURPLE -> TextColor.fromHexString("#3F0C5F"); + case CYAN -> TextColor.fromHexString("#006565"); + case LIGHT_GRAY -> TextColor.fromHexString("#535353"); + case GRAY -> TextColor.fromHexString("#323232"); + case PINK -> TextColor.fromHexString("#652947"); + case LIME -> TextColor.fromHexString("#4B6500"); + case YELLOW -> TextColor.fromHexString("#656500"); + case LIGHT_BLUE -> TextColor.fromHexString("#3C4B51"); + case MAGENTA -> TextColor.fromHexString("#650065"); + case ORANGE -> TextColor.fromHexString("#65280C"); + case WHITE -> TextColor.fromHexString("#656565"); + }; + } + + - - - - - - - - - - + private ColorUtil() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ExperienceUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ExperienceUtil.java index 1a161cc..964e4af 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ExperienceUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ExperienceUtil.java @@ -54,12 +54,12 @@ public class ExperienceUtil { /** * @see Experience (#leveling up) - Minecraft Wiki - * - * "The formulas for figuring out how many experience orbs you need to + *

+ * The formulas for figuring out how many experience orbs you need to * get to the next level are as follows: * Experience Required = 2[Current Level] + 7 (at levels 0-15) * 5[Current Level] - 38 (at levels 16-30) - * 9[Current Level] - 158 (at level 31+)" + * 9[Current Level] - 158 (at level 31+) */ private static int getExpToNext(int level) { if (level > 30) return 9 * level - 158; @@ -89,4 +89,7 @@ public class ExperienceUtil { player.setExp((float) (levelAndExp - level)); } + + private ExperienceUtil() {} + } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java deleted file mode 100644 index 3c5acde..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/GameWorldUtils.java +++ /dev/null @@ -1,116 +0,0 @@ -package fr.pandacube.lib.paper.util; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.function.Consumer; - -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.World.Environment; -import org.bukkit.WorldCreator; -import org.bukkit.entity.Player; -import org.bukkit.event.Listener; - -import fr.pandacube.lib.util.BiMap; -import fr.pandacube.lib.util.FileUtils; -import fr.pandacube.lib.util.log.Log; -import fr.pandacube.lib.util.RandomUtil; - -public class GameWorldUtils implements Listener { - - private static final BiMap gameWorld = new BiMap<>(); - - - public static World getOrLoadGameWorld(String world, Consumer operationOnLoad) throws IOException { - if (gameWorld.containsKey(world)) { - return gameWorld.get(world); - } - try { - return loadGameWorld(world, operationOnLoad); - } catch (IllegalStateException e) { - Log.severe(e); - return null; - } - } - - - - - public static World getGameWorldIfLoaded(String world) { - if (gameWorld.containsKey(world)) { - return gameWorld.get(world); - } - return null; - } - - - - - public static boolean unloadGameWorld(String world) { - if (gameWorld.containsKey(world)) { - World rem = gameWorld.remove(world); - String copiedName = rem.getName(); - boolean ret = Bukkit.unloadWorld(rem, false); - if (ret) - FileUtils.delete(new File(Bukkit.getWorldContainer(), copiedName)); - else - Log.warning("Unable to unload game world " + copiedName + " for some reason."); - return ret; - } - return true; - } - - - - public static void unloadUnusedGameWorlds() { - for (String world : new ArrayList<>(gameWorld.keySet())) { - World rem = gameWorld.get(world); - if (rem.getPlayers().stream().noneMatch(Player::isOnline)) { - unloadGameWorld(world); - } - } - } - - - - - public static boolean isGameWorldLoaded(String world) { - return gameWorld.containsKey(world); - } - - - - private static World loadGameWorld(String world, Consumer operationOnLoad) throws IOException { - if (gameWorld.containsKey(world)) - throw new IllegalStateException("GameWorld '"+world+"' is already loaded."); - - if (!new File(Bukkit.getWorldContainer(), world).isDirectory()) - throw new IllegalStateException("GameWorld '"+world+"' does not exist"); - - String copiedName = world + "_gen" + RandomUtil.rand.nextInt(100000, 999999); - - File srcDir = new File(Bukkit.getWorldContainer(), world); - File destDir = new File(Bukkit.getWorldContainer(), copiedName); - FileUtils.delete(destDir); - FileUtils.copy(srcDir, destDir); - new File(destDir, "session.lock").delete(); - new File(destDir, "uid.dat").delete(); - - World w = Bukkit.createWorld(new WorldCreator(copiedName).environment(Environment.NORMAL)); - if (w == null) { - throw new RuntimeException("Unable to create the world " + copiedName + ": Bukkit.createWorld(...) returned null value."); - } - w.setAutoSave(false); - gameWorld.put(world, w); - if (Bukkit.getPluginManager().getPlugin("Multiverse-Core") != null) - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "mvm set hidden true "+copiedName); - operationOnLoad.accept(w); - return w; - } - - - - - -} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/LocationUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/LocationUtil.java index 40cdcec..6913ac2 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/LocationUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/LocationUtil.java @@ -1,12 +1,7 @@ package fr.pandacube.lib.paper.util; -import java.util.EnumSet; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Predicate; - +import fr.pandacube.lib.paper.PandaLibPaper; +import fr.pandacube.lib.util.RandomUtil; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -17,20 +12,35 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.scheduler.BukkitTask; -import fr.pandacube.lib.util.RandomUtil; -import fr.pandacube.lib.paper.PandaLibPaper; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; +/** + * Utility class related to {@link Location}. + */ public class LocationUtil { - + + /** + * Gets a concise {@link String} representation of a {@link Location}. + *

+ * The format is {@code (worldName, 12, 45, -1304)}. The coordinates are those of the containing block (the values + * are cast to int). + * @param loc the location. + * @return a short string representation of the location. + */ public static String conciseToString(Location loc) { String world = loc.getWorld() == null ? "null" : loc.getWorld().getName(); return "(" + world + ", " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")"; } /** - * Return a random secure location in the provided world, inside the current - * WorldBorder. Will be on the surface, for non-nether world, or below the roof of the nether world - * @param w the world in which to pick a location - * @param extraSecureCheck provides extra checks to determine location security + * Return a random secure location in the provided world, inside the current WorldBorder of the world. + * Will be on the surface, for non-nether world, or below the roof of the nether world. + * @param w the world in which to pick a location. + * @param extraSecureCheck provides extra checks to determine location security. + * @return a future that will provide a random secure location. */ public static CompletableFuture getRandomSecureLocation(World w, Predicate extraSecureCheck) { @@ -48,6 +58,17 @@ public class LocationUtil { private static final int maxTryBeforeCancelRandomLocation = 75; + + /** + * Return a random secure location in the provided world, inside the bounding box defined by min and max. + * Will be on the surface (the height limits of the bounding box are ignored), for non-nether world, or below the + * roof of the nether world. + * @param w the world in which to pick a location. + * @param min the min of the bounding box. + * @param max the max of the bounding box. + * @param extraSecureCheck provides extra checks to determine location security. + * @return a future that will provide a random secure location. + */ public static CompletableFuture getRandomSecureLocation(World w, Location min, Location max, Predicate extraSecureCheck) { CompletableFuture future = new CompletableFuture<>(); @@ -94,12 +115,12 @@ public class LocationUtil { /** - * - * @param l the source location - * @return a secure location with the same X and Z coordinate as the + * Try to get a secure location with the same X and Z coordinate as the * provided location, but Y modified to ensure security for player * who will be teleported to this location. * May return null if it is impossible to find a secure location. + * @param l the source location + * @return a secure location, or null if not found around the provided location. */ public static Location getSecureLocationOrNull(Location l) { l = l.clone(); @@ -112,7 +133,13 @@ public class LocationUtil { return currPosSafe(b) ? b.getLocation().add(0.5, 0, 0.5) : null; } - + + /** + * Tells if the provided block is a safe block to spawn/be teleported in. + * More specifically, this block and its block above is air, and the block below is a non-lethal solid block. + * @param b the block to test. + * @return true if the provided block is a safe block to spawn/be teleported in, false otherwise. + */ public static boolean currPosSafe(Block b) { return b.getY() >= b.getWorld().getMinHeight() + 1 && b.getY() <= b.getWorld().getMaxHeight() && isSecureFloor(b.getRelative(BlockFace.DOWN)) @@ -120,10 +147,10 @@ public class LocationUtil { && isAir(b.getRelative(BlockFace.UP)); } - public static boolean isAir(Block b) { return b.getType() == Material.AIR; } - public static boolean isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); } + private static boolean isAir(Block b) { return b.getType() == Material.AIR; } + private static boolean isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); } - public static final Set dangerousBlocks = EnumSet.of( + private static final Set dangerousBlocks = Set.of( Material.LAVA, Material.WATER, Material.COBWEB, @@ -137,21 +164,6 @@ public class LocationUtil { Material.NETHER_PORTAL, Material.END_GATEWAY ); - - - - - - /** - * Check if the {@link Location} l is inside the cuboid formed by the 2 others - * Locations min and max. - * @return true if l is inside the cuboid min-max - */ - public static boolean isIn(Location l, Location min, Location max) { - return (l.getWorld().equals(min.getWorld()) && l.getWorld().equals(max.getWorld()) && l.getX() >= min.getX() - && l.getX() <= max.getX() && l.getY() >= min.getY() && l.getY() <= max.getY() && l.getZ() >= min.getZ() - && l.getZ() <= max.getZ()); - } @@ -160,14 +172,18 @@ public class LocationUtil { /** * Return a new location based on the linear interpolation between p0 and p1, according to the value c. - * @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. Other finite numbers are allowed, but the returned location won't be part of the {@code [p0;p1]} segment. + * @param p0 the first location. + * @param p1 the second location. + * @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. All finite numbers are allowed. * @return The location, linearly interpolated between p0 and p1 with the value c. The yaw and pitch in the returned location are those of p0. * @throws IllegalArgumentException if the provided locations are not in the same world. */ public static Location lerp(Location p0, Location p1, float c) { return p0.clone().add(p1.clone().subtract(p0).multiply(c)); } - + + + private LocationUtil() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/MaterialUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/MaterialUtil.java index 8b568ec..b1ea60c 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/MaterialUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/MaterialUtil.java @@ -1,13 +1,29 @@ package fr.pandacube.lib.paper.util; import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.HangingSign; import org.bukkit.block.data.type.Sign; import org.bukkit.block.data.type.WallSign; +import java.util.Set; + +/** + * Utility class around {@link Material}. + */ public class MaterialUtil { - + + private static final Set> signBlockDataTypes = Set.of(WallSign.class, Sign.class, HangingSign.class); + + /** + * Tells if the provided {@link Material} is a sign, a wall sign or a hanging sign. + * @param m the material. + * @return true if the material is a kind of sign. + */ public static boolean isSign(Material m) { - return WallSign.class.equals(m.data) || Sign.class.equals(m.data); + return signBlockDataTypes.contains(m.data); } + private MaterialUtil() {} + } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ScoreboardUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ScoreboardUtil.java index 6d6e660..153e2df 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ScoreboardUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/ScoreboardUtil.java @@ -140,6 +140,8 @@ public class ScoreboardUtil { updateScoreboardSidebar(scBrd, title.get(), cmpLines); } - + + + private ScoreboardUtil() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java index 2bd807b..7ee32af 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java @@ -24,40 +24,73 @@ import fr.pandacube.lib.chat.Chat; */ public enum Skull { + /** Standard skull of player MHF_ArrowLeft. */ ARROW_LEFT("MHF_ArrowLeft"), + /** Standard skull of player MHF_ArrowRight. */ ARROW_RIGHT("MHF_ArrowRight"), + /** Standard skull of player MHF_ArrowUp. */ ARROW_UP("MHF_ArrowUp"), + /** Standard skull of player MHF_ArrowDown. */ ARROW_DOWN("MHF_ArrowDown"), + /** Standard skull of player MHF_Question. */ QUESTION("MHF_Question"), + /** Standard skull of player MHF_Exclamation. */ EXCLAMATION("MHF_Exclamation"), + /** Standard skull of player FHG_Cam. */ CAMERA("FHG_Cam"), + /** Standard skull of player MHF_PigZombie. */ ZOMBIE_PIGMAN("MHF_PigZombie"), + /** Standard skull of player MHF_Pig. */ PIG("MHF_Pig"), + /** Standard skull of player MHF_Sheep. */ SHEEP("MHF_Sheep"), + /** Standard skull of player MHF_Blaze. */ BLAZE("MHF_Blaze"), + /** Standard skull of player MHF_Chicken. */ CHICKEN("MHF_Chicken"), + /** Standard skull of player MHF_Cow. */ COW("MHF_Cow"), + /** Standard skull of player MHF_Slime. */ SLIME("MHF_Slime"), + /** Standard skull of player MHF_Spider. */ SPIDER("MHF_Spider"), + /** Standard skull of player MHF_Squid. */ SQUID("MHF_Squid"), + /** Standard skull of player MHF_Villager. */ VILLAGER("MHF_Villager"), + /** Standard skull of player MHF_Ocelot. */ OCELOT("MHF_Ocelot"), + /** Standard skull of player MHF_Herobrine. */ HEROBRINE("MHF_Herobrine"), + /** Standard skull of player MHF_LavaSlime. */ LAVA_SLIME("MHF_LavaSlime"), + /** Standard skull of player MHF_MushroomCow. */ MOOSHROOM("MHF_MushroomCow"), + /** Standard skull of player MHF_Golem. */ GOLEM("MHF_Golem"), + /** Standard skull of player MHF_Ghast. */ GHAST("MHF_Ghast"), + /** Standard skull of player MHF_Enderman. */ ENDERMAN("MHF_Enderman"), + /** Standard skull of player MHF_CaveSpider. */ CAVE_SPIDER("MHF_CaveSpider"), + /** Standard skull of player MHF_Cactus. */ CACTUS("MHF_Cactus"), + /** Standard skull of player MHF_Cake. */ CAKE("MHF_Cake"), + /** Standard skull of player MHF_Chest. */ CHEST("MHF_Chest"), + /** Standard skull of player MHF_Melon. */ MELON("MHF_Melon"), + /** Standard skull of player MHF_OakLog. */ LOG("MHF_OakLog"), + /** Standard skull of player MHF_Pumpkin. */ PUMPKIN("MHF_Pumpkin"), + /** Standard skull of player MHF_TNT. */ TNT("MHF_TNT"), + /** Standard skull of player MHF_TNT2. */ DYNAMITE("MHF_TNT2"); private final String name; @@ -76,7 +109,8 @@ public enum Skull { } /** * Return the item based on this Skull enum, with the provided display name and lore. - * + * @param displayName the display name to add to the returned skull. + * @param lore the lore to add to the returned skull. * @return item stack */ public ItemStack get(Chat displayName, List lore) { @@ -89,6 +123,8 @@ public enum Skull { * Return a skull of a player based on their name. * * @param name player's name + * @param displayName the display name to add to the returned skull. + * @param lore the lore to add to the returned skull. * @return item stack */ public static ItemStack getFromPlayerName(String name, Chat displayName, List lore) { @@ -120,8 +156,7 @@ public enum Skull { /** * Return a skull that has a custom texture specified by url. - * - * @param url skin url + * @param url skin url. * @return item stack */ public static ItemStack getFromSkinURL(String url) { @@ -131,11 +166,13 @@ public enum Skull { /** * Return a skull that has a custom texture specified by url. * - * @param url the skin full url + * @param url the skin full url. + * @param displayName the display name to add to the returned skull. + * @param lore the lore to add to the returned skull. * @return item stack */ - public static ItemStack getFromSkinURL(String url, Chat name, List lore) { - return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", url).getBytes()), name, lore); + public static ItemStack getFromSkinURL(String url, Chat displayName, List lore) { + return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", url).getBytes()), displayName, lore); } @@ -148,7 +185,7 @@ public enum Skull { /** * Return a skull that has a custom texture specified by a base64 String. * - * @param str the base64 string from game profile information + * @param str the base64 string from game profile information. * @return item stack */ public static ItemStack getFromBase64String(String str) { @@ -159,7 +196,9 @@ public enum Skull { /** * Return a skull that has a custom texture specified by a base64 String. * - * @param str the base64 string from game profile information + * @param str the base64 string from game profile information. + * @param displayName the display name to add to the returned skull. + * @param lore the lore to add to the returned skull. * @return item stack */ public static ItemStack getFromBase64String(String str, Chat displayName, List lore) { diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/util/PrimaryWorlds.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/PrimaryWorlds.java similarity index 94% rename from pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/util/PrimaryWorlds.java rename to pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/PrimaryWorlds.java index 978e886..22fb2f6 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/util/PrimaryWorlds.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/PrimaryWorlds.java @@ -1,4 +1,4 @@ -package fr.pandacube.lib.paper.reflect.util; +package fr.pandacube.lib.paper.world; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; @@ -36,5 +36,5 @@ public class PrimaryWorlds { } - + private PrimaryWorlds() {} } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/TemplatedWorldHandler.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/TemplatedWorldHandler.java new file mode 100644 index 0000000..1451dc2 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/TemplatedWorldHandler.java @@ -0,0 +1,145 @@ +package fr.pandacube.lib.paper.world; + +import fr.pandacube.lib.util.FileUtils; +import fr.pandacube.lib.util.RandomUtil; +import fr.pandacube.lib.util.log.Log; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.WorldCreator; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Handles worlds that are loaded temporarily and based on a template. Useful for mini-game servers. + */ +public class TemplatedWorldHandler implements Listener { + + private static final Map loadedWorlds = new HashMap<>(); + + + /** + * Gets a world based on the provided template world. + *

+ * If a world based on the template is already loaded, it will be returned. + * If there is no world loaded based on the template, the template world will be copied with a temporary name, and + * loaded like a regular world with {@link Bukkit#createWorld(WorldCreator)}. + * Only one copy per template can be loaded per server instance. + * @param templateWorld the template name of the world, that is the name of the original world's folder. + * @param operationOnLoad an optional consumer executed if a world is loaded (it is ignored if a copy of the template is already loaded). + * @return a World instance based on a copy of the provided world. + * @throws IOException if an error occurs while loading a world. + */ + public static World getOrLoadWorld(String templateWorld, Consumer operationOnLoad) throws IOException { + if (loadedWorlds.containsKey(templateWorld)) { + return loadedWorlds.get(templateWorld); + } + try { + return loadGameWorld(templateWorld, operationOnLoad); + } catch (IllegalStateException e) { + Log.severe(e); + return null; + } + } + + + /** + * Gets the already-loaded world based on the provided template world. + * @param templateWorld the template name of the world, that is the name of the original world's folder. + * @return a World instance based on a copy of the provided world, or null if there is none loaded yet. + */ + public static World getWorldIfLoaded(String templateWorld) { + if (loadedWorlds.containsKey(templateWorld)) { + return loadedWorlds.get(templateWorld); + } + return null; + } + + + /** + * Unload the world based on the provided template world. + * Do nothing if there is no loaded wold based on the provided template. + * After unloading using {@link Bukkit#unloadWorld(World, boolean)}, the copy of the template is deleted. + * @param templateWorld the template name of the world, that is the name of the original world's folder. + * @return true if the world was unloaded successfully (or there were no world to unload), false if the unloading + * failed ({@link Bukkit#unloadWorld(World, boolean)} returned false). + */ + public static boolean unloadWorld(String templateWorld) { + if (loadedWorlds.containsKey(templateWorld)) { + World rem = loadedWorlds.remove(templateWorld); + String copiedName = rem.getName(); + boolean ret = Bukkit.unloadWorld(rem, false); + if (ret) + FileUtils.delete(new File(Bukkit.getWorldContainer(), copiedName)); + else + Log.warning("Unable to unload game world " + copiedName + " for some reason."); + return ret; + } + return true; + } + + + /** + * Unload all the templated worlds, using {@link #unloadWorld(String)}. + */ + public static void unloadUnusedWorlds() { + for (String world : new ArrayList<>(loadedWorlds.keySet())) { + World rem = loadedWorlds.get(world); + if (rem.getPlayers().stream().noneMatch(Player::isOnline)) { + unloadWorld(world); + } + } + } + + + /** + * Tells if the provided template world has a currently loaded copy. + * @param templateWorld the template name of the world, that is the name of the original world's folder. + * @return true if the world is loaded. + */ + public static boolean isWorldLoaded(String templateWorld) { + return loadedWorlds.containsKey(templateWorld); + } + + + + private static World loadGameWorld(String world, Consumer operationOnLoad) throws IOException { + if (loadedWorlds.containsKey(world)) + throw new IllegalStateException("GameWorld '"+world+"' is already loaded."); + + if (!new File(Bukkit.getWorldContainer(), world).isDirectory()) + throw new IllegalStateException("GameWorld '"+world+"' does not exist"); + + String copiedName = world + "_gen" + RandomUtil.rand.nextInt(100000, 999999); + + File srcDir = new File(Bukkit.getWorldContainer(), world); + File destDir = new File(Bukkit.getWorldContainer(), copiedName); + FileUtils.delete(destDir); + FileUtils.copy(srcDir, destDir); + new File(destDir, "session.lock").delete(); + new File(destDir, "uid.dat").delete(); + + World w = Bukkit.createWorld(new WorldCreator(copiedName).environment(Environment.NORMAL)); + if (w == null) { + throw new RuntimeException("Unable to create the world " + copiedName + ": Bukkit.createWorld(...) returned null value."); + } + w.setAutoSave(false); + loadedWorlds.put(world, w); + if (Bukkit.getPluginManager().getPlugin("Multiverse-Core") != null) + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "mvm set hidden true "+copiedName); + operationOnLoad.accept(w); + return w; + } + + + + private TemplatedWorldHandler() {} + +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/WorldUtil.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/WorldUtil.java index c252c09..a499f1d 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/WorldUtil.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/world/WorldUtil.java @@ -10,11 +10,18 @@ import java.util.List; import java.util.Objects; import java.util.regex.Pattern; +/** + * Provides utility methods to manage and analyze world's directory. + */ public class WorldUtil { - - - - + + + /** + * Determine the {@link Environment} of the provided world. + * @param world the name of the world. + * @return the {@link Environment}. + * @throws IllegalStateException if the provided world is not valid (cannot determine its environment) + */ public static Environment determineEnvironment(String world) { World bWorld = Bukkit.getWorld(world); if (bWorld != null) { @@ -40,7 +47,12 @@ public class WorldUtil { throw new IllegalStateException("Unable to determine the type of the world " + world + "."); } - + + /** + * Gets the list of all the regions of the provided world, based on the name of the region files. + * @param world the world. + * @return a {@link List} of {@link RegionCoord}. + */ public static List getExistingRegions(String world) { Environment env = determineEnvironment(world); @@ -71,11 +83,22 @@ public class WorldUtil { } private static final List REGION_DATA_FILES = Arrays.asList("entities", "poi", "region", "DIM-1", "DIM1"); - + + /** + * Gets all the directory containing region related data (entities, poi, region, DIM*) of the provided world. + * @param world the world. + * @return a {@link List} of directory. + */ public static List regionDataFolders(String world) { return onlyExisting(worldDir(world), REGION_DATA_FILES); } + /** + * Gets the list of all map related data files. + * That is the file {@code data/idcounts.dat} and all the files with the pattern {@code data/map_*.dat}. + * @param world the world. + * @return the list of all map related data files + */ public static List mapFiles(String world) { Pattern mapFilePattern = Pattern.compile("map_\\d+.dat"); return List.of(dataDir(world).listFiles((dir, name) -> mapFilePattern.matcher(name).find() || "idcounts.dat".equals(name))); @@ -88,18 +111,35 @@ public class WorldUtil { .toList(); } + /** + * Gets the directory of the provided world. + * @param world the world. + * @return the directory of the world. + */ public static File worldDir(String world) { return new File(Bukkit.getWorldContainer(), world); } + /** + * Gets the data directory of the provided world. + * @param world the world. + * @return the data directory of the world. + */ public static File dataDir(String world) { return new File(worldDir(world), "data"); } - + + /** + * Tells if the provided world is a valid one (has a directory and contains a file named level.dat). + * @param world the world. + * @return true if the world exists, false otherwise. + */ public static boolean isValidWorld(String world) { File d = worldDir(world); return d.isDirectory() && new File(d, "level.dat").isFile(); } - + + + private WorldUtil() {} } diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftVersion.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftVersion.java deleted file mode 100644 index f84af55..0000000 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/MinecraftVersion.java +++ /dev/null @@ -1,394 +0,0 @@ -package fr.pandacube.lib.util; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Enumeration of all known, post Netty-rewrite (1.7.2+), stable Minecraft Java versions. - *

- * It provides various utility methods to nicely display a set of Minecraft version (for instance "1.13.x", - * "1.16-1.16.3", "1.8.x and 1.9", "1.18.1 or 1.18.2") - *

- * Note that this enum uses one value to represent every Minecraft version using the same protocol version number. - * @deprecated This class may not be updated. Use the class ProtocolVersion in pandalib-core module instead. - */ -@Deprecated(forRemoval = true) -public enum MinecraftVersion { - /** Minecraft versions 1.7.2 to 1.7.5, protocol version 4. */ - v1_7_2_to_1_7_5(4, "1.7.2", "1.7.3", "1.7.4", "1.7.5"), - /** Minecraft versions 1.7.6 to 1.7.10, protocol version 5. */ - v1_7_6_to_1_7_10(5, "1.7.6", "1.7.7", "1.7.8", "1.7.9", "1.7.10"), - - /** Minecraft versions 1.8.x, protocol version 47. */ - v1_8(47, "1.8", "1.8.1", "1.8.2", "1.8.3", "1.8.4", "1.8.5", "1.8.6", "1.8.7", "1.8.8", "1.8.9"), - - /** Minecraft version 1.9, protocol version 107. */ - v1_9(107, "1.9"), - /** Minecraft version 1.9.1, protocol version 108. */ - v1_9_1(108, "1.9.1"), - /** Minecraft version 1.9.2, protocol version 109. */ - v1_9_2(109, "1.9.2"), - /** Minecraft versions 1.9.3 and 1.9.4, protocol version 110. */ - v1_9_3_to_1_9_4(110, "1.9.3", "1.9.4"), - - /** Minecraft versions 1.10.x, protocol version 210. */ - v1_10(210, "1.10", "1.10.1", "1.10.2"), - - /** Minecraft version 1.11, protocol version 315. */ - v1_11(315, "1.11"), - /** Minecraft versions 1.11.1 and 1.11.2, protocol version 316. */ - v1_11_1_to_1_11_2(316, "1.11.1", "1.11.2"), - - /** Minecraft version 1.12, protocol version 335. */ - v1_12(335, "1.12"), - /** Minecraft version 1.12.1, protocol version 338. */ - v1_12_1(338, "1.12.1"), - /** Minecraft version 1.12.2, protocol version 340. */ - v1_12_2(340, "1.12.2"), - - /** Minecraft version 1.13, protocol version 393. */ - v1_13(393, "1.13"), - /** Minecraft version 1.13.1, protocol version 401. */ - v1_13_1(401, "1.13.1"), - /** Minecraft version 1.13.2, protocol version 404. */ - v1_13_2(404, "1.13.2"), - - /** Minecraft version 1.14, protocol version 477. */ - v1_14(477, "1.14"), - /** Minecraft version 1.14.1, protocol version 480. */ - v1_14_1(480, "1.14.1"), - /** Minecraft version 1.14.2, protocol version 485. */ - v1_14_2(485, "1.14.2"), - /** Minecraft version 1.14.3, protocol version 490. */ - v1_14_3(490, "1.14.3"), - /** Minecraft version 1.14.4, protocol version 498. */ - v1_14_4(498, "1.14.4"), - - /** Minecraft version 1.15, protocol version 573. */ - v1_15(573, "1.15"), - /** Minecraft version 1.15.1, protocol version 575. */ - v1_15_1(575, "1.15.1"), - /** Minecraft version 1.15.2, protocol version 578. */ - v1_15_2(578, "1.15.2"), - - /** Minecraft version 1.16, protocol version 735. */ - v1_16(735, "1.16"), - /** Minecraft version 1.16.1, protocol version 736. */ - v1_16_1(736, "1.16.1"), - /** Minecraft version 1.16.2, protocol version 751. */ - v1_16_2(751, "1.16.2"), - /** Minecraft version 1.16.3, protocol version 753. */ - v1_16_3(753, "1.16.3"), - /** Minecraft versions 1.16.4 and 1.16.5, protocol version 754. */ - v1_16_4_to_1_16_5(754, "1.16.4", "1.16.5"), - - /** Minecraft version 1.17, protocol version 755. */ - v1_17(755, "1.17"), - /** Minecraft version 1.17.1, protocol version 756. */ - v1_17_1(756, "1.17.1"), - - /** Minecraft versions 1.18 and 1.18.1, protocol version 757. */ - v1_18_to_1_18_1(757, "1.18", "1.18.1"), - /** Minecraft version 1.18.2, protocol version 758. */ - v1_18_2(758, "1.18.2"), - - /** Minecraft version 1.19, protocol version 759. */ - v1_19(759, "1.19"), - /** Minecraft versions 1.19.1 and 1.19.2, protocol version 760. */ - v1_19_1_to_1_19_2(760, "1.19.1", "1.19.2"), - /** Minecraft versions 1.19.3, protocol version 761. */ - v1_19_3(761, "1.19.3"), - /** Minecraft versions 1.19.4, protocol version 762. */ - v1_19_4(762, "1.19.4"), - - /** Minecraft version 1.20 and 1.20.1, protocol version 763. */ - v1_20(763, "1.20", "1.20.1"); - - // IMPORTANT: don't forget to update the versionMergeDisplay value when adding a new version; - - private static final Map, List> versionMergeDisplay; - - static { - versionMergeDisplay = new HashMap<>(); - - versionMergeDisplay.put(EnumSet.of(v1_7_2_to_1_7_5, v1_7_6_to_1_7_10), - List.of("1.7.2-1.7.10")); - - versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1, v1_9_2, v1_9_3_to_1_9_4), - List.of("1.9.x")); - versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1, v1_9_2), - List.of("1.9-1.9.2")); - versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1), - List.of("1.9", "1.9.1")); - versionMergeDisplay.put(EnumSet.of(v1_9_1, v1_9_2, v1_9_3_to_1_9_4), - List.of("1.9.1-1.9.4")); - versionMergeDisplay.put(EnumSet.of(v1_9_1, v1_9_2), - List.of("1.9.1", "1.9.2")); - versionMergeDisplay.put(EnumSet.of(v1_9_2, v1_9_3_to_1_9_4), - List.of("1.9.2-1.9.4")); - - versionMergeDisplay.put(EnumSet.of(v1_11, v1_11_1_to_1_11_2), - List.of("1.11.x")); - - versionMergeDisplay.put(EnumSet.of(v1_12, v1_12_1, v1_12_2), - List.of("1.12.x")); - versionMergeDisplay.put(EnumSet.of(v1_12, v1_12_1), - List.of("1.12", "1.12.1")); - versionMergeDisplay.put(EnumSet.of(v1_12_1, v1_12_2), - List.of("1.12.1", "1.12.2")); - - versionMergeDisplay.put(EnumSet.of(v1_13, v1_13_1, v1_13_2), - List.of("1.13.x")); - versionMergeDisplay.put(EnumSet.of(v1_13, v1_13_1), - List.of("1.13", "1.13.1")); - versionMergeDisplay.put(EnumSet.of(v1_13_1, v1_13_2), - List.of("1.13.1", "1.13.2")); - - versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2, v1_14_3, v1_14_4), - List.of("1.14.x")); - versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2, v1_14_3), - List.of("1.14-1.14.3")); - versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2, v1_14_3, v1_14_4), - List.of("1.14.1-1.14.4")); - versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2), - List.of("1.14-1.14.2")); - versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2, v1_14_3), - List.of("1.14.1-1.14.3")); - versionMergeDisplay.put(EnumSet.of(v1_14_2, v1_14_3, v1_14_4), - List.of("1.14.2-1.14.4")); - versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1), - List.of("1.14", "1.14.1")); - versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2), - List.of("1.14.1", "1.14.2")); - versionMergeDisplay.put(EnumSet.of(v1_14_2, v1_14_3), - List.of("1.14.2", "1.14.3")); - versionMergeDisplay.put(EnumSet.of(v1_14_3, v1_14_4), - List.of("1.14.3", "1.14.4")); - - versionMergeDisplay.put(EnumSet.of(v1_15, v1_15_1, v1_15_2), - List.of("1.15.x")); - versionMergeDisplay.put(EnumSet.of(v1_15, v1_15_1), - List.of("1.15", "1.15.1")); - versionMergeDisplay.put(EnumSet.of(v1_15_1, v1_15_2), - List.of("1.15.1", "1.15.2")); - - versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3, v1_16_4_to_1_16_5), - List.of("1.16.x")); - versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3), - List.of("1.16-1.16.3")); - versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3, v1_16_4_to_1_16_5), - List.of("1.16.1-1.16.5")); - versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2), - List.of("1.16-1.16.2")); - versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3), - List.of("1.16.1-1.16.3")); - versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3, v1_16_4_to_1_16_5), - List.of("1.16.2-1.16.5")); - versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1), - List.of("1.16", "1.16.1")); - versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2), - List.of("1.16.1", "1.16.2")); - versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3), - List.of("1.16.2", "1.16.3")); - versionMergeDisplay.put(EnumSet.of(v1_16_3, v1_16_4_to_1_16_5), - List.of("1.16.3-1.16.5")); - - versionMergeDisplay.put(EnumSet.of(v1_17, v1_17_1), - List.of("1.17.x")); - - versionMergeDisplay.put(EnumSet.of(v1_18_to_1_18_1, v1_18_2), - List.of("1.18.x")); - - versionMergeDisplay.put(EnumSet.of(v1_19, v1_19_1_to_1_19_2, v1_19_3, v1_19_4), - List.of("1.19.x")); - versionMergeDisplay.put(EnumSet.of(v1_19, v1_19_1_to_1_19_2, v1_19_3), - List.of("1.19-1.19.3")); - versionMergeDisplay.put(EnumSet.of(v1_19_1_to_1_19_2, v1_19_3, v1_19_4), - List.of("1.19.1-1.19.4")); - versionMergeDisplay.put(EnumSet.of(v1_19, v1_19_1_to_1_19_2), - List.of("1.19-1.19.2")); - versionMergeDisplay.put(EnumSet.of(v1_19_1_to_1_19_2, v1_19_3), - List.of("1.19.1-1.19.3")); - versionMergeDisplay.put(EnumSet.of(v1_19_3, v1_19_4), - List.of("1.19.3-1.19.4")); - } - - - /** - * The protocol version number of this Minecraft version. - */ - public final int protocolVersionNumber; - /** - * All Minecraft version supported by this protocol version number. - */ - public final List versionsDisplay; - - MinecraftVersion(int protocolVersionNumber, String... versionsDisplay) { - this.protocolVersionNumber = protocolVersionNumber; - this.versionsDisplay = Arrays.asList(versionsDisplay); - } - - @Override - public String toString() { - return name() + "{protocol=" + protocolVersionNumber + ", toString(\"and\")=" + toString("and") + "}"; - } - - /** - * Returns a string representation of all the Minecraft version of this enum value, using - * {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)}. - * - * @param finalWordSeparator the word separator between the two last versions in the returned string, like "and", - * "or" or any other word of any language. The spaces before and after are already - * concatenated. - * @return a string representation of this {@link MinecraftVersion}. - */ - public String toString(String finalWordSeparator) { - return StringUtil.joinGrammatically(", ", " " + finalWordSeparator + " ", versionsDisplay); - } - - /** - * Returns a string representation of all the Minecraft version of this enum value, using - * {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the grammatical word "et" - * ("and" in french). - * - * @return a string representation of this {@link MinecraftVersion}. - * @deprecated it uses the hardcoded French word "et" as the final word separator. - * Use {@link #displayOptimizedListOfVersions(List, String)} with "et" as the last parameter instead. - */ - @Deprecated(forRemoval = true) - public String toStringAnd() { - return toString("et"); - } - - /** - * Returns a string representation of all the Minecraft version of this enum value, using - * {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the grammatical word "ou" - * ("or" in french). - * - * @return a string representation of this {@link MinecraftVersion}. - * @deprecated it uses the hardcoded French word "ou" as the final word separator. - * Use {@link #displayOptimizedListOfVersions(List, String)} with "ou" as the last parameter instead. - */ - @Deprecated(forRemoval = true) - public String toStringOr() { - return toString("ou"); - } - - - - - - - - - /** - * Gets the {@link MinecraftVersion} instance associated with the provided protocol version number. - * - * @param protocolVersionNumber the protocol version number - * @return the {@link MinecraftVersion} instance associated with the provided protocol version number, or null if - * there is none. - */ - public static MinecraftVersion getVersion(int protocolVersionNumber) { - for (MinecraftVersion mcV : values()) - if (mcV.protocolVersionNumber == protocolVersionNumber) return mcV; - return null; - } - - - /** - * Generate a string representation of the provided list of version, using - * {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)}. - * - * @param versions the minecraft versions to list - * @param finalWordSeparator the word separator between the two last versions in the returned string, like "and", - * "or" or any other word of any language. The spaces before and after are already - * concatenated. - * @return a string representation of the provided list of version. - */ - public static String displayOptimizedListOfVersions(List versions, String finalWordSeparator) { - return StringUtil.joinGrammatically(", ", " " + finalWordSeparator + " ", getVersionsDisplayList(versions)); - } - - /** - * Generate a string representation of the provided list of version, using - * {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the grammatical word "et" - * ("and" in french). - * - * @param versions the minecraft versions to list - * @return a string representation of the provided list of version. - * @deprecated it uses the hardcoded French word "et" as the final word separator. - * Use {@link #displayOptimizedListOfVersions(List, String)} with "et" as the last parameter instead. - */ - @Deprecated(forRemoval = true) - public static String displayOptimizedListOfVersionsAnd(List versions) { - return displayOptimizedListOfVersions(versions, "et"); - } - - /** - * Generate a string representation of the provided list of version, using - * {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the grammatical word "ou" - * ("or" in french). - * - * @param versions the minecraft versions to list - * @return a string representation of the provided list of version. - * @deprecated it uses the hardcoded French word "ou" as the final word separator. - * Use {@link #displayOptimizedListOfVersions(List, String)} with "ou" as the last parameter instead. - */ - @Deprecated(forRemoval = true) - public static String displayOptimizedListOfVersionsOr(List versions) { - return displayOptimizedListOfVersions(versions, "ou"); - } - - /** - * Returns an optimized list of string representation of Minecraft version, that represent the provided list of - * Minecraft version. - *

- * This method try to merge successive Minecraft version into a single string: for instance, all versions from 1.18 - * to 1.18.2 are represented by the string "1.18.x"; all version from 1.14.1 to 1.14.4 are represented by the string - * "1.14.1-1.14.4". - *

- * All possible merges of {@link MinecraftVersion} are listed in the static initializer of this enum. - * - * @param vList the {@link List} of {@link MinecraftVersion} - * @return an optimized list of string representation of Minecraft version. - */ - public static List getVersionsDisplayList(List vList) { - if (vList == null) - return new ArrayList<>(); - Set vSet = EnumSet.copyOf(vList); - - List ret = new ArrayList<>(); - - for (int i = 0; i < values().length; i++) { - if (!vSet.contains(values()[i])) - continue; - - EnumSet vSubSet = EnumSet.of(values()[i]); - while (i + 1 < values().length && vSet.contains(values()[i + 1])) { - i++; - vSubSet.add(values()[i]); - if (!versionMergeDisplay.containsKey(vSubSet)) { - vSubSet.remove(values()[i]); - i--; - break; - } - } - - if (vSubSet.size() == 1) { - ret.addAll(values()[i].versionsDisplay); - } - else { - ret.addAll(versionMergeDisplay.get(vSubSet)); - } - } - - return ret; - - } - -} diff --git a/pom.xml b/pom.xml index 38fe8a9..59b3aa8 100644 --- a/pom.xml +++ b/pom.xml @@ -123,11 +123,6 @@ - - apiNote - a - API Note: - implSpec a