Big Javadoc update + cleaned/refactored some code
This commit is contained in:
		| @@ -5,27 +5,47 @@ import fr.pandacube.lib.paper.json.PaperJson; | |||||||
| import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager; | import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager; | ||||||
| import org.bukkit.plugin.Plugin; | import org.bukkit.plugin.Plugin; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Main class for pandalib-paper. | ||||||
|  |  */ | ||||||
| public class PandaLibPaper { | public class PandaLibPaper { | ||||||
| 	 | 	 | ||||||
| 	private static Plugin plugin; | 	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) { | 	public static void onLoad(Plugin plugin) { | ||||||
| 		PandaLibPaper.plugin = plugin; | 		PandaLibPaper.plugin = plugin; | ||||||
| 		PaperJson.init(); | 		PaperJson.init(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Method to call in plugin's {@link Plugin#onEnable()} method. | ||||||
|  | 	 */ | ||||||
| 	public static void onEnable() { | 	public static void onEnable() { | ||||||
| 		PerformanceAnalysisManager.getInstance(); // initialize | 		PerformanceAnalysisManager.getInstance(); // initialize | ||||||
| 		ServerStopEvent.init(); | 		ServerStopEvent.init(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Method to call in plugin's {@link Plugin#onDisable()} method. | ||||||
|  | 	 */ | ||||||
| 	public static void disable() { | 	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() { | 	public static Plugin getPlugin() { | ||||||
| 		return plugin; | 		return plugin; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	private PandaLibPaper() {} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,11 @@ import java.util.List; | |||||||
| @SuppressWarnings("CanBeFinal") | @SuppressWarnings("CanBeFinal") | ||||||
| public class PaperBackupConfig { | public class PaperBackupConfig { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a new Paper backup config. | ||||||
|  |      */ | ||||||
|  |     public PaperBackupConfig() {} | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Set to true to enable worlds backup. |      * Set to true to enable worlds backup. | ||||||
|      * Defaults to true. |      * Defaults to true. | ||||||
|   | |||||||
| @@ -130,12 +130,12 @@ public class PaperBackupManager extends BackupManager implements Listener { | |||||||
| 	private final Set<String> dirtyForSave = new HashSet<>(); | 	private final Set<String> dirtyForSave = new HashSet<>(); | ||||||
|  |  | ||||||
| 	@EventHandler(priority = EventPriority.MONITOR) | 	@EventHandler(priority = EventPriority.MONITOR) | ||||||
| 	public void onWorldLoad(WorldLoadEvent event) { | 	void onWorldLoad(WorldLoadEvent event) { | ||||||
| 		initWorldProcess(event.getWorld().getName()); | 		initWorldProcess(event.getWorld().getName()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@EventHandler(priority = EventPriority.MONITOR) | 	@EventHandler(priority = EventPriority.MONITOR) | ||||||
| 	public void onWorldSave(WorldSaveEvent event) { | 	void onWorldSave(WorldSaveEvent event) { | ||||||
| 		if (event.getWorld().getLoadedChunks().length > 0 | 		if (event.getWorld().getLoadedChunks().length > 0 | ||||||
| 				|| dirtyForSave.contains(event.getWorld().getName())) { | 				|| dirtyForSave.contains(event.getWorld().getName())) { | ||||||
| 			compressWorlds.get(event.getWorld().getName()).setDirtyAfterSave(); | 			compressWorlds.get(event.getWorld().getName()).setDirtyAfterSave(); | ||||||
| @@ -148,18 +148,18 @@ public class PaperBackupManager extends BackupManager implements Listener { | |||||||
|  |  | ||||||
|  |  | ||||||
| 	@EventHandler(priority = EventPriority.MONITOR) | 	@EventHandler(priority = EventPriority.MONITOR) | ||||||
| 	public void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) { | 	void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) { | ||||||
| 		dirtyForSave.add(event.getFrom().getName()); | 		dirtyForSave.add(event.getFrom().getName()); | ||||||
| 		dirtyForSave.add(event.getPlayer().getWorld().getName()); | 		dirtyForSave.add(event.getPlayer().getWorld().getName()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@EventHandler(priority = EventPriority.MONITOR) | 	@EventHandler(priority = EventPriority.MONITOR) | ||||||
| 	public void onPlayerJoin(PlayerJoinEvent event) { | 	void onPlayerJoin(PlayerJoinEvent event) { | ||||||
| 		dirtyForSave.add(event.getPlayer().getWorld().getName()); | 		dirtyForSave.add(event.getPlayer().getWorld().getName()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@EventHandler(priority = EventPriority.MONITOR) | 	@EventHandler(priority = EventPriority.MONITOR) | ||||||
| 	public void onPlayerQuit(PlayerQuitEvent event) { | 	void onPlayerQuit(PlayerQuitEvent event) { | ||||||
| 		dirtyForSave.add(event.getPlayer().getWorld().getName()); | 		dirtyForSave.add(event.getPlayer().getWorld().getName()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,11 +10,19 @@ import net.kyori.adventure.bossbar.BossBar.Color; | |||||||
| import net.kyori.adventure.bossbar.BossBar.Overlay; | import net.kyori.adventure.bossbar.BossBar.Overlay; | ||||||
| import org.bukkit.Bukkit; | import org.bukkit.Bukkit; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A backup process with specific logic around Paper server. | ||||||
|  |  */ | ||||||
| public abstract class PaperBackupProcess extends BackupProcess { | public abstract class PaperBackupProcess extends BackupProcess { | ||||||
|  |  | ||||||
|  |  | ||||||
| 	private BossBar bossBar; | 	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) { | 	protected PaperBackupProcess(PaperBackupManager bm, String id) { | ||||||
| 		super(bm, id); | 		super(bm, id); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -3,8 +3,15 @@ package fr.pandacube.lib.paper.backup; | |||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.util.function.BiPredicate; | import java.util.function.BiPredicate; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A backup process with specific logic around Paper server working directory. | ||||||
|  |  */ | ||||||
| public class PaperWorkdirProcess extends PaperBackupProcess { | 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) { | 	protected PaperWorkdirProcess(PaperBackupManager bm) { | ||||||
| 		super(bm, "workdir"); | 		super(bm, "workdir"); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| package fr.pandacube.lib.paper.backup; | package fr.pandacube.lib.paper.backup; | ||||||
|  |  | ||||||
|  | import fr.pandacube.lib.chat.LegacyChatFormat; | ||||||
| import fr.pandacube.lib.paper.scheduler.SchedulerUtil; | import fr.pandacube.lib.paper.scheduler.SchedulerUtil; | ||||||
| import fr.pandacube.lib.paper.world.WorldUtil; | import fr.pandacube.lib.paper.world.WorldUtil; | ||||||
| import fr.pandacube.lib.util.log.Log; | import fr.pandacube.lib.util.log.Log; | ||||||
| import net.md_5.bungee.api.ChatColor; |  | ||||||
| import org.bukkit.Bukkit; | import org.bukkit.Bukkit; | ||||||
| import org.bukkit.World; | import org.bukkit.World; | ||||||
|  |  | ||||||
| @@ -11,14 +11,22 @@ import java.io.File; | |||||||
| import java.text.DateFormat; | import java.text.DateFormat; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A backup process with specific logic around Paper server world. | ||||||
|  |  */ | ||||||
| public class PaperWorldProcess extends PaperBackupProcess { | public class PaperWorldProcess extends PaperBackupProcess { | ||||||
| 	private final String worldName; | 	private final String worldName; | ||||||
| 	 | 	 | ||||||
| 	private boolean autoSave = true;  | 	private boolean autoSave = true; | ||||||
| 	 |  | ||||||
| 	protected PaperWorldProcess(PaperBackupManager bm, final String n) { | 	/** | ||||||
| 		super(bm, "worlds/" + n); | 	 * Instantiates a new backup process for a world. | ||||||
| 		worldName = n; | 	 * @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() { | 	private World getWorld() { | ||||||
| @@ -62,11 +70,11 @@ public class PaperWorldProcess extends PaperBackupProcess { | |||||||
|  |  | ||||||
| 	public void displayNextSchedule() { | 	public void displayNextSchedule() { | ||||||
| 		if (hasNextScheduled()) { | 		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()))); | 					+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext()))); | ||||||
| 		} | 		} | ||||||
| 		else { | 		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() { | 	public void setDirtyAfterSave() { | ||||||
| 		if (!isDirty()) { // don't set dirty if it is already | 		if (!isDirty()) { // don't set dirty if it is already | ||||||
| 			setDirtySinceNow(); | 			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) | 					+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG) | ||||||
| 					.format(new Date(getNext())) | 					.format(new Date(getNext())) | ||||||
| 			); | 			); | ||||||
|   | |||||||
| @@ -57,10 +57,21 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour | |||||||
|  |  | ||||||
|     private static CommandDispatcher<CommandSourceStack> vanillaPaperDispatcher = null; |     private static CommandDispatcher<CommandSourceStack> vanillaPaperDispatcher = null; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the Brigadier dispatcher provided by paper API during {@link LifecycleEvents#COMMANDS}. | ||||||
|  |      * <p> | ||||||
|  |      * 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<CommandSourceStack> getVanillaPaperDispatcher() { |     public static CommandDispatcher<CommandSourceStack> getVanillaPaperDispatcher() { | ||||||
|         return vanillaPaperDispatcher; |         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<CommandSourceStack> getRootNode() { |     public static RootCommandNode<CommandSourceStack> getRootNode() { | ||||||
|         return vanillaPaperDispatcher == null ? null : vanillaPaperDispatcher.getRoot(); |         return vanillaPaperDispatcher == null ? null : vanillaPaperDispatcher.getRoot(); | ||||||
|     } |     } | ||||||
| @@ -135,6 +146,9 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour | |||||||
|      */ |      */ | ||||||
|     protected final String[] aliases; |     protected final String[] aliases; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The command description. | ||||||
|  |      */ | ||||||
|     protected final String description; |     protected final String description; | ||||||
|  |  | ||||||
|     private final RegistrationPolicy registrationPolicy; |     private final RegistrationPolicy registrationPolicy; | ||||||
| @@ -248,7 +262,9 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour | |||||||
|         fixedNodes.add(originalNode); |         fixedNodes.add(originalNode); | ||||||
|         if (originalNode.getRedirect() != null) { |         if (originalNode.getRedirect() != null) { | ||||||
|             try { |             try { | ||||||
|  |                 @SuppressWarnings("rawtypes") | ||||||
|                 ReflectClass<CommandNode> cmdNodeClass = Reflect.ofClass(CommandNode.class); |                 ReflectClass<CommandNode> cmdNodeClass = Reflect.ofClass(CommandNode.class); | ||||||
|  |                 @SuppressWarnings("unchecked") | ||||||
|                 CommandNode<CommandSourceStack> unwrappedNode = (CommandNode<CommandSourceStack>) cmdNodeClass.field("unwrappedCached").getValue(originalNode); |                 CommandNode<CommandSourceStack> unwrappedNode = (CommandNode<CommandSourceStack>) cmdNodeClass.field("unwrappedCached").getValue(originalNode); | ||||||
|                 if (unwrappedNode != null) { |                 if (unwrappedNode != null) { | ||||||
|                     cmdNodeClass.field("modifier").setValue(unwrappedNode, cmdNodeClass.field("modifier").getValue(originalNode)); |                     cmdNodeClass.field("modifier").setValue(unwrappedNode, cmdNodeClass.field("modifier").getValue(originalNode)); | ||||||
| @@ -333,6 +349,10 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the aliases that are actually registered in the server. | ||||||
|  |      * @return the actually registered aliases. | ||||||
|  |      */ | ||||||
|     protected Set<String> getRegisteredAliases() { |     protected Set<String> getRegisteredAliases() { | ||||||
|         return Set.copyOf(registeredAliases); |         return Set.copyOf(registeredAliases); | ||||||
|     } |     } | ||||||
| @@ -379,12 +399,15 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|     public boolean isConsole(CommandSourceStack wrapper) { |     public boolean isConsole(CommandSourceStack wrapper) { | ||||||
|         return isConsole(getCommandSender(wrapper)); |         return isConsole(getCommandSender(wrapper)); | ||||||
|     } |     } | ||||||
|  |     @Override | ||||||
|     public boolean isPlayer(CommandSourceStack wrapper) { |     public boolean isPlayer(CommandSourceStack wrapper) { | ||||||
|         return isPlayer(getCommandSender(wrapper)); |         return isPlayer(getCommandSender(wrapper)); | ||||||
|     } |     } | ||||||
|  |     @Override | ||||||
|     public Predicate<CommandSourceStack> hasPermission(String permission) { |     public Predicate<CommandSourceStack> hasPermission(String permission) { | ||||||
|         return wrapper -> getCommandSender(wrapper).hasPermission(permission); |         return wrapper -> getCommandSender(wrapper).hasPermission(permission); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -10,28 +10,38 @@ import org.bukkit.event.server.PluginDisableEvent; | |||||||
| import org.bukkit.event.server.ServerEvent; | import org.bukkit.event.server.ServerEvent; | ||||||
| import org.jetbrains.annotations.NotNull; | 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. | ||||||
|  |  * <p> | ||||||
|  |  * 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 { | public class ServerStopEvent extends ServerEvent { | ||||||
|  |  | ||||||
|  |  | ||||||
|     private static final HandlerList handlers = new HandlerList(); |     private static final HandlerList handlers = new HandlerList(); | ||||||
|  |  | ||||||
|     @NotNull |     /** | ||||||
|     @Override |      * Gets the handler list of the event. | ||||||
|     public HandlerList getHandlers() { |      * @return the handler list of the event. | ||||||
|         return handlers; |      */ | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @NotNull |     @NotNull | ||||||
|     public static HandlerList getHandlerList() { |     public static HandlerList getHandlerList() { | ||||||
|         return handlers; |         return handlers; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     private static boolean hasTriggered = false; |     private static boolean hasTriggered = false; | ||||||
|  |     private static boolean isInit = false; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Register the event used to detect the server stop. | ||||||
|  |      */ | ||||||
|     public static void init() { |     public static void init() { | ||||||
|  |         if (isInit) | ||||||
|  |             return; | ||||||
|  |  | ||||||
|         BukkitEvent.register(new Listener() { |         BukkitEvent.register(new Listener() { | ||||||
|  |  | ||||||
|             @EventHandler(priority = EventPriority.LOWEST) |             @EventHandler(priority = EventPriority.LOWEST) | ||||||
| @@ -45,7 +55,25 @@ public class ServerStopEvent extends ServerEvent { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         isInit = true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private ServerStopEvent() {} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @NotNull | ||||||
|  |     @Override | ||||||
|  |     public HandlerList getHandlers() { | ||||||
|  |         return handlers; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -37,13 +37,21 @@ public class GUIHotBar implements Listener { | |||||||
| 	private final int defaultSlot; | 	private final int defaultSlot; | ||||||
| 	 | 	 | ||||||
| 	private final List<Player> currentPlayers = new ArrayList<>(); | 	private final List<Player> 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) { | 	public GUIHotBar(int defaultSlot) { | ||||||
| 		this.defaultSlot = Math.max(0, Math.min(8, defaultSlot)); | 		this.defaultSlot = Math.max(0, Math.min(8, defaultSlot)); | ||||||
| 		 | 		 | ||||||
| 		BukkitEvent.register(this); | 		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) { | 	public void disable(boolean clearPlayerMenuItems) { | ||||||
| 		removeAllPlayers(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 | 	 * 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. | 	 * 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 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. | 	 * @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<PlayerInventory, ItemStack> setter, Consumer<Player> run) { | 	public GUIHotBar addItem(ItemStack i, BiConsumer<PlayerInventory, ItemStack> setter, Consumer<Player> run) { | ||||||
| 		itemsAndSetters.put(i, setter); | 		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. | 	 * Add the hot bar elements to this player, or update them if applicable. | ||||||
| 	 * <br> | 	 * <br> | ||||||
| 	 * The player is automatically removed when they quit. You can remove it before by calling {@link #removePlayer(Player, boolean)}. | 	 * 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) { | 	public void addPlayer(Player p) { | ||||||
| 		if (!currentPlayers.contains(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. | 	 * 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) { | 	public void removePlayer(Player p) { | ||||||
| 		removePlayer(p, true); | 		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. | 	 * 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) { | 	public void removePlayer(Player p, boolean clearMenuItems) { | ||||||
| 		if (!currentPlayers.contains(p)) | 		if (!currentPlayers.contains(p)) | ||||||
| @@ -106,18 +119,28 @@ public class GUIHotBar implements Listener { | |||||||
| 		currentPlayers.remove(p); | 		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) { | 	public boolean containsPlayer(Player p) { | ||||||
| 		return currentPlayers.contains(p); | 		return currentPlayers.contains(p); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 | 	/** | ||||||
|  | 	 * Detach all players from this hot bar. | ||||||
|  | 	 */ | ||||||
| 	public void removeAllPlayers() { | 	public void removeAllPlayers() { | ||||||
| 		removeAllPlayers(true); | 		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) { | 	public void removeAllPlayers(boolean clearMenuItems) { | ||||||
| 		for (Player p : new ArrayList<>(currentPlayers)) | 		for (Player p : new ArrayList<>(currentPlayers)) | ||||||
| 			removePlayer(p, clearMenuItems); | 			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)) | 		if (!itemsAndSetters.containsKey(is)) | ||||||
| 			throw new IllegalArgumentException("The provided ItemStack is not registered in this GUIHotBar"); | 			throw new IllegalArgumentException("The provided ItemStack is not registered in this GUIHotBar"); | ||||||
| 		if (!currentPlayers.contains(p)) | 		if (!currentPlayers.contains(p)) | ||||||
| @@ -135,7 +158,7 @@ public class GUIHotBar implements Listener { | |||||||
| 		itemsAndSetters.get(is).accept(p.getInventory(), is.clone()); | 		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); | 		p.getInventory().remove(is); | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package fr.pandacube.lib.paper.util; | package fr.pandacube.lib.paper.inventory; | ||||||
| 
 | 
 | ||||||
| import org.bukkit.Location; | import org.bukkit.Location; | ||||||
| import org.bukkit.Material; | import org.bukkit.Material; | ||||||
| @@ -13,12 +13,22 @@ import org.jetbrains.annotations.Nullable; | |||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.ListIterator; | 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 { | public class InventoryWrapper implements Inventory { | ||||||
|     private final Inventory base; |     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) { |     public InventoryWrapper(Inventory base) { | ||||||
|         this.base = base; |         this.base = Objects.requireNonNull(base, "base inventory cannot be null."); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package fr.pandacube.lib.paper.util; | package fr.pandacube.lib.paper.inventory; | ||||||
| 
 | 
 | ||||||
| import com.google.common.collect.Streams; | import com.google.common.collect.Streams; | ||||||
| import fr.pandacube.lib.chat.Chat; | import fr.pandacube.lib.chat.Chat; | ||||||
| @@ -18,6 +18,9 @@ import java.util.function.Consumer; | |||||||
| 
 | 
 | ||||||
| import static fr.pandacube.lib.chat.ChatStatic.chatComponent; | import static fr.pandacube.lib.chat.ChatStatic.chatComponent; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * A builder for {@link ItemStack}. | ||||||
|  |  */ | ||||||
| public class ItemStackBuilder { | public class ItemStackBuilder { | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| @@ -77,10 +80,22 @@ public class ItemStackBuilder { | |||||||
| 		return (cachedMeta != null) ? cachedMeta : (cachedMeta = stack.getItemMeta()); | 		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<ItemMeta> metaUpdater) { | 	public ItemStackBuilder meta(Consumer<ItemMeta> metaUpdater) { | ||||||
| 		return meta(metaUpdater, ItemMeta.class); | 		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 <T> the type of item meta. | ||||||
|  | 	 * @return itself. | ||||||
|  | 	 */ | ||||||
| 	public <T extends ItemMeta> ItemStackBuilder meta(Consumer<T> metaUpdater, Class<T> metaType) { | 	public <T extends ItemMeta> ItemStackBuilder meta(Consumer<T> metaUpdater, Class<T> metaType) { | ||||||
| 		stack.editMeta(metaType, m -> { | 		stack.editMeta(metaType, m -> { | ||||||
| 			metaUpdater.accept(m); | 			metaUpdater.accept(m); | ||||||
| @@ -88,28 +103,52 @@ public class ItemStackBuilder { | |||||||
| 		}); | 		}); | ||||||
| 		return this; | 		return this; | ||||||
| 	} | 	} | ||||||
| 	 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Sets the amount of the built stack. | ||||||
|  | 	 * @param a the new amount. | ||||||
|  | 	 * @return itself. | ||||||
|  | 	 */ | ||||||
| 	public ItemStackBuilder amount(int a) { | 	public ItemStackBuilder amount(int a) { | ||||||
| 		stack.setAmount(a); | 		stack.setAmount(a); | ||||||
| 		return this; | 		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) { | 	public ItemStackBuilder rawDisplayName(Component displayName) { | ||||||
| 		return meta(m -> m.displayName(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) { | 	public ItemStackBuilder displayName(ComponentLike displayName) { | ||||||
| 		if (displayName != null) { | 		return rawDisplayName(displayName != null | ||||||
| 			return rawDisplayName(Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent()); | 				? Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent() | ||||||
| 		} | 				: null); | ||||||
| 		else |  | ||||||
| 			return rawDisplayName(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<Component> lore) { | 	public ItemStackBuilder rawLore(List<Component> lore) { | ||||||
| 		return meta(m -> m.lore(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<? extends ComponentLike> lore) { | 	public ItemStackBuilder lore(List<? extends ComponentLike> lore) { | ||||||
| 		if (lore != null) { | 		if (lore != null) { | ||||||
| 			return rawLore(lore.stream() | 			return rawLore(lore.stream() | ||||||
| @@ -119,7 +158,12 @@ public class ItemStackBuilder { | |||||||
| 		else | 		else | ||||||
| 			return rawLore(Collections.emptyList()); | 			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<? extends ComponentLike> lores) { | 	public ItemStackBuilder addLoreAfter(List<? extends ComponentLike> lores) { | ||||||
| 		if (lores != null) { | 		if (lores != null) { | ||||||
| 			List<Component> baseLore = getOrInitMeta().lore(); | 			List<Component> baseLore = getOrInitMeta().lore(); | ||||||
| @@ -135,7 +179,12 @@ public class ItemStackBuilder { | |||||||
| 		else | 		else | ||||||
| 			return this; | 			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) { | 	public ItemStackBuilder addLoreAfter(ComponentLike... lores) { | ||||||
| 		if (lores == null || lores.length == 0) | 		if (lores == null || lores.length == 0) | ||||||
| 			return this; | 			return this; | ||||||
| @@ -143,69 +192,120 @@ public class ItemStackBuilder { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
|  | 	 * Enchant the item. | ||||||
| 	 * Supports unsafe enchants. | 	 * Supports unsafe enchants. | ||||||
|  | 	 * @param enchantment the enchantment. | ||||||
|  | 	 * @param level the enchant level. | ||||||
|  | 	 * @return itself. | ||||||
| 	 */ | 	 */ | ||||||
| 	public ItemStackBuilder enchant(Enchantment enchantment, int level) { | 	public ItemStackBuilder enchant(Enchantment enchantment, int level) { | ||||||
| 		return meta(m -> m.addEnchant(enchantment, level, true)); | 		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) { | 	public ItemStackBuilder flags(ItemFlag... flags) { | ||||||
| 		return flags(true, 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) { | 	public ItemStackBuilder flags(boolean add, ItemFlag... flags) { | ||||||
| 		return add ? meta(m -> m.addItemFlags(flags)) | 		return add ? meta(m -> m.addItemFlags(flags)) | ||||||
| 				: meta(m -> m.removeItemFlags(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() { | 	public ItemStackBuilder hideEnchants() { | ||||||
| 		return hideEnchants(true); | 		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) { | 	public ItemStackBuilder hideEnchants(boolean hide) { | ||||||
| 		return flags(hide, ItemFlag.HIDE_ENCHANTS); | 		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() { | 	public ItemStackBuilder hideAttributes() { | ||||||
| 		return hideAttributes(true); | 		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) { | 	public ItemStackBuilder hideAttributes(boolean hide) { | ||||||
| 		return flags(hide, ItemFlag.HIDE_ATTRIBUTES); | 		return flags(hide, ItemFlag.HIDE_ATTRIBUTES); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Apply the enchantment glint to the item, event if it's not enchant. | ||||||
|  | 	 * @return itself. | ||||||
|  | 	 */ | ||||||
| 	public ItemStackBuilder fakeEnchant() { | 	public ItemStackBuilder fakeEnchant() { | ||||||
| 		return fakeEnchant(true); | 		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) { | 	public ItemStackBuilder fakeEnchant(boolean apply) { | ||||||
| 		if (apply) { | 		return meta(m -> m.setEnchantmentGlintOverride(apply ? true : null)); | ||||||
| 			enchant(Enchantment.UNBREAKING, 1); |  | ||||||
| 			return hideEnchants(); |  | ||||||
| 		} |  | ||||||
| 		return this; |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Sets this item as unbreakable. | ||||||
|  | 	 * @return itself. | ||||||
|  | 	 */ | ||||||
| 	public ItemStackBuilder unbreakable() { | 	public ItemStackBuilder unbreakable() { | ||||||
| 		return unbreakable(true); | 		return unbreakable(true); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Sets the unbreakable status of this item. | ||||||
|  | 	 * @param unbreakable the unbreakable status. | ||||||
|  | 	 * @return itself. | ||||||
|  | 	 */ | ||||||
| 	public ItemStackBuilder unbreakable(boolean unbreakable) { | 	public ItemStackBuilder unbreakable(boolean unbreakable) { | ||||||
| 		return meta(m -> m.setUnbreakable(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) { | 	public ItemStackBuilder damage(int d) { | ||||||
| 		return meta(m -> m.setDamage(d), Damageable.class); | 		return meta(m -> m.setDamage(d), Damageable.class); | ||||||
| 	} | 	} | ||||||
| 	 | 
 | ||||||
| 	 | 
 | ||||||
| 	 | 	/** | ||||||
| 	 | 	 * Build the {@link ItemStack}. | ||||||
| 	 | 	 * @return the build item stack. | ||||||
| 	 | 	 */ | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	public ItemStack build() { | 	public ItemStack build() { | ||||||
| 		return stack; | 		return stack; | ||||||
| 	} | 	} | ||||||
| @@ -14,4 +14,7 @@ public class PaperJson { | |||||||
|         Json.registerTypeAdapterFactory(ItemStackAdapter.FACTORY); |         Json.registerTypeAdapterFactory(ItemStackAdapter.FACTORY); | ||||||
|         Json.registerTypeAdapterFactory(ConfigurationSerializableAdapter.FACTORY); |         Json.registerTypeAdapterFactory(ConfigurationSerializableAdapter.FACTORY); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private PaperJson() {} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ import net.kyori.adventure.bossbar.BossBar.Color; | |||||||
| import net.kyori.adventure.bossbar.BossBar.Overlay; | import net.kyori.adventure.bossbar.BossBar.Overlay; | ||||||
| import net.kyori.adventure.text.format.NamedTextColor; | import net.kyori.adventure.text.format.NamedTextColor; | ||||||
| import net.kyori.adventure.text.format.TextColor; | import net.kyori.adventure.text.format.TextColor; | ||||||
| import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; |  | ||||||
| import org.bukkit.Bukkit; | import org.bukkit.Bukkit; | ||||||
| import org.bukkit.command.CommandSender; | import org.bukkit.command.CommandSender; | ||||||
| import org.bukkit.command.ConsoleCommandSender; | 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.successText; | ||||||
| import static fr.pandacube.lib.chat.ChatStatic.text; | 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 { | public class PerformanceAnalysisManager implements Listener { | ||||||
|  |  | ||||||
| 	private static PerformanceAnalysisManager instance; | 	private static PerformanceAnalysisManager instance; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets the instance of {@link PerformanceAnalysisManager}. | ||||||
|  | 	 * @return the instance of {@link PerformanceAnalysisManager}. | ||||||
|  | 	 */ | ||||||
| 	public static synchronized PerformanceAnalysisManager getInstance() { | 	public static synchronized PerformanceAnalysisManager getInstance() { | ||||||
| 		if (instance == null) | 		if (instance == null) | ||||||
| 			instance = new PerformanceAnalysisManager(); | 			instance = new PerformanceAnalysisManager(); | ||||||
| @@ -76,14 +82,22 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 	private final LinkedList<Long> interTPSDurations = new LinkedList<>(); | 	private final LinkedList<Long> interTPSDurations = new LinkedList<>(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The boss bar that shows in real time the CPU performance of the main server thread. | ||||||
|  | 	 */ | ||||||
| 	public final AutoUpdatedBossBar tpsBar; | 	public final AutoUpdatedBossBar tpsBar; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The boss bar that shows in real time the JVM RAM usage. | ||||||
|  | 	 */ | ||||||
| 	public final AutoUpdatedBossBar memoryBar; | 	public final AutoUpdatedBossBar memoryBar; | ||||||
| 	private final List<Player> barPlayers = new ArrayList<>(); | 	private final List<Player> barPlayers = new ArrayList<>(); | ||||||
| 	private final List<BossBar> relatedBossBars = new ArrayList<>(); | 	private final List<BossBar> relatedBossBars = new ArrayList<>(); | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * The gradient of color covering the common range of TPS values. | ||||||
|  | 	 */ | ||||||
| 	public final ChatColorGradient tps1sGradient = new ChatColorGradient() | 	public final ChatColorGradient tps1sGradient = new ChatColorGradient() | ||||||
| 			.add(0, NamedTextColor.BLACK) | 			.add(0, NamedTextColor.BLACK) | ||||||
| 			.add(1, NamedTextColor.DARK_RED) | 			.add(1, NamedTextColor.DARK_RED) | ||||||
| @@ -94,23 +108,7 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 			.add(21, PandaTheme.CHAT_GREEN_1_NORMAL) | 			.add(21, PandaTheme.CHAT_GREEN_1_NORMAL) | ||||||
| 			.add(26, NamedTextColor.BLUE); | 			.add(26, NamedTextColor.BLUE); | ||||||
|  |  | ||||||
|  | 	private final ChatColorGradient memoryUsageGradient = new ChatColorGradient() | ||||||
| 	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() |  | ||||||
| 			.add(.60f, PandaTheme.CHAT_GREEN_1_NORMAL) | 			.add(.60f, PandaTheme.CHAT_GREEN_1_NORMAL) | ||||||
| 			.add(.70f, NamedTextColor.YELLOW) | 			.add(.70f, NamedTextColor.YELLOW) | ||||||
| 			.add(.80f, NamedTextColor.GOLD) | 			.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) { | 	public boolean barsContainsPlayer(Player p) { | ||||||
| 		return barPlayers.contains(p); | 		return barPlayers.contains(p); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * Shows the performance boss bars to the provided player. | ||||||
|  | 	 * @param p the player. | ||||||
|  | 	 */ | ||||||
| 	public synchronized void addPlayerToBars(Player p) { | 	public synchronized void addPlayerToBars(Player p) { | ||||||
| 		barPlayers.add(p); | 		barPlayers.add(p); | ||||||
| 		p.showBossBar(tpsBar.bar); | 		p.showBossBar(tpsBar.bar); | ||||||
| @@ -143,7 +150,11 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 		for (BossBar bar : relatedBossBars) | 		for (BossBar bar : relatedBossBars) | ||||||
| 			p.showBossBar(bar); | 			p.showBossBar(bar); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * Hides the performance boss bars from the provided player. | ||||||
|  | 	 * @param p the player. | ||||||
|  | 	 */ | ||||||
| 	public synchronized void removePlayerToBars(Player p) { | 	public synchronized void removePlayerToBars(Player p) { | ||||||
| 		p.hideBossBar(tpsBar.bar); | 		p.hideBossBar(tpsBar.bar); | ||||||
| 		p.hideBossBar(memoryBar.bar); | 		p.hideBossBar(memoryBar.bar); | ||||||
| @@ -151,7 +162,11 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 			p.hideBossBar(bar); | 			p.hideBossBar(bar); | ||||||
| 		barPlayers.remove(p); | 		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) { | 	public synchronized void addBossBar(BossBar bar) { | ||||||
| 		if (relatedBossBars.contains(bar)) | 		if (relatedBossBars.contains(bar)) | ||||||
| 			return; | 			return; | ||||||
| @@ -159,7 +174,11 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 		for (Player p : barPlayers) | 		for (Player p : barPlayers) | ||||||
| 			p.showBossBar(bar); | 			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) { | 	public synchronized void removeBossBar(BossBar bar) { | ||||||
| 		if (!relatedBossBars.contains(bar)) | 		if (!relatedBossBars.contains(bar)) | ||||||
| 			return; | 			return; | ||||||
| @@ -167,8 +186,11 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 		for (Player p : barPlayers) | 		for (Player p : barPlayers) | ||||||
| 			p.hideBossBar(bar); | 			p.hideBossBar(bar); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	public synchronized void cancelInternalBossBar() { | 	/** | ||||||
|  | 	 * De-initialize the performance analyzer. | ||||||
|  | 	 */ | ||||||
|  | 	public synchronized void deinit() { | ||||||
| 		tpsBar.cancel(); | 		tpsBar.cancel(); | ||||||
| 		memoryBar.cancel(); | 		memoryBar.cancel(); | ||||||
| 	} | 	} | ||||||
| @@ -178,7 +200,7 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 	 | 	 | ||||||
| 	 | 	 | ||||||
| 	@EventHandler | 	@EventHandler | ||||||
| 	public synchronized void onTickStart(ServerTickStartEvent event) { | 	synchronized void onTickStart(ServerTickStartEvent event) { | ||||||
| 		tickStartNanoTime = System.nanoTime(); | 		tickStartNanoTime = System.nanoTime(); | ||||||
| 		tickStartCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0; | 		tickStartCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0; | ||||||
| 		 | 		 | ||||||
| @@ -186,7 +208,7 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	@EventHandler | 	@EventHandler | ||||||
| 	public synchronized void onTickEnd(ServerTickEndEvent event) { | 	synchronized void onTickEnd(ServerTickEndEvent event) { | ||||||
| 		tickEndNanoTime = System.nanoTime(); | 		tickEndNanoTime = System.nanoTime(); | ||||||
| 		long tickEndCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0; | 		long tickEndCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0; | ||||||
| 		 | 		 | ||||||
| @@ -213,7 +235,7 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
|  |  | ||||||
| 	 | 	 | ||||||
| 	@EventHandler | 	@EventHandler | ||||||
| 	public void onPlayerJoin(PlayerJoinEvent event) { | 	void onPlayerJoin(PlayerJoinEvent event) { | ||||||
| 		plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { | 		plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { | ||||||
| 			@SuppressWarnings("unchecked") | 			@SuppressWarnings("unchecked") | ||||||
| 			AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer> playerManager = (AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer>) AbstractPlayerManager.getInstance(); | 			AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer> playerManager = (AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer>) AbstractPlayerManager.getInstance(); | ||||||
| @@ -233,7 +255,7 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
|  |  | ||||||
|  |  | ||||||
| 	@EventHandler | 	@EventHandler | ||||||
| 	public void onPlayerQuit(PlayerQuitEvent event) { | 	void onPlayerQuit(PlayerQuitEvent event) { | ||||||
| 		removePlayerToBars(event.getPlayer()); | 		removePlayerToBars(event.getPlayer()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -375,28 +397,34 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	private Chat alteredTPSTitle = null; | 	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) { | 	public synchronized void setAlteredTPSTitle(Chat title) { | ||||||
| 		alteredTPSTitle = 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() { | 	public int getTPS1s() { | ||||||
| 		return (int) getTPS(1_000); | 		return (int) getTPS(1_000); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 *  |  | ||||||
| 	 * @param nbTicks number of ticks when the avg value is computed from history | 	 * @param nbTicks number of ticks when the avg value is computed from history | ||||||
| 	 * @return the avg number of TPS in the interval | 	 * @return the avg number of TPS in the interval | ||||||
| 	 */ | 	 */ | ||||||
| 	public synchronized float getAvgNano(List<Long> data, int nbTicks) { | 	private synchronized float getAvgNano(List<Long> data, int nbTicks) { | ||||||
| 		if (data.isEmpty()) | 		if (data.isEmpty()) | ||||||
| 			return 0; | 			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 | 	 * @param nbMillis number of milliseconds when the avg TPS is computed from history | ||||||
| 	 * @return the avg number of TPS in the interval | 	 * @return the avg number of TPS in the interval | ||||||
| 	 */ | 	 */ | ||||||
| @@ -429,8 +457,12 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
|  |  | ||||||
| 		return count * (1000 / (float) nbMillis); | 		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() { | 	public synchronized int[] getTPSHistory() { | ||||||
| 		int[] history = new int[60]; | 		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() { | 	public static int getTargetTickRate() { | ||||||
| 		return Math.round(Bukkit.getServerTickManager().getTickRate()); | 		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) { | 	public static void gc(CommandSender sender) { | ||||||
| 		long t1 = System.currentTimeMillis(); | 		long t1 = System.currentTimeMillis(); | ||||||
| 		long alloc1 = Runtime.getRuntime().totalMemory(); | 		long alloc1 = Runtime.getRuntime().totalMemory(); | ||||||
| @@ -478,7 +517,7 @@ public class PerformanceAnalysisManager implements Listener { | |||||||
| 			Log.info(finalMessage.getLegacyText()); | 			Log.info(finalMessage.getLegacyText()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public static String displayRound10(double val) { | 	private static String displayRound10(double val) { | ||||||
| 		long v = (long) Math.ceil(val * 10); | 		long v = (long) Math.ceil(val * 10); | ||||||
| 		return "" + (v / 10f); | 		return "" + (v / 10f); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,11 +1,10 @@ | |||||||
| package fr.pandacube.lib.paper.players; | 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.craftbukkit.CraftServer; | ||||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag; | 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.reflect.wrapper.minecraft.nbt.NbtIo; | ||||||
| import fr.pandacube.lib.paper.util.PlayerDataWrapper; | import fr.pandacube.lib.paper.players.PlayerDataWrapper.PlayerDataLoadException; | ||||||
| import fr.pandacube.lib.paper.util.PlayerDataWrapper.PlayerDataLoadException; |  | ||||||
| import fr.pandacube.lib.paper.world.WorldUtil; | import fr.pandacube.lib.paper.world.WorldUtil; | ||||||
| import fr.pandacube.lib.players.standalone.AbstractOffPlayer; | import fr.pandacube.lib.players.standalone.AbstractOffPlayer; | ||||||
| import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; | import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; | ||||||
| @@ -116,26 +115,31 @@ public interface PaperOffPlayer extends AbstractOffPlayer { | |||||||
|      * Player config |      * Player config | ||||||
|      */ |      */ | ||||||
|  |  | ||||||
|  |     @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation | ||||||
|     @Override |     @Override | ||||||
|     default String getConfig(String key) throws Exception { |     default String getConfig(String key) throws Exception { | ||||||
|         return PaperPlayerConfigStorage.get(getUniqueId(), key); |         return PaperPlayerConfigStorage.get(getUniqueId(), key); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation | ||||||
|     @Override |     @Override | ||||||
|     default String getConfig(String key, String deflt) throws Exception { |     default String getConfig(String key, String deflt) throws Exception { | ||||||
|         return PaperPlayerConfigStorage.get(getUniqueId(), key, deflt); |         return PaperPlayerConfigStorage.get(getUniqueId(), key, deflt); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation | ||||||
|     @Override |     @Override | ||||||
|     default void setConfig(String key, String value) throws Exception { |     default void setConfig(String key, String value) throws Exception { | ||||||
|         PaperPlayerConfigStorage.set(getUniqueId(), key, value); |         PaperPlayerConfigStorage.set(getUniqueId(), key, value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation | ||||||
|     @Override |     @Override | ||||||
|     default void updateConfig(String key, String deflt, UnaryOperator<String> updater) throws Exception { |     default void updateConfig(String key, String deflt, UnaryOperator<String> updater) throws Exception { | ||||||
|         PaperPlayerConfigStorage.update(getUniqueId(), key, deflt, updater); |         PaperPlayerConfigStorage.update(getUniqueId(), key, deflt, updater); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation | ||||||
|     @Override |     @Override | ||||||
|     default void unsetConfig(String key) throws Exception { |     default void unsetConfig(String key) throws Exception { | ||||||
|         PaperPlayerConfigStorage.unset(getUniqueId(), key); |         PaperPlayerConfigStorage.unset(getUniqueId(), key); | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package fr.pandacube.lib.paper.players; | |||||||
| import com.destroystokyo.paper.ClientOption; | import com.destroystokyo.paper.ClientOption; | ||||||
| import com.destroystokyo.paper.ClientOption.ChatVisibility; | import com.destroystokyo.paper.ClientOption.ChatVisibility; | ||||||
| import com.destroystokyo.paper.SkinParts; | 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.paper.reflect.wrapper.craftbukkit.CraftPlayer; | ||||||
| import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer; | import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer; | ||||||
| import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; | import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; | ||||||
| @@ -304,18 +304,39 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer | |||||||
|      * Player config |      * 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) { |     default String getNonPersistentConfig(String key) { | ||||||
|         return PlayerNonPersistentConfig.getData(getUniqueId(), 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) { |     default String getNonPersistentConfig(String key, String deflt) { | ||||||
|         return PlayerNonPersistentConfig.getData(getUniqueId(), key); |         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) { |     default void unsetNonPersistentConfig(String key) { | ||||||
|         PlayerNonPersistentConfig.unsetData(getUniqueId(), key); |         PlayerNonPersistentConfig.unsetData(getUniqueId(), key); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -16,6 +16,10 @@ import java.util.UUID; | |||||||
| import java.util.function.UnaryOperator; | import java.util.function.UnaryOperator; | ||||||
| import java.util.stream.Collectors; | 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 { | public class PaperPlayerConfigStorage { | ||||||
|  |  | ||||||
|     static final File storageFile = new File(PandaLibPaper.getPlugin().getDataFolder(), "playerdata.yml"); |     static final File storageFile = new File(PandaLibPaper.getPlugin().getDataFolder(), "playerdata.yml"); | ||||||
| @@ -77,6 +81,8 @@ public class PaperPlayerConfigStorage { | |||||||
|  |  | ||||||
|  |  | ||||||
|     private static synchronized void save() { |     private static synchronized void save() { | ||||||
|  |         if (!changed) | ||||||
|  |             return; | ||||||
|         YamlConfiguration config = new YamlConfiguration(); |         YamlConfiguration config = new YamlConfiguration(); | ||||||
|         for (UUID pId : playerSortedData.keySet()) { |         for (UUID pId : playerSortedData.keySet()) { | ||||||
|             String pIdStr = pId.toString(); |             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(); |         initIfNeeded(); | ||||||
|         ConfigKey cKey = new ConfigKey(player, key); |         ConfigKey cKey = new ConfigKey(player, key); | ||||||
|         ConfigEntry e = data.get(cKey); |         ConfigEntry e = data.get(cKey); | ||||||
|         if (e != null && newValue == null) { // delete |         if (e != null && value == null) { // delete | ||||||
|             data.remove(cKey); |             data.remove(cKey); | ||||||
|             if (playerSortedData.containsKey(player)) |             if (playerSortedData.containsKey(player)) | ||||||
|                 playerSortedData.get(player).remove(e); |                 playerSortedData.get(player).remove(e); | ||||||
| @@ -121,50 +133,91 @@ public class PaperPlayerConfigStorage { | |||||||
|                 keySortedData.get(key).remove(e); |                 keySortedData.get(key).remove(e); | ||||||
|             changed = true; |             changed = true; | ||||||
|         } |         } | ||||||
|         else if (e == null && newValue != null) { // create |         else if (e == null && value != null) { // create | ||||||
|             create(player, key, newValue); |             create(player, key, value); | ||||||
|             changed = true; |             changed = true; | ||||||
|         } |         } | ||||||
|         else if (e != null && !newValue.equals(e.value)) { // update |         else if (e != null && !value.equals(e.value)) { // update | ||||||
|             e.value = newValue; |             e.value = value; | ||||||
|             changed = true; |             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) { |     public static synchronized String get(UUID player, String key) { | ||||||
|         initIfNeeded(); |         initIfNeeded(); | ||||||
|         ConfigEntry e = data.get(new ConfigKey(player, key)); |         ConfigEntry e = data.get(new ConfigKey(player, key)); | ||||||
|         return e != null ? e.value : null; |         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; |         return value == null ? deflt : value; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static synchronized void update(UUID p, String k, String deflt, UnaryOperator<String> updater) { |     /** | ||||||
|         String oldValue = get(p, k, deflt); |      * Updates the value of the provided configuration key for the player, using the provided updater. | ||||||
|         set(p, k, updater.apply(oldValue)); |      * @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<String> 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<ConfigEntry> 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<ConfigEntry> getAllFromPlayer(UUID player) { | ||||||
|         initIfNeeded(); |         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<ConfigEntry> getAllWithKeys(String key) { |     public static LinkedHashSet<ConfigEntry> getAllWithKeys(String key) { | ||||||
|         initIfNeeded(); |         initIfNeeded(); | ||||||
|         return new LinkedHashSet<>(keySortedData.getOrDefault(key, new LinkedHashSet<>())); |         return new LinkedHashSet<>(keySortedData.getOrDefault(key, new LinkedHashSet<>())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static LinkedHashSet<ConfigEntry> 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<ConfigEntry> getAllWithKeyValue(String key, String v) { | ||||||
|         initIfNeeded(); |         initIfNeeded(); | ||||||
|         return getAllWithKeys(k).stream() |         return getAllWithKeys(key).stream() | ||||||
|                 .filter(c -> c.value.equals(v)) |                 .filter(c -> c.value.equals(v)) | ||||||
|                 .collect(Collectors.toCollection(LinkedHashSet::new)); |                 .collect(Collectors.toCollection(LinkedHashSet::new)); | ||||||
|     } |     } | ||||||
| @@ -173,25 +226,46 @@ public class PaperPlayerConfigStorage { | |||||||
|  |  | ||||||
|     private record ConfigKey(UUID playerId, String key) { } |     private record ConfigKey(UUID playerId, String key) { } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Class holding the playerId-key-value triplet. | ||||||
|  |      */ | ||||||
|     public static class ConfigEntry { |     public static class ConfigEntry { | ||||||
|         private final UUID playerId; |         private final UUID playerId; | ||||||
|         private final String key; |         private final String key; | ||||||
|         private String value; |         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) { |         private ConfigEntry(UUID playerId, String key, String value) { | ||||||
|             this.playerId = playerId; |             this.playerId = playerId; | ||||||
|             this.key = key; |             this.key = key; | ||||||
|             this.value = value; |             this.value = value; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Gets the player id. | ||||||
|  |          * @return the player id. | ||||||
|  |          */ | ||||||
|         public UUID getPlayerId() { |         public UUID getPlayerId() { | ||||||
|             return playerId; |             return playerId; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Gets the config key. | ||||||
|  |          * @return the config key. | ||||||
|  |          */ | ||||||
|         public String getKey() { |         public String getKey() { | ||||||
|             return key; |             return key; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Gets the config value. | ||||||
|  |          * @return the config value. | ||||||
|  |          */ | ||||||
|         public String getValue() { |         public String getValue() { | ||||||
|             return value; |             return value; | ||||||
|         } |         } | ||||||
| @@ -208,4 +282,7 @@ public class PaperPlayerConfigStorage { | |||||||
|                     && Objects.equals(key, o.key); |                     && Objects.equals(key, o.key); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private PaperPlayerConfigStorage() {} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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.craftbukkit.CraftItemStack; | ||||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag; | 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.ListTag; | ||||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.Tag; | import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.Tag; | ||||||
|  | import fr.pandacube.lib.paper.util.ExperienceUtil; | ||||||
| import org.bukkit.Bukkit; | import org.bukkit.Bukkit; | ||||||
| import org.bukkit.Material; |  | ||||||
| import org.bukkit.entity.HumanEntity; |  | ||||||
| import org.bukkit.event.inventory.InventoryType; | import org.bukkit.event.inventory.InventoryType; | ||||||
| import org.bukkit.inventory.EquipmentSlot; |  | ||||||
| import org.bukkit.inventory.Inventory; | import org.bukkit.inventory.Inventory; | ||||||
| import org.bukkit.inventory.ItemStack; | import org.bukkit.inventory.ItemStack; | ||||||
| import org.bukkit.inventory.PlayerInventory; | 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; | ||||||
| import java.util.Map.Entry; | import java.util.Map.Entry; | ||||||
| import java.util.Objects; |  | ||||||
| import java.util.TreeMap; | import java.util.TreeMap; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| import java.util.function.IntUnaryOperator; | 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) { | public record PlayerDataWrapper(CompoundTag data) { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Creates a new wrapper for the provided player 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) { |     public PlayerDataWrapper(CompoundTag data) { | ||||||
|         this.data = data == null ? new 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. |      * Gets a snapshot of the inventory of this player. | ||||||
|      * If modified, call the {@link #setInventory(PlayerInventory)} to update the data. |      * If the inventory is modified, the {@link #setInventory(PlayerInventory)} method should be called to update the | ||||||
|      * @return the player inventory |      * data in this wrapper. | ||||||
|  |      * @return the player inventory. | ||||||
|      */ |      */ | ||||||
|     public PlayerInventory getInventory() { |     public PlayerInventory getInventory() { | ||||||
|         return new DummyPlayerInventory( |         return new DummyPlayerInventory( | ||||||
| @@ -67,6 +62,11 @@ public record PlayerDataWrapper(CompoundTag data) { | |||||||
|         throw new IllegalArgumentException("Unrecognized NBT player inventory slot " + nbtSlot); |         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) { |     public void setInventory(PlayerInventory inv) { | ||||||
|         setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot); |         setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot); | ||||||
|         setHeldItemSlot(inv.getHeldItemSlot()); |         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() { |     public Inventory getEnderChest() { | ||||||
|         return getBukkitInventory("EnderItems", InventoryType.ENDER_CHEST, IntUnaryOperator.identity()); |         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) { |     public void setEnderChest(Inventory inv) { | ||||||
|         setBukkitInventory("EnderItems", inv, IntUnaryOperator.identity()); |         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() { |     public int getScore() { | ||||||
|         if (!data.contains("Score")) |         if (!data.contains("Score")) | ||||||
|             return 0; |             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) { |     public void setScore(int score) { | ||||||
|         data.putInt("Score", 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() { |     public int getTotalExperience() { | ||||||
|         if (!data.contains("XpTotal")) |         if (!data.contains("XpTotal")) | ||||||
|             return 0; |             return 0; | ||||||
|         return data.getInt("XpTotal"); |         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) { |     public void setTotalExperience(int xp) { | ||||||
|         data.putInt("XpTotal", xp); |         data.putInt("XpTotal", xp); | ||||||
|         double levelAndExp = ExperienceUtil.getLevelFromExp(xp); |         double levelAndExp = ExperienceUtil.getLevelFromExp(xp); | ||||||
| @@ -198,178 +225,11 @@ public record PlayerDataWrapper(CompoundTag data) { | |||||||
|         data.putFloat("XpP", (float) expProgress); |         data.putFloat("XpP", (float) expProgress); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     /** | ||||||
|     private static class DummyPlayerInventory extends InventoryWrapper implements PlayerInventory { |      * Thrown to indicate that an error occurred while loading the data of the player from the file. | ||||||
| 
 |      */ | ||||||
|         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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     public static class PlayerDataLoadException extends RuntimeException { |     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); |             super("Unable to load data of player " + playerName + " (" + playerId + ")", cause); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -12,6 +12,9 @@ import java.util.Map; | |||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Handles the player related configuration that is not persisted to disk. | ||||||
|  |  */ | ||||||
| public class PlayerNonPersistentConfig { | public class PlayerNonPersistentConfig { | ||||||
|     private static final Map<UUID, Map<String, ConfigEntry>> data = new HashMap<>(); |     private static final Map<UUID, Map<String, ConfigEntry>> 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"), |                 .put(Objects.requireNonNull(key, "key"), | ||||||
|                         new ConfigEntry(Objects.requireNonNull(value, "value"), |                         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")); |                 .remove(Objects.requireNonNull(key, "key")); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static String getData(UUID playerId, String key) { |     /** | ||||||
|         Map<String, ConfigEntry> 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<String, ConfigEntry> playerData = data.getOrDefault(Objects.requireNonNull(player, "playerId"), new HashMap<>()); | ||||||
|         ConfigEntry ce = playerData.get(Objects.requireNonNull(key, "key")); |         ConfigEntry ce = playerData.get(Objects.requireNonNull(key, "key")); | ||||||
|         if (ce == null) |         if (ce == null) | ||||||
|             return null; |             return null; | ||||||
|         if (!ce.expiration.valid(playerId, key)) { |         if (!ce.expirationPolicy.valid(player, key)) { | ||||||
|             playerData.remove(key); |             playerData.remove(key); | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|         return ce.value; |         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); |         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) { |         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 |             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; |         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) { |         public ExpiresTick(long expirationDelayTick) { | ||||||
|             expirationTick = tick + expirationDelayTick; |             expirationTick = tick + expirationDelayTick; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|         protected boolean valid(UUID player, String key) { |         protected boolean valid(UUID player, String key) { | ||||||
|             return tick < expirationTick; |             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) { |         protected boolean valid(UUID player, String key) { | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
| @@ -103,7 +172,7 @@ public class PlayerNonPersistentConfig { | |||||||
|  |  | ||||||
|  |  | ||||||
|     private static class ConfigListeners implements Listener { |     private static class ConfigListeners implements Listener { | ||||||
|         public ConfigListeners() { |         private ConfigListeners() { | ||||||
|             Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin()); |             Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -111,7 +180,7 @@ public class PlayerNonPersistentConfig { | |||||||
|         public void onPlayerQuit(PlayerQuitEvent event) { |         public void onPlayerQuit(PlayerQuitEvent event) { | ||||||
|             data.getOrDefault(event.getPlayer().getUniqueId(), new HashMap<>()) |             data.getOrDefault(event.getPlayer().getUniqueId(), new HashMap<>()) | ||||||
|                     .entrySet() |                     .entrySet() | ||||||
|                     .removeIf(e -> e.getValue().expiration instanceof ExpiresLogout); |                     .removeIf(e -> e.getValue().expirationPolicy instanceof ExpiresLogout); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         @EventHandler |         @EventHandler | ||||||
| @@ -119,4 +188,9 @@ public class PlayerNonPersistentConfig { | |||||||
|             tick++; |             tick++; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private PlayerNonPersistentConfig() {} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -23,4 +23,6 @@ public class OBCReflect { | |||||||
| 		return Reflect.ofClass(CRAFTBUKKIT_PACKAGE + "." + obcClass); | 		return Reflect.ofClass(CRAFTBUKKIT_PACKAGE + "." + obcClass); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	private OBCReflect() { } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -226,4 +226,6 @@ public class PandalibPaperReflect { | |||||||
|         thAcc.throwCaught(); |         thAcc.throwCaught(); | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private PandalibPaperReflect() {} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,21 +1,24 @@ | |||||||
| package fr.pandacube.lib.paper.reflect.util; | 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.Chat; | ||||||
| import fr.pandacube.lib.chat.ChatConfig.PandaTheme; | import fr.pandacube.lib.chat.ChatConfig.PandaTheme; | ||||||
| import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager; | import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager; | ||||||
| import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftWorld; | 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 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 { | 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) { | 	public static void nmsSaveFlush(World w) { | ||||||
| 		PerformanceAnalysisManager.getInstance().setAlteredTPSTitle( | 		PerformanceAnalysisManager.getInstance().setAlteredTPSTitle( | ||||||
| 				Chat.text("Sauvegarde map ").color(PandaTheme.CHAT_BROWN_2_SAT).thenData(w.getName()).thenText(" ...") | 				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); | 			PerformanceAnalysisManager.getInstance().setAlteredTPSTitle(null); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * Save all the loaded worlds, using {@link #nmsSaveFlush(World)}. | ||||||
|  | 	 */ | ||||||
| 	public static void nmsSaveAllFlush() { | 	public static void nmsSaveAllFlush() { | ||||||
| 		Bukkit.getWorlds().forEach(WorldSaveUtil::nmsSaveFlush); | 		Bukkit.getWorlds().forEach(WorldSaveUtil::nmsSaveFlush); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	private WorldSaveUtil() {} | ||||||
| 	 | 	 | ||||||
| } | } | ||||||
|   | |||||||
| @@ -69,6 +69,6 @@ public class SchedulerUtil { | |||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	 | 	 | ||||||
| 	 | 	private SchedulerUtil() {} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| package fr.pandacube.lib.paper.util; | package fr.pandacube.lib.paper.util; | ||||||
|  |  | ||||||
| import java.util.Timer; | import fr.pandacube.lib.chat.Chat; | ||||||
| import java.util.TimerTask; | import fr.pandacube.lib.paper.PandaLibPaper; | ||||||
| import java.util.function.Predicate; | 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.Bukkit; | ||||||
| import org.bukkit.entity.Player; | import org.bukkit.entity.Player; | ||||||
| import org.bukkit.event.EventHandler; | import org.bukkit.event.EventHandler; | ||||||
| @@ -14,26 +16,37 @@ import org.bukkit.event.player.PlayerQuitEvent; | |||||||
| import org.bukkit.scheduler.BukkitScheduler; | import org.bukkit.scheduler.BukkitScheduler; | ||||||
| import org.bukkit.scheduler.BukkitTask; | import org.bukkit.scheduler.BukkitTask; | ||||||
|  |  | ||||||
| import fr.pandacube.lib.chat.Chat; | import java.util.Timer; | ||||||
| import fr.pandacube.lib.util.log.Log; | import java.util.TimerTask; | ||||||
| import fr.pandacube.lib.paper.PandaLibPaper; | import java.util.function.Predicate; | ||||||
| import net.kyori.adventure.bossbar.BossBar; |  | ||||||
| import net.kyori.adventure.bossbar.BossBar.Color; |  | ||||||
| import net.kyori.adventure.text.Component; |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A {@link BossBar} capable of automatically updating itself by registering a {@link BukkitTask} or using a {@link Timer}. | ||||||
|  |  */ | ||||||
| public class AutoUpdatedBossBar implements Listener { | public class AutoUpdatedBossBar implements Listener { | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * The boss bar itself. | ||||||
|  | 	 */ | ||||||
| 	public final BossBar bar; | 	public final BossBar bar; | ||||||
|  | 	/** | ||||||
|  | 	 * the function executed to update the boss bar. | ||||||
|  | 	 */ | ||||||
| 	public final BarUpdater updater; | 	public final BarUpdater updater; | ||||||
| 	 |  | ||||||
| 	private Timer timer = null; | 	private Timer timer = null; | ||||||
| 	private BukkitTask bukkitTask = null; | 	private BukkitTask bukkitTask = null; | ||||||
| 	 | 	 | ||||||
| 	private boolean scheduled = false; | 	private boolean scheduled = false; | ||||||
| 	 | 	 | ||||||
| 	private boolean followPlayerList = false; | 	private LoginLogoutListener followPlayerList = null; | ||||||
| 	private Predicate<Player> playerCondition = null; | 	private Predicate<Player> 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) { | 	public AutoUpdatedBossBar(BossBar bar, BarUpdater updater) { | ||||||
| 		this.bar = bar; | 		this.bar = bar; | ||||||
| 		this.updater = updater; | 		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 | 	 * Schedule the update of this boss bar with synchronisation with the ticking of this Minecraft server. | ||||||
| 	 * current Minecraft server (follow the tick count progress). |  | ||||||
| 	 * The underlying method called is {@link BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)}. | 	 * 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 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 | 	 * @param tickPeriod number of server tick between each call of the updater | ||||||
| 	 */ | 	 */ | ||||||
| @@ -90,55 +102,65 @@ public class AutoUpdatedBossBar implements Listener { | |||||||
| 				}, tickDelay, tickPeriod); | 				}, 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<Player> condition) { | 	public synchronized void followLoginLogout(Predicate<Player> condition) { | ||||||
| 		playerCondition = condition; | 		playerCondition = condition; | ||||||
| 		if (followPlayerList) | 		if (followPlayerList != null) | ||||||
| 			return; | 			return; | ||||||
| 		followPlayerList = true; | 		followPlayerList = new LoginLogoutListener(); | ||||||
| 		BukkitEvent.register(this); | 		BukkitEvent.register(followPlayerList); | ||||||
| 		Bukkit.getServer().getOnlinePlayers().forEach(p -> onPlayerJoin(new PlayerJoinEvent(p, Component.text("")))); | 		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() { | 	public synchronized void unfollowPlayerList() { | ||||||
| 		if (!followPlayerList) | 		if (followPlayerList == null) | ||||||
| 			return; | 			return; | ||||||
| 		followPlayerList = false; |  | ||||||
| 		playerCondition = null; | 		playerCondition = null; | ||||||
| 		PlayerJoinEvent.getHandlerList().unregister(this); | 		BukkitEvent.unregister(followPlayerList); | ||||||
| 		PlayerQuitEvent.getHandlerList().unregister(this); | 		followPlayerList = null; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@EventHandler(priority=EventPriority.MONITOR) | 	private class LoginLogoutListener implements Listener { | ||||||
| 	public synchronized void onPlayerJoin(PlayerJoinEvent event) { |  | ||||||
| 		if (!followPlayerList) | 		@EventHandler(priority=EventPriority.MONITOR) | ||||||
| 			return; | 		public void onPlayerJoin(PlayerJoinEvent event) { | ||||||
| 		if (playerCondition != null && !playerCondition.test(event.getPlayer())) | 			if (playerCondition != null && !playerCondition.test(event.getPlayer())) | ||||||
| 			return; | 				return; | ||||||
| 		synchronized (bar) { | 			synchronized (bar) { | ||||||
| 			event.getPlayer().showBossBar(bar); | 				event.getPlayer().showBossBar(bar); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
| 	 | 		@EventHandler(priority=EventPriority.HIGH) | ||||||
| 	@EventHandler(priority=EventPriority.HIGH) | 		public void onPlayerQuit(PlayerQuitEvent event) { | ||||||
| 	public synchronized void onPlayerQuit(PlayerQuitEvent event) { | 			synchronized (bar) { | ||||||
| 		if (!followPlayerList) | 				event.getPlayer().hideBossBar(bar); | ||||||
| 			return; | 			} | ||||||
| 		synchronized (bar) { |  | ||||||
| 			event.getPlayer().hideBossBar(bar); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Hides this boss bar from all players. | ||||||
|  | 	 */ | ||||||
| 	public void removeAll() { | 	public void removeAll() { | ||||||
| 		synchronized (bar) { | 		synchronized (bar) { | ||||||
| 			for (Player p : Bukkit.getOnlinePlayers()) | 			for (Player p : Bukkit.getOnlinePlayers()) | ||||||
| 				p.hideBossBar(bar); | 				p.hideBossBar(bar); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	 | 	/** | ||||||
|  | 	 * Cancel any auto-updating of this boss bar. | ||||||
|  | 	 */ | ||||||
| 	public synchronized void cancel() { | 	public synchronized void cancel() { | ||||||
| 		if (!scheduled) | 		if (!scheduled) | ||||||
| 			return; | 			return; | ||||||
| @@ -151,13 +173,19 @@ public class AutoUpdatedBossBar implements Listener { | |||||||
| 			bukkitTask.cancel(); | 			bukkitTask.cancel(); | ||||||
| 			bukkitTask = null; | 			bukkitTask = null; | ||||||
| 		} | 		} | ||||||
| 		 | 		unfollowPlayerList(); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 | 	/** | ||||||
|  | 	 * Functional interface taking an instance of {@link AutoUpdatedBossBar} to update it. Returns nothing. | ||||||
|  | 	 */ | ||||||
| 	@FunctionalInterface | 	@FunctionalInterface | ||||||
| 	public interface BarUpdater { | 	public interface BarUpdater { | ||||||
|  | 		/** | ||||||
|  | 		 * Updates the boss bar. | ||||||
|  | 		 * @param bar the auto-updated boss bar instance. | ||||||
|  | 		 */ | ||||||
| 		void update(AutoUpdatedBossBar bar); | 		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. | 	 * Utility method to update the title of the boss bar without unnecessary packet. | ||||||
|  | 	 * @param title the new title. | ||||||
| 	 */ | 	 */ | ||||||
| 	public void setTitle(Chat title) { | 	public void setTitle(Chat title) { | ||||||
| 		synchronized (bar) { | 		synchronized (bar) { | ||||||
| @@ -174,6 +203,7 @@ public class AutoUpdatedBossBar implements Listener { | |||||||
| 	 | 	 | ||||||
| 	/** | 	/** | ||||||
| 	 * Utility method to update the color of the boss bar without unnecessary packet. | 	 * Utility method to update the color of the boss bar without unnecessary packet. | ||||||
|  | 	 * @param color the new color. | ||||||
| 	 */ | 	 */ | ||||||
| 	public void setColor(Color color) { | 	public void setColor(Color color) { | ||||||
| 		synchronized (bar) { | 		synchronized (bar) { | ||||||
| @@ -183,6 +213,7 @@ public class AutoUpdatedBossBar implements Listener { | |||||||
| 	 | 	 | ||||||
| 	/** | 	/** | ||||||
| 	 * Utility method to update the progress of the boss bar without unnecessary packet. | 	 * Utility method to update the progress of the boss bar without unnecessary packet. | ||||||
|  | 	 * @param progress the new progress value. | ||||||
| 	 */ | 	 */ | ||||||
| 	public void setProgress(double progress) { | 	public void setProgress(double progress) { | ||||||
| 		synchronized (bar) { | 		synchronized (bar) { | ||||||
|   | |||||||
| @@ -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()); |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| } |  | ||||||
| @@ -16,45 +16,102 @@ import org.bukkit.scheduler.BukkitTask; | |||||||
| import fr.pandacube.lib.paper.PandaLibPaper; | import fr.pandacube.lib.paper.PandaLibPaper; | ||||||
| import fr.pandacube.lib.reflect.Reflect; | import fr.pandacube.lib.reflect.Reflect; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Utility class to more concisely handle Bukkit Events | ||||||
|  |  */ | ||||||
| public class BukkitEvent { | public class BukkitEvent { | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * Register a single event executor. | ||||||
|  | 	 * <p> | ||||||
|  | 	 * 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 <E> the event type. | ||||||
|  | 	 */ | ||||||
| 	public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor) { | 	public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor) { | ||||||
| 		return register(eventClass, eventExecutor, EventPriority.NORMAL, false); | 		return register(eventClass, eventExecutor, EventPriority.NORMAL, false); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * Register a single event executor. | ||||||
|  | 	 * <p> | ||||||
|  | 	 * 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 <E> the event type. | ||||||
|  | 	 */ | ||||||
| 	public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority) { | 	public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority) { | ||||||
| 		return register(eventClass, eventExecutor, priority, false); | 		return register(eventClass, eventExecutor, priority, false); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * Register a single event executor. | ||||||
|  | 	 * <p> | ||||||
|  | 	 * 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 <E> the event type. | ||||||
|  | 	 */ | ||||||
| 	public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, boolean ignoreCancelled) { | 	public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, boolean ignoreCancelled) { | ||||||
| 		return register(eventClass, eventExecutor, EventPriority.NORMAL, 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 <E> the event type. | ||||||
|  | 	 */ | ||||||
| 	public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority, boolean ignoreCancelled) { | 	public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority, boolean ignoreCancelled) { | ||||||
| 		Bukkit.getPluginManager().registerEvent(eventClass, eventExecutor, priority, eventExecutor, PandaLibPaper.getPlugin(), ignoreCancelled); | 		Bukkit.getPluginManager().registerEvent(eventClass, eventExecutor, priority, eventExecutor, PandaLibPaper.getPlugin(), ignoreCancelled); | ||||||
| 		return eventExecutor; | 		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) { | 	public static void register(Listener l) { | ||||||
| 		Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin()); | 		Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin()); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 | 	/** | ||||||
|  | 	 * Unregister a listener | ||||||
|  | 	 * @param listener the listener to unregister. | ||||||
|  | 	 */ | ||||||
| 	public static void unregister(Listener listener) { | 	public static void unregister(Listener listener) { | ||||||
| 		HandlerList.unregisterAll(listener); | 		HandlerList.unregisterAll(listener); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Lists all existing subclasses of {@link Event}. | ||||||
|  | 	 * @return a list of all existing subclasses of {@link Event}. | ||||||
|  | 	 */ | ||||||
| 	public static List<Class<? extends Event>> getAllEventClasses() { | 	public static List<Class<? extends Event>> getAllEventClasses() { | ||||||
| 		List<Class<? extends Event>> classes = Reflect.ofClass(Event.class).getAllSubclasses(false); | 		List<Class<? extends Event>> classes = Reflect.ofClass(Event.class).getAllSubclasses(false); | ||||||
| 		classes.removeIf(e -> getHandlerList(e) == null); | 		classes.removeIf(e -> getHandlerList(e) == null); | ||||||
| 		return classes; | 		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 |     // method retrieved from OB.plugin.SimplePluginManager#getEventListeners | ||||||
|     public static HandlerList getHandlerList(Class<? extends Event> type) { |     public static HandlerList getHandlerList(Class<? extends Event> type) { | ||||||
|         try { |         try { | ||||||
| @@ -81,11 +138,18 @@ public class BukkitEvent { | |||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 | 	/** | ||||||
|  | 	 * An single executor event listener. Used for the {@link #register(Class, EventListener)} static method and the other variants. | ||||||
|  | 	 * @param <E> the event type. | ||||||
|  | 	 */ | ||||||
| 	public interface EventListener<E extends Event> extends Listener, EventExecutor { | 	public interface EventListener<E extends Event> extends Listener, EventExecutor { | ||||||
| 		 |  | ||||||
|  | 		/** | ||||||
|  | 		 * The event handler. | ||||||
|  | 		 * @param event the event. | ||||||
|  | 		 */ | ||||||
| 		void onEvent(E event); | 		void onEvent(E event); | ||||||
| 		 | 		 | ||||||
| 		@SuppressWarnings("unchecked") | 		@SuppressWarnings("unchecked") | ||||||
| @@ -99,12 +163,17 @@ public class BukkitEvent { | |||||||
| 	 * Abstract implementation of {@link EventListener} that ensure as good as it can, | 	 * Abstract implementation of {@link EventListener} that ensure as good as it can, | ||||||
| 	 * that it is the last listener called to handle the event. | 	 * that it is the last listener called to handle the event. | ||||||
| 	 * | 	 * | ||||||
| 	 * @param <E> the type of the event | 	 * @param <E> the type of the event. | ||||||
| 	 */ | 	 */ | ||||||
| 	public static abstract class EnforcedLastListener<E extends Event> implements EventListener<E> { | 	public static abstract class EnforcedLastListener<E extends Event> implements EventListener<E> { | ||||||
| 		private final Class<E> eventClass; | 		private final Class<E> eventClass; | ||||||
| 		private final boolean ignoreCancelled; | 		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<E> eventClass, boolean ignoreCancelled) { | 		public EnforcedLastListener(Class<E> eventClass, boolean ignoreCancelled) { | ||||||
| 			this.eventClass = eventClass; | 			this.eventClass = eventClass; | ||||||
| 			this.ignoreCancelled = ignoreCancelled; | 			this.ignoreCancelled = ignoreCancelled; | ||||||
| @@ -143,6 +212,9 @@ public class BukkitEvent { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	private BukkitEvent() {} | ||||||
| 	 | 	 | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,8 +3,13 @@ package fr.pandacube.lib.paper.util; | |||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
|  | import net.kyori.adventure.text.format.TextColor; | ||||||
| import org.bukkit.Color; | import org.bukkit.Color; | ||||||
|  | import org.bukkit.DyeColor; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Color related utility class. | ||||||
|  |  */ | ||||||
| public class ColorUtil { | public class ColorUtil { | ||||||
| 	 | 	 | ||||||
| 	/* | 	/* | ||||||
| @@ -13,9 +18,10 @@ public class ColorUtil { | |||||||
| 	private static final List<Color> rainbowColors = new ArrayList<>(); | 	private static final List<Color> rainbowColors = new ArrayList<>(); | ||||||
| 	 | 	 | ||||||
| 	/** | 	/** | ||||||
| 	 * Get a rainbow color | 	 * Gets a rainbow color. | ||||||
| 	 * @param variation from 0 (include) to 1 (exclude). | 	 * @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 | 	 * 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) { | 	public static Color getRainbowColor(double variation) { | ||||||
| 		synchronized (rainbowColors) { | 		synchronized (rainbowColors) { | ||||||
| @@ -34,16 +40,42 @@ public class ColorUtil { | |||||||
| 		 | 		 | ||||||
| 		return rainbowColors.get((int)(variation * rainbowColors.size())); | 		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() {} | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -54,12 +54,12 @@ public class ExperienceUtil { | |||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * @see <a href="http://minecraft.gamepedia.com/Experience#Leveling_up">Experience (#leveling up) - Minecraft Wiki</a> | 	 * @see <a href="http://minecraft.gamepedia.com/Experience#Leveling_up">Experience (#leveling up) - Minecraft Wiki</a> | ||||||
| 	 * | 	 * <p> | ||||||
| 	 *      "The formulas for figuring out how many experience orbs you need to | 	 * <q>The formulas for figuring out how many experience orbs you need to | ||||||
| 	 *      get to the next level are as follows: | 	 *      get to the next level are as follows: | ||||||
| 	 *      Experience Required = 2[Current Level] + 7 (at levels 0-15) | 	 *      Experience Required = 2[Current Level] + 7 (at levels 0-15) | ||||||
| 	 *      5[Current Level] - 38 (at levels 16-30) | 	 *      5[Current Level] - 38 (at levels 16-30) | ||||||
| 	 *      9[Current Level] - 158 (at level 31+)" | 	 *      9[Current Level] - 158 (at level 31+)</q> | ||||||
| 	 */ | 	 */ | ||||||
| 	private static int getExpToNext(int level) { | 	private static int getExpToNext(int level) { | ||||||
| 		if (level > 30) return 9 * level - 158; | 		if (level > 30) return 9 * level - 158; | ||||||
| @@ -89,4 +89,7 @@ public class ExperienceUtil { | |||||||
| 		player.setExp((float) (levelAndExp - level)); | 		player.setExp((float) (levelAndExp - level)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	private ExperienceUtil() {} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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<String, World> gameWorld = new BiMap<>(); |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	public static World getOrLoadGameWorld(String world, Consumer<World> 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<World> 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; |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| } |  | ||||||
| @@ -1,12 +1,7 @@ | |||||||
| package fr.pandacube.lib.paper.util; | package fr.pandacube.lib.paper.util; | ||||||
|  |  | ||||||
| import java.util.EnumSet; | import fr.pandacube.lib.paper.PandaLibPaper; | ||||||
| import java.util.Set; | import fr.pandacube.lib.util.RandomUtil; | ||||||
| import java.util.concurrent.CompletableFuture; |  | ||||||
| import java.util.concurrent.atomic.AtomicInteger; |  | ||||||
| import java.util.concurrent.atomic.AtomicReference; |  | ||||||
| import java.util.function.Predicate; |  | ||||||
|  |  | ||||||
| import org.bukkit.Bukkit; | import org.bukkit.Bukkit; | ||||||
| import org.bukkit.Location; | import org.bukkit.Location; | ||||||
| import org.bukkit.Material; | import org.bukkit.Material; | ||||||
| @@ -17,20 +12,35 @@ import org.bukkit.block.Block; | |||||||
| import org.bukkit.block.BlockFace; | import org.bukkit.block.BlockFace; | ||||||
| import org.bukkit.scheduler.BukkitTask; | import org.bukkit.scheduler.BukkitTask; | ||||||
|  |  | ||||||
| import fr.pandacube.lib.util.RandomUtil; | import java.util.Set; | ||||||
| import fr.pandacube.lib.paper.PandaLibPaper; | 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 { | public class LocationUtil { | ||||||
| 	 |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets a concise {@link String} representation of a {@link Location}. | ||||||
|  | 	 * <p> | ||||||
|  | 	 * 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) { | 	public static String conciseToString(Location loc) { | ||||||
| 		String world = loc.getWorld() == null ? "null" : loc.getWorld().getName(); | 		String world = loc.getWorld() == null ? "null" : loc.getWorld().getName(); | ||||||
| 		return "(" + world + ", " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")"; | 		return "(" + world + ", " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")"; | ||||||
| 	} | 	} | ||||||
| 	/** | 	/** | ||||||
| 	 * Return a random secure location in the provided world, inside the current | 	 * Return a random secure location in the provided world, inside the current WorldBorder of the world. | ||||||
| 	 * WorldBorder. Will be on the surface, for non-nether world, or below the roof of the nether 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 w the world in which to pick a location. | ||||||
| 	 * @param extraSecureCheck provides extra checks to determine location security | 	 * @param extraSecureCheck provides extra checks to determine location security. | ||||||
|  | 	 * @return a future that will provide a random secure location. | ||||||
| 	 */ | 	 */ | ||||||
| 	public static CompletableFuture<Location> getRandomSecureLocation(World w, Predicate<Location> extraSecureCheck) { | 	public static CompletableFuture<Location> getRandomSecureLocation(World w, Predicate<Location> extraSecureCheck) { | ||||||
|  |  | ||||||
| @@ -48,6 +58,17 @@ public class LocationUtil { | |||||||
| 	 | 	 | ||||||
|  |  | ||||||
| 	private static final int maxTryBeforeCancelRandomLocation = 75; | 	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<Location> getRandomSecureLocation(World w, Location min, Location max, Predicate<Location> extraSecureCheck) { | 	public static CompletableFuture<Location> getRandomSecureLocation(World w, Location min, Location max, Predicate<Location> extraSecureCheck) { | ||||||
|  |  | ||||||
| 		CompletableFuture<Location> future = new CompletableFuture<>(); | 		CompletableFuture<Location> future = new CompletableFuture<>(); | ||||||
| @@ -94,12 +115,12 @@ public class LocationUtil { | |||||||
| 	 | 	 | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 *  | 	 * Try to get a secure location with the same X and Z coordinate as the | ||||||
| 	 * @param l the source location |  | ||||||
| 	 * @return a secure location with the same X and Z coordinate as the |  | ||||||
| 	 * provided location, but Y modified to ensure security for player | 	 * provided location, but Y modified to ensure security for player | ||||||
| 	 * who will be teleported to this location. | 	 * who will be teleported to this location. | ||||||
| 	 * May return null if it is impossible to find a secure 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) { | 	public static Location getSecureLocationOrNull(Location l) { | ||||||
| 		l = l.clone(); | 		l = l.clone(); | ||||||
| @@ -112,7 +133,13 @@ public class LocationUtil { | |||||||
| 		return currPosSafe(b) ? b.getLocation().add(0.5, 0, 0.5) : null; | 		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) { | 	public static boolean currPosSafe(Block b) { | ||||||
| 		return b.getY() >= b.getWorld().getMinHeight() + 1 && b.getY() <= b.getWorld().getMaxHeight() | 		return b.getY() >= b.getWorld().getMinHeight() + 1 && b.getY() <= b.getWorld().getMaxHeight() | ||||||
| 				&& isSecureFloor(b.getRelative(BlockFace.DOWN)) | 				&& isSecureFloor(b.getRelative(BlockFace.DOWN)) | ||||||
| @@ -120,10 +147,10 @@ public class LocationUtil { | |||||||
| 				&& isAir(b.getRelative(BlockFace.UP)); | 				&& isAir(b.getRelative(BlockFace.UP)); | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	public static boolean isAir(Block b) { return b.getType() == Material.AIR; } | 	private 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 isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); } | ||||||
| 	 | 	 | ||||||
| 	public static final Set<Material> dangerousBlocks = EnumSet.of( | 	private static final Set<Material> dangerousBlocks = Set.of( | ||||||
| 			Material.LAVA, | 			Material.LAVA, | ||||||
| 			Material.WATER, | 			Material.WATER, | ||||||
| 			Material.COBWEB, | 			Material.COBWEB, | ||||||
| @@ -137,21 +164,6 @@ public class LocationUtil { | |||||||
| 			Material.NETHER_PORTAL, | 			Material.NETHER_PORTAL, | ||||||
| 			Material.END_GATEWAY | 			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. | 	 * 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. | 	 * @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. | 	 * @throws IllegalArgumentException if the provided locations are not in the same world. | ||||||
| 	 */ | 	 */ | ||||||
| 	public static Location lerp(Location p0, Location p1, float c) { | 	public static Location lerp(Location p0, Location p1, float c) { | ||||||
| 		return p0.clone().add(p1.clone().subtract(p0).multiply(c)); | 		return p0.clone().add(p1.clone().subtract(p0).multiply(c)); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  |  | ||||||
|  | 	private LocationUtil() {} | ||||||
| 	 | 	 | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,13 +1,29 @@ | |||||||
| package fr.pandacube.lib.paper.util; | package fr.pandacube.lib.paper.util; | ||||||
|  |  | ||||||
| import org.bukkit.Material; | 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.Sign; | ||||||
| import org.bukkit.block.data.type.WallSign; | import org.bukkit.block.data.type.WallSign; | ||||||
|  |  | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Utility class around {@link Material}. | ||||||
|  |  */ | ||||||
| public class MaterialUtil { | public class MaterialUtil { | ||||||
| 	 |  | ||||||
|  | 	private static final Set<Class<? extends BlockData>> 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) { | 	public static boolean isSign(Material m) { | ||||||
| 		return WallSign.class.equals(m.data) || Sign.class.equals(m.data); | 		return signBlockDataTypes.contains(m.data); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	private MaterialUtil() {} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -140,6 +140,8 @@ public class ScoreboardUtil { | |||||||
| 		updateScoreboardSidebar(scBrd, title.get(), cmpLines); | 		updateScoreboardSidebar(scBrd, title.get(), cmpLines); | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	 |  | ||||||
|  |  | ||||||
|  | 	private ScoreboardUtil() {} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,40 +24,73 @@ import fr.pandacube.lib.chat.Chat; | |||||||
|  */ |  */ | ||||||
| public enum Skull { | public enum Skull { | ||||||
|  |  | ||||||
|  |     /** Standard skull of player MHF_ArrowLeft. */ | ||||||
|     ARROW_LEFT("MHF_ArrowLeft"), |     ARROW_LEFT("MHF_ArrowLeft"), | ||||||
|  |     /** Standard skull of player MHF_ArrowRight. */ | ||||||
|     ARROW_RIGHT("MHF_ArrowRight"), |     ARROW_RIGHT("MHF_ArrowRight"), | ||||||
|  |     /** Standard skull of player MHF_ArrowUp. */ | ||||||
|     ARROW_UP("MHF_ArrowUp"), |     ARROW_UP("MHF_ArrowUp"), | ||||||
|  |     /** Standard skull of player MHF_ArrowDown. */ | ||||||
|     ARROW_DOWN("MHF_ArrowDown"), |     ARROW_DOWN("MHF_ArrowDown"), | ||||||
|  |     /** Standard skull of player MHF_Question. */ | ||||||
|     QUESTION("MHF_Question"), |     QUESTION("MHF_Question"), | ||||||
|  |     /** Standard skull of player MHF_Exclamation. */ | ||||||
|     EXCLAMATION("MHF_Exclamation"), |     EXCLAMATION("MHF_Exclamation"), | ||||||
|  |     /** Standard skull of player FHG_Cam. */ | ||||||
|     CAMERA("FHG_Cam"), |     CAMERA("FHG_Cam"), | ||||||
|  |  | ||||||
|  |     /** Standard skull of player MHF_PigZombie. */ | ||||||
|     ZOMBIE_PIGMAN("MHF_PigZombie"), |     ZOMBIE_PIGMAN("MHF_PigZombie"), | ||||||
|  |     /** Standard skull of player MHF_Pig. */ | ||||||
|     PIG("MHF_Pig"), |     PIG("MHF_Pig"), | ||||||
|  |     /** Standard skull of player MHF_Sheep. */ | ||||||
|     SHEEP("MHF_Sheep"), |     SHEEP("MHF_Sheep"), | ||||||
|  |     /** Standard skull of player MHF_Blaze. */ | ||||||
|     BLAZE("MHF_Blaze"), |     BLAZE("MHF_Blaze"), | ||||||
|  |     /** Standard skull of player MHF_Chicken. */ | ||||||
|     CHICKEN("MHF_Chicken"), |     CHICKEN("MHF_Chicken"), | ||||||
|  |     /** Standard skull of player MHF_Cow. */ | ||||||
|     COW("MHF_Cow"), |     COW("MHF_Cow"), | ||||||
|  |     /** Standard skull of player MHF_Slime. */ | ||||||
|     SLIME("MHF_Slime"), |     SLIME("MHF_Slime"), | ||||||
|  |     /** Standard skull of player MHF_Spider. */ | ||||||
|     SPIDER("MHF_Spider"), |     SPIDER("MHF_Spider"), | ||||||
|  |     /** Standard skull of player MHF_Squid. */ | ||||||
|     SQUID("MHF_Squid"), |     SQUID("MHF_Squid"), | ||||||
|  |     /** Standard skull of player MHF_Villager. */ | ||||||
|     VILLAGER("MHF_Villager"), |     VILLAGER("MHF_Villager"), | ||||||
|  |     /** Standard skull of player MHF_Ocelot. */ | ||||||
|     OCELOT("MHF_Ocelot"), |     OCELOT("MHF_Ocelot"), | ||||||
|  |     /** Standard skull of player MHF_Herobrine. */ | ||||||
|     HEROBRINE("MHF_Herobrine"), |     HEROBRINE("MHF_Herobrine"), | ||||||
|  |     /** Standard skull of player MHF_LavaSlime. */ | ||||||
|     LAVA_SLIME("MHF_LavaSlime"), |     LAVA_SLIME("MHF_LavaSlime"), | ||||||
|  |     /** Standard skull of player MHF_MushroomCow. */ | ||||||
|     MOOSHROOM("MHF_MushroomCow"), |     MOOSHROOM("MHF_MushroomCow"), | ||||||
|  |     /** Standard skull of player MHF_Golem. */ | ||||||
|     GOLEM("MHF_Golem"), |     GOLEM("MHF_Golem"), | ||||||
|  |     /** Standard skull of player MHF_Ghast. */ | ||||||
|     GHAST("MHF_Ghast"), |     GHAST("MHF_Ghast"), | ||||||
|  |     /** Standard skull of player MHF_Enderman. */ | ||||||
|     ENDERMAN("MHF_Enderman"), |     ENDERMAN("MHF_Enderman"), | ||||||
|  |     /** Standard skull of player MHF_CaveSpider. */ | ||||||
|     CAVE_SPIDER("MHF_CaveSpider"), |     CAVE_SPIDER("MHF_CaveSpider"), | ||||||
|  |  | ||||||
|  |     /** Standard skull of player MHF_Cactus. */ | ||||||
|     CACTUS("MHF_Cactus"), |     CACTUS("MHF_Cactus"), | ||||||
|  |     /** Standard skull of player MHF_Cake. */ | ||||||
|     CAKE("MHF_Cake"), |     CAKE("MHF_Cake"), | ||||||
|  |     /** Standard skull of player MHF_Chest. */ | ||||||
|     CHEST("MHF_Chest"), |     CHEST("MHF_Chest"), | ||||||
|  |     /** Standard skull of player MHF_Melon. */ | ||||||
|     MELON("MHF_Melon"), |     MELON("MHF_Melon"), | ||||||
|  |     /** Standard skull of player MHF_OakLog. */ | ||||||
|     LOG("MHF_OakLog"), |     LOG("MHF_OakLog"), | ||||||
|  |     /** Standard skull of player MHF_Pumpkin. */ | ||||||
|     PUMPKIN("MHF_Pumpkin"), |     PUMPKIN("MHF_Pumpkin"), | ||||||
|  |     /** Standard skull of player MHF_TNT. */ | ||||||
|     TNT("MHF_TNT"), |     TNT("MHF_TNT"), | ||||||
|  |     /** Standard skull of player MHF_TNT2. */ | ||||||
|     DYNAMITE("MHF_TNT2"); |     DYNAMITE("MHF_TNT2"); | ||||||
|  |  | ||||||
|     private final String name; |     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. |      * 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 |      * @return item stack | ||||||
|      */ |      */ | ||||||
|     public ItemStack get(Chat displayName, List<Chat> lore) { |     public ItemStack get(Chat displayName, List<Chat> lore) { | ||||||
| @@ -89,6 +123,8 @@ public enum Skull { | |||||||
|      * Return a skull of a player based on their name. |      * Return a skull of a player based on their name. | ||||||
|      * |      * | ||||||
|      * @param name player's 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 |      * @return item stack | ||||||
|      */ |      */ | ||||||
| 	public static ItemStack getFromPlayerName(String name, Chat displayName, List<Chat> lore) { | 	public static ItemStack getFromPlayerName(String name, Chat displayName, List<Chat> lore) { | ||||||
| @@ -120,8 +156,7 @@ public enum Skull { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Return a skull that has a custom texture specified by url. |      * Return a skull that has a custom texture specified by url. | ||||||
|      * |      * @param url skin url. | ||||||
|      * @param url skin url |  | ||||||
|      * @return item stack |      * @return item stack | ||||||
|      */ |      */ | ||||||
|     public static ItemStack getFromSkinURL(String url) { |     public static ItemStack getFromSkinURL(String url) { | ||||||
| @@ -131,11 +166,13 @@ public enum Skull { | |||||||
|     /** |     /** | ||||||
|      * Return a skull that has a custom texture specified by url. |      * 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 |      * @return item stack | ||||||
|      */ |      */ | ||||||
|     public static ItemStack getFromSkinURL(String url, Chat name, List<Chat> lore) { |     public static ItemStack getFromSkinURL(String url, Chat displayName, List<Chat> lore) { | ||||||
|         return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", url).getBytes()), name, 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. |      * 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 |      * @return item stack | ||||||
|      */ |      */ | ||||||
|     public static ItemStack getFromBase64String(String str) { |     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. |      * 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 |      * @return item stack | ||||||
|      */ |      */ | ||||||
|     public static ItemStack getFromBase64String(String str, Chat displayName, List<Chat> lore) { |     public static ItemStack getFromBase64String(String str, Chat displayName, List<Chat> lore) { | ||||||
|   | |||||||
| @@ -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.paper.reflect.wrapper.craftbukkit.CraftServer; | ||||||
| import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; | import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; | ||||||
| @@ -36,5 +36,5 @@ public class PrimaryWorlds { | |||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 
 | 
 | ||||||
| 	 | 	private PrimaryWorlds() {} | ||||||
| } | } | ||||||
| @@ -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<String, World> loadedWorlds = new HashMap<>(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets a world based on the provided template world. | ||||||
|  | 	 * <p> | ||||||
|  | 	 * 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<World> 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<World> 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() {} | ||||||
|  | 	 | ||||||
|  | } | ||||||
| @@ -10,11 +10,18 @@ import java.util.List; | |||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Provides utility methods to manage and analyze world's directory. | ||||||
|  |  */ | ||||||
| public class WorldUtil { | 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) { | 	public static Environment determineEnvironment(String world) { | ||||||
| 		World bWorld = Bukkit.getWorld(world); | 		World bWorld = Bukkit.getWorld(world); | ||||||
| 		if (bWorld != null) { | 		if (bWorld != null) { | ||||||
| @@ -40,7 +47,12 @@ public class WorldUtil { | |||||||
| 		 | 		 | ||||||
| 		throw new IllegalStateException("Unable to determine the type of the world " + world + "."); | 		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<RegionCoord> getExistingRegions(String world) { | 	public static List<RegionCoord> getExistingRegions(String world) { | ||||||
| 		Environment env = determineEnvironment(world); | 		Environment env = determineEnvironment(world); | ||||||
|  |  | ||||||
| @@ -71,11 +83,22 @@ public class WorldUtil { | |||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	private static final List<String> REGION_DATA_FILES = Arrays.asList("entities", "poi", "region", "DIM-1", "DIM1"); | 	private static final List<String> 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<File> regionDataFolders(String world) { | 	public static List<File> regionDataFolders(String world) { | ||||||
| 		return onlyExisting(worldDir(world), REGION_DATA_FILES); | 		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<File> mapFiles(String world) { | 	public static List<File> mapFiles(String world) { | ||||||
| 		Pattern mapFilePattern = Pattern.compile("map_\\d+.dat"); | 		Pattern mapFilePattern = Pattern.compile("map_\\d+.dat"); | ||||||
| 		return List.of(dataDir(world).listFiles((dir, name) -> mapFilePattern.matcher(name).find() || "idcounts.dat".equals(name))); | 		return List.of(dataDir(world).listFiles((dir, name) -> mapFilePattern.matcher(name).find() || "idcounts.dat".equals(name))); | ||||||
| @@ -88,18 +111,35 @@ public class WorldUtil { | |||||||
| 				.toList(); | 				.toList(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets the directory of the provided world. | ||||||
|  | 	 * @param world the world. | ||||||
|  | 	 * @return the directory of the world. | ||||||
|  | 	 */ | ||||||
| 	public static File worldDir(String world) { | 	public static File worldDir(String world) { | ||||||
| 		return new File(Bukkit.getWorldContainer(), 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) { | 	public static File dataDir(String world) { | ||||||
| 		return new File(worldDir(world), "data"); | 		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) { | 	public static boolean isValidWorld(String world) { | ||||||
| 		File d = worldDir(world); | 		File d = worldDir(world); | ||||||
| 		return d.isDirectory() && new File(d, "level.dat").isFile();  | 		return d.isDirectory() && new File(d, "level.dat").isFile();  | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
|  |  | ||||||
|  | 	private WorldUtil() {} | ||||||
| 	 | 	 | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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. |  | ||||||
|  * <p> |  | ||||||
|  * 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") |  | ||||||
|  * <p> |  | ||||||
|  * 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<EnumSet<MinecraftVersion>, List<String>> 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<String> 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<MinecraftVersion> 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<MinecraftVersion> 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<MinecraftVersion> versions) { |  | ||||||
| 		return displayOptimizedListOfVersions(versions, "ou"); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Returns an optimized list of string representation of Minecraft version, that represent the provided list of |  | ||||||
| 	 * Minecraft version. |  | ||||||
| 	 * <p> |  | ||||||
| 	 * 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". |  | ||||||
| 	 * <p> |  | ||||||
| 	 * 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<String> getVersionsDisplayList(List<MinecraftVersion> vList) { |  | ||||||
| 		if (vList == null) |  | ||||||
| 			return new ArrayList<>(); |  | ||||||
| 		Set<MinecraftVersion> vSet = EnumSet.copyOf(vList); |  | ||||||
| 		 |  | ||||||
| 		List<String> ret = new ArrayList<>(); |  | ||||||
| 		 |  | ||||||
| 		for (int i = 0; i < values().length; i++) { |  | ||||||
| 			if (!vSet.contains(values()[i])) |  | ||||||
| 				continue; |  | ||||||
| 			 |  | ||||||
| 			EnumSet<MinecraftVersion> 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; |  | ||||||
| 		 |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
							
								
								
									
										5
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -123,11 +123,6 @@ | |||||||
|                 </executions> |                 </executions> | ||||||
|                 <configuration> |                 <configuration> | ||||||
|                     <tags> |                     <tags> | ||||||
|                         <tag> |  | ||||||
|                             <name>apiNote</name> |  | ||||||
|                             <placement>a</placement> |  | ||||||
|                             <head>API Note:</head> |  | ||||||
|                         </tag> |  | ||||||
|                         <tag> |                         <tag> | ||||||
|                             <name>implSpec</name> |                             <name>implSpec</name> | ||||||
|                             <placement>a</placement> |                             <placement>a</placement> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user