Big Javadoc update + cleaned/refactored some code
This commit is contained in:
parent
1925dd9b36
commit
3fe4a1b244
@ -5,27 +5,47 @@ import fr.pandacube.lib.paper.json.PaperJson;
|
||||
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Main class for pandalib-paper.
|
||||
*/
|
||||
public class PandaLibPaper {
|
||||
|
||||
private static Plugin plugin;
|
||||
|
||||
/**
|
||||
* Method to call in plugin's {@link Plugin#onLoad()} method.
|
||||
* @param plugin the plugin instance.
|
||||
*/
|
||||
public static void onLoad(Plugin plugin) {
|
||||
PandaLibPaper.plugin = plugin;
|
||||
PaperJson.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to call in plugin's {@link Plugin#onEnable()} method.
|
||||
*/
|
||||
public static void onEnable() {
|
||||
PerformanceAnalysisManager.getInstance(); // initialize
|
||||
ServerStopEvent.init();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Method to call in plugin's {@link Plugin#onDisable()} method.
|
||||
*/
|
||||
public static void disable() {
|
||||
PerformanceAnalysisManager.getInstance().cancelInternalBossBar();
|
||||
PerformanceAnalysisManager.getInstance().deinit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin instance.
|
||||
* @return the plugin instance provided with {@link #onLoad(Plugin)}.
|
||||
*/
|
||||
public static Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private PandaLibPaper() {}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,11 @@ import java.util.List;
|
||||
@SuppressWarnings("CanBeFinal")
|
||||
public class PaperBackupConfig {
|
||||
|
||||
/**
|
||||
* Creates a new Paper backup config.
|
||||
*/
|
||||
public PaperBackupConfig() {}
|
||||
|
||||
/**
|
||||
* Set to true to enable worlds backup.
|
||||
* Defaults to true.
|
||||
|
@ -130,12 +130,12 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
||||
private final Set<String> dirtyForSave = new HashSet<>();
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
void onWorldLoad(WorldLoadEvent event) {
|
||||
initWorldProcess(event.getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onWorldSave(WorldSaveEvent event) {
|
||||
void onWorldSave(WorldSaveEvent event) {
|
||||
if (event.getWorld().getLoadedChunks().length > 0
|
||||
|| dirtyForSave.contains(event.getWorld().getName())) {
|
||||
compressWorlds.get(event.getWorld().getName()).setDirtyAfterSave();
|
||||
@ -148,18 +148,18 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) {
|
||||
void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) {
|
||||
dirtyForSave.add(event.getFrom().getName());
|
||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
void onPlayerJoin(PlayerJoinEvent event) {
|
||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
void onPlayerQuit(PlayerQuitEvent event) {
|
||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,19 @@ import net.kyori.adventure.bossbar.BossBar.Color;
|
||||
import net.kyori.adventure.bossbar.BossBar.Overlay;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
/**
|
||||
* A backup process with specific logic around Paper server.
|
||||
*/
|
||||
public abstract class PaperBackupProcess extends BackupProcess {
|
||||
|
||||
|
||||
private BossBar bossBar;
|
||||
|
||||
/**
|
||||
* Instantiates a new backup process.
|
||||
* @param bm the associated backup manager.
|
||||
* @param id the process identifier.
|
||||
*/
|
||||
protected PaperBackupProcess(PaperBackupManager bm, String id) {
|
||||
super(bm, id);
|
||||
}
|
||||
|
@ -3,8 +3,15 @@ package fr.pandacube.lib.paper.backup;
|
||||
import java.io.File;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* A backup process with specific logic around Paper server working directory.
|
||||
*/
|
||||
public class PaperWorkdirProcess extends PaperBackupProcess {
|
||||
|
||||
/**
|
||||
* Instantiates a new backup process for the paper server working directory.
|
||||
* @param bm the associated backup manager.
|
||||
*/
|
||||
protected PaperWorkdirProcess(PaperBackupManager bm) {
|
||||
super(bm, "workdir");
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package fr.pandacube.lib.paper.backup;
|
||||
|
||||
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||
import fr.pandacube.lib.paper.scheduler.SchedulerUtil;
|
||||
import fr.pandacube.lib.paper.world.WorldUtil;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
|
||||
@ -11,14 +11,22 @@ import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A backup process with specific logic around Paper server world.
|
||||
*/
|
||||
public class PaperWorldProcess extends PaperBackupProcess {
|
||||
private final String worldName;
|
||||
|
||||
private boolean autoSave = true;
|
||||
|
||||
protected PaperWorldProcess(PaperBackupManager bm, final String n) {
|
||||
super(bm, "worlds/" + n);
|
||||
worldName = n;
|
||||
/**
|
||||
* Instantiates a new backup process for a world.
|
||||
* @param bm the associated backup manager.
|
||||
* @param worldName the name of the world.
|
||||
*/
|
||||
protected PaperWorldProcess(PaperBackupManager bm, final String worldName) {
|
||||
super(bm, "worlds/" + worldName);
|
||||
this.worldName = worldName;
|
||||
}
|
||||
|
||||
private World getWorld() {
|
||||
@ -62,11 +70,11 @@ public class PaperWorldProcess extends PaperBackupProcess {
|
||||
|
||||
public void displayNextSchedule() {
|
||||
if (hasNextScheduled()) {
|
||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is dirty. Next backup on "
|
||||
Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " is dirty. Next backup on "
|
||||
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext())));
|
||||
}
|
||||
else {
|
||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is clean. Next backup not scheduled.");
|
||||
Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " is clean. Next backup not scheduled.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +88,7 @@ public class PaperWorldProcess extends PaperBackupProcess {
|
||||
public void setDirtyAfterSave() {
|
||||
if (!isDirty()) { // don't set dirty if it is already
|
||||
setDirtySinceNow();
|
||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " was saved and is now dirty. Next backup on "
|
||||
Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " was saved and is now dirty. Next backup on "
|
||||
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG)
|
||||
.format(new Date(getNext()))
|
||||
);
|
||||
|
@ -57,10 +57,21 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
||||
|
||||
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() {
|
||||
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() {
|
||||
return vanillaPaperDispatcher == null ? null : vanillaPaperDispatcher.getRoot();
|
||||
}
|
||||
@ -135,6 +146,9 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
||||
*/
|
||||
protected final String[] aliases;
|
||||
|
||||
/**
|
||||
* The command description.
|
||||
*/
|
||||
protected final String description;
|
||||
|
||||
private final RegistrationPolicy registrationPolicy;
|
||||
@ -248,7 +262,9 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
||||
fixedNodes.add(originalNode);
|
||||
if (originalNode.getRedirect() != null) {
|
||||
try {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ReflectClass<CommandNode> cmdNodeClass = Reflect.ofClass(CommandNode.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
CommandNode<CommandSourceStack> unwrappedNode = (CommandNode<CommandSourceStack>) cmdNodeClass.field("unwrappedCached").getValue(originalNode);
|
||||
if (unwrappedNode != null) {
|
||||
cmdNodeClass.field("modifier").setValue(unwrappedNode, cmdNodeClass.field("modifier").getValue(originalNode));
|
||||
@ -333,6 +349,10 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the aliases that are actually registered in the server.
|
||||
* @return the actually registered aliases.
|
||||
*/
|
||||
protected Set<String> getRegisteredAliases() {
|
||||
return Set.copyOf(registeredAliases);
|
||||
}
|
||||
@ -379,12 +399,15 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isConsole(CommandSourceStack wrapper) {
|
||||
return isConsole(getCommandSender(wrapper));
|
||||
}
|
||||
@Override
|
||||
public boolean isPlayer(CommandSourceStack wrapper) {
|
||||
return isPlayer(getCommandSender(wrapper));
|
||||
}
|
||||
@Override
|
||||
public Predicate<CommandSourceStack> hasPermission(String 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.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 {
|
||||
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler list of the event.
|
||||
* @return the handler list of the event.
|
||||
*/
|
||||
@NotNull
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static boolean hasTriggered = false;
|
||||
private static boolean isInit = false;
|
||||
|
||||
/**
|
||||
* Register the event used to detect the server stop.
|
||||
*/
|
||||
public static void init() {
|
||||
if (isInit)
|
||||
return;
|
||||
|
||||
BukkitEvent.register(new Listener() {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
@ -45,7 +55,25 @@ public class ServerStopEvent extends ServerEvent {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
isInit = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private ServerStopEvent() {}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -38,12 +38,20 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
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) {
|
||||
this.defaultSlot = Math.max(0, Math.min(8, defaultSlot));
|
||||
|
||||
BukkitEvent.register(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables this hot bar.
|
||||
* @param clearPlayerMenuItems if the items of this hot bar should be removed from the players inventories.
|
||||
*/
|
||||
public void disable(boolean clearPlayerMenuItems) {
|
||||
removeAllPlayers(clearPlayerMenuItems);
|
||||
|
||||
@ -53,9 +61,10 @@ public class GUIHotBar implements Listener {
|
||||
/**
|
||||
* Add the item to this hot bar menu. if there is already players hooked to this hot bar, the item will be directly added to
|
||||
* their inventories.
|
||||
* @param i the item stack
|
||||
* @param i the item stack.
|
||||
* @param setter code executed to put the item in the inventory. Additionally, check for permission before doing the addition.
|
||||
* @param run the Runnable to run when the user right-click on the item in the hot bar.
|
||||
* @return itself for daisy-chaining.
|
||||
*/
|
||||
public GUIHotBar addItem(ItemStack i, BiConsumer<PlayerInventory, ItemStack> setter, Consumer<Player> run) {
|
||||
itemsAndSetters.put(i, setter);
|
||||
@ -71,6 +80,7 @@ public class GUIHotBar implements Listener {
|
||||
* Add the hot bar elements to this player, or update them if applicable.
|
||||
* <br>
|
||||
* The player is automatically removed when they quit. You can remove it before by calling {@link #removePlayer(Player, boolean)}.
|
||||
* @param p the player to add.
|
||||
*/
|
||||
public void addPlayer(Player p) {
|
||||
if (!currentPlayers.contains(p))
|
||||
@ -85,6 +95,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
/**
|
||||
* Detach this player from this hot bar manager and removes the managed items from the players inventory.
|
||||
* @param p the player to remove.
|
||||
*/
|
||||
public void removePlayer(Player p) {
|
||||
removePlayer(p, true);
|
||||
@ -92,6 +103,8 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
/**
|
||||
* Detach this player from this hot bar manager and optionally removes the managed items from the players inventory.
|
||||
* @param p the player to remove.
|
||||
* @param clearMenuItems if the items from this hot bar should be removed from the player inventory.
|
||||
*/
|
||||
public void removePlayer(Player p, boolean clearMenuItems) {
|
||||
if (!currentPlayers.contains(p))
|
||||
@ -107,17 +120,27 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells if the provided player is attached to this hot bar.
|
||||
* @param p the player to check.
|
||||
* @return true if the player is attached, false otherwise.
|
||||
*/
|
||||
public boolean containsPlayer(Player p) {
|
||||
return currentPlayers.contains(p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Detach all players from this hot bar.
|
||||
*/
|
||||
public void removeAllPlayers() {
|
||||
removeAllPlayers(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach all players from this hot bar.
|
||||
* @param clearMenuItems if the items from this hot bar should be removed from the player inventory.
|
||||
*/
|
||||
public void removeAllPlayers(boolean clearMenuItems) {
|
||||
for (Player p : new ArrayList<>(currentPlayers))
|
||||
removePlayer(p, clearMenuItems);
|
||||
@ -127,7 +150,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
|
||||
public void addItemToPlayer(Player p, ItemStack is) {
|
||||
private void addItemToPlayer(Player p, ItemStack is) {
|
||||
if (!itemsAndSetters.containsKey(is))
|
||||
throw new IllegalArgumentException("The provided ItemStack is not registered in this GUIHotBar");
|
||||
if (!currentPlayers.contains(p))
|
||||
@ -135,7 +158,7 @@ public class GUIHotBar implements Listener {
|
||||
itemsAndSetters.get(is).accept(p.getInventory(), is.clone());
|
||||
}
|
||||
|
||||
public void removeItemFromPlayer(Player p, ItemStack is) {
|
||||
private void removeItemFromPlayer(Player p, ItemStack is) {
|
||||
p.getInventory().remove(is);
|
||||
}
|
||||
|
||||
|
@ -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.Material;
|
||||
@ -13,12 +13,22 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Wrapper for an {@link Inventory}.
|
||||
* Can be overridden to add specific behaviour to some methods.
|
||||
*/
|
||||
public class InventoryWrapper implements Inventory {
|
||||
private final Inventory base;
|
||||
|
||||
/**
|
||||
* Creates a wrapper for the provided inventory.
|
||||
* @param base the wrapped inventory. Cannot be null.
|
||||
* @throws NullPointerException if the base inventory is null.
|
||||
*/
|
||||
public InventoryWrapper(Inventory base) {
|
||||
this.base = base;
|
||||
this.base = Objects.requireNonNull(base, "base inventory cannot be null.");
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
package fr.pandacube.lib.paper.inventory;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
@ -18,6 +18,9 @@ import java.util.function.Consumer;
|
||||
|
||||
import static fr.pandacube.lib.chat.ChatStatic.chatComponent;
|
||||
|
||||
/**
|
||||
* A builder for {@link ItemStack}.
|
||||
*/
|
||||
public class ItemStackBuilder {
|
||||
|
||||
/**
|
||||
@ -77,10 +80,22 @@ public class ItemStackBuilder {
|
||||
return (cachedMeta != null) ? cachedMeta : (cachedMeta = stack.getItemMeta());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the provided updater on the {@link ItemMeta} instance of the built stack.
|
||||
* @param metaUpdater the updater that will modify the meta.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder meta(Consumer<ItemMeta> metaUpdater) {
|
||||
return meta(metaUpdater, ItemMeta.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the provided updater on the {@link ItemMeta} instance of the built stack.
|
||||
* @param metaUpdater the updater that will modify the meta.
|
||||
* @param metaType the type of the meta instance.
|
||||
* @param <T> the type of item meta.
|
||||
* @return itself.
|
||||
*/
|
||||
public <T extends ItemMeta> ItemStackBuilder meta(Consumer<T> metaUpdater, Class<T> metaType) {
|
||||
stack.editMeta(metaType, m -> {
|
||||
metaUpdater.accept(m);
|
||||
@ -89,27 +104,51 @@ public class ItemStackBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount of the built stack.
|
||||
* @param a the new amount.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder amount(int a) {
|
||||
stack.setAmount(a);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display name of the item, directly passing to {@link ItemMeta#displayName(Component)}.
|
||||
* @param displayName the new display name. Can be null to unset.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder rawDisplayName(Component displayName) {
|
||||
return meta(m -> m.displayName(displayName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display name of the item, filtering to make default italic to false.
|
||||
* @param displayName the new display name. Can be null to unset.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder displayName(ComponentLike displayName) {
|
||||
if (displayName != null) {
|
||||
return rawDisplayName(Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent());
|
||||
}
|
||||
else
|
||||
return rawDisplayName(null);
|
||||
return rawDisplayName(displayName != null
|
||||
? Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent()
|
||||
: null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lore of the item, directly passing to {@link ItemMeta#lore(List)}.
|
||||
* @param lore the new lore. Can be null to unset.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder rawLore(List<Component> 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) {
|
||||
if (lore != null) {
|
||||
return rawLore(lore.stream()
|
||||
@ -120,6 +159,11 @@ public class ItemStackBuilder {
|
||||
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) {
|
||||
if (lores != null) {
|
||||
List<Component> baseLore = getOrInitMeta().lore();
|
||||
@ -136,6 +180,11 @@ public class ItemStackBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new lore lines to the existing lore of the item.
|
||||
* @param lores the added lore lines.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder addLoreAfter(ComponentLike... lores) {
|
||||
if (lores == null || lores.length == 0)
|
||||
return this;
|
||||
@ -143,69 +192,120 @@ public class ItemStackBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enchant the item.
|
||||
* Supports unsafe enchants.
|
||||
* @param enchantment the enchantment.
|
||||
* @param level the enchant level.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder enchant(Enchantment enchantment, int level) {
|
||||
return meta(m -> m.addEnchant(enchantment, level, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided flags to the item.
|
||||
* @param flags he flags to add.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder flags(ItemFlag... flags) {
|
||||
return flags(true, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes the provided flags to the item.
|
||||
* @param add true to add, false to remove.
|
||||
* @param flags he flags to add.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder flags(boolean add, ItemFlag... flags) {
|
||||
return add ? meta(m -> m.addItemFlags(flags))
|
||||
: meta(m -> m.removeItemFlags(flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the enchants from the tooltip of the item.
|
||||
* Will set the {@link ItemFlag#HIDE_ENCHANTS} flag of the item.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder hideEnchants() {
|
||||
return hideEnchants(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or unsets the {@link ItemFlag#HIDE_ENCHANTS} flag of the item.
|
||||
* @param hide true to hide, false to show.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder hideEnchants(boolean hide) {
|
||||
return flags(hide, ItemFlag.HIDE_ENCHANTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the attributes from the tooltip of the item.
|
||||
* Will set the {@link ItemFlag#HIDE_ATTRIBUTES} flag of the item.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder hideAttributes() {
|
||||
return hideAttributes(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or unsets the {@link ItemFlag#HIDE_ATTRIBUTES} flag of the item.
|
||||
* @param hide true to hide, false to show.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder hideAttributes(boolean hide) {
|
||||
return flags(hide, ItemFlag.HIDE_ATTRIBUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the enchantment glint to the item, event if it's not enchant.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder fakeEnchant() {
|
||||
return fakeEnchant(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enchantment glint override to the item.
|
||||
* @param apply true to enforce the enchantment glint, false to set to default.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder fakeEnchant(boolean apply) {
|
||||
if (apply) {
|
||||
enchant(Enchantment.UNBREAKING, 1);
|
||||
return hideEnchants();
|
||||
}
|
||||
return this;
|
||||
return meta(m -> m.setEnchantmentGlintOverride(apply ? true : null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this item as unbreakable.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder unbreakable() {
|
||||
return unbreakable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the unbreakable status of this item.
|
||||
* @param unbreakable the unbreakable status.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder unbreakable(boolean unbreakable) {
|
||||
return meta(m -> m.setUnbreakable(unbreakable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the damage value of this item.
|
||||
* @param d the new damage value.
|
||||
* @return itself.
|
||||
*/
|
||||
public ItemStackBuilder damage(int d) {
|
||||
return meta(m -> m.setDamage(d), Damageable.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Build the {@link ItemStack}.
|
||||
* @return the build item stack.
|
||||
*/
|
||||
public ItemStack build() {
|
||||
return stack;
|
||||
}
|
@ -14,4 +14,7 @@ public class PaperJson {
|
||||
Json.registerTypeAdapterFactory(ItemStackAdapter.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.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
@ -44,10 +43,17 @@ import static fr.pandacube.lib.chat.ChatStatic.infoText;
|
||||
import static fr.pandacube.lib.chat.ChatStatic.successText;
|
||||
import static fr.pandacube.lib.chat.ChatStatic.text;
|
||||
|
||||
/**
|
||||
* Various tools to supervise the JVM RAM and the CPU usage of the main server thread.
|
||||
*/
|
||||
public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
private static PerformanceAnalysisManager instance;
|
||||
|
||||
/**
|
||||
* Gets the instance of {@link PerformanceAnalysisManager}.
|
||||
* @return the instance of {@link PerformanceAnalysisManager}.
|
||||
*/
|
||||
public static synchronized PerformanceAnalysisManager getInstance() {
|
||||
if (instance == null)
|
||||
instance = new PerformanceAnalysisManager();
|
||||
@ -76,14 +82,22 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
private final LinkedList<Long> interTPSDurations = new LinkedList<>();
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The boss bar that shows in real time the CPU performance of the main server thread.
|
||||
*/
|
||||
public final AutoUpdatedBossBar tpsBar;
|
||||
|
||||
/**
|
||||
* The boss bar that shows in real time the JVM RAM usage.
|
||||
*/
|
||||
public final AutoUpdatedBossBar memoryBar;
|
||||
private final List<Player> barPlayers = 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()
|
||||
.add(0, NamedTextColor.BLACK)
|
||||
.add(1, NamedTextColor.DARK_RED)
|
||||
@ -94,23 +108,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
.add(21, PandaTheme.CHAT_GREEN_1_NORMAL)
|
||||
.add(26, NamedTextColor.BLUE);
|
||||
|
||||
|
||||
public final ChatColorGradient tps10sGradient = new ChatColorGradient()
|
||||
.add(0, NamedTextColor.DARK_RED)
|
||||
.add(5, NamedTextColor.RED)
|
||||
.add(10, NamedTextColor.GOLD)
|
||||
.add(14, NamedTextColor.YELLOW)
|
||||
.add(19, PandaTheme.CHAT_GREEN_1_NORMAL);
|
||||
|
||||
|
||||
public final ChatColorGradient tps1mGradient = new ChatColorGradient()
|
||||
.add(0, NamedTextColor.DARK_RED)
|
||||
.add(8, NamedTextColor.RED)
|
||||
.add(14, NamedTextColor.GOLD)
|
||||
.add(17, NamedTextColor.YELLOW)
|
||||
.add(19, PandaTheme.CHAT_GREEN_1_NORMAL);
|
||||
|
||||
public final ChatColorGradient memoryUsageGradient = new ChatColorGradient()
|
||||
private final ChatColorGradient memoryUsageGradient = new ChatColorGradient()
|
||||
.add(.60f, PandaTheme.CHAT_GREEN_1_NORMAL)
|
||||
.add(.70f, NamedTextColor.YELLOW)
|
||||
.add(.80f, NamedTextColor.GOLD)
|
||||
@ -132,10 +130,19 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the provided players is seeing the performance boss bars.
|
||||
* @param p the player to verify.
|
||||
* @return true if the provided players is seeing the performance boss bars, false otherwise.
|
||||
*/
|
||||
public boolean barsContainsPlayer(Player p) {
|
||||
return barPlayers.contains(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the performance boss bars to the provided player.
|
||||
* @param p the player.
|
||||
*/
|
||||
public synchronized void addPlayerToBars(Player p) {
|
||||
barPlayers.add(p);
|
||||
p.showBossBar(tpsBar.bar);
|
||||
@ -144,6 +151,10 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
p.showBossBar(bar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the performance boss bars from the provided player.
|
||||
* @param p the player.
|
||||
*/
|
||||
public synchronized void removePlayerToBars(Player p) {
|
||||
p.hideBossBar(tpsBar.bar);
|
||||
p.hideBossBar(memoryBar.bar);
|
||||
@ -152,6 +163,10 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
barPlayers.remove(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an additional boss bar to the players currently seeing the performance ones.
|
||||
* @param bar the new bar to show.
|
||||
*/
|
||||
public synchronized void addBossBar(BossBar bar) {
|
||||
if (relatedBossBars.contains(bar))
|
||||
return;
|
||||
@ -160,6 +175,10 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
p.showBossBar(bar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides an additional boss bar from the players currently seeing the performance ones.
|
||||
* @param bar the additional bar to hide.
|
||||
*/
|
||||
public synchronized void removeBossBar(BossBar bar) {
|
||||
if (!relatedBossBars.contains(bar))
|
||||
return;
|
||||
@ -168,7 +187,10 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
p.hideBossBar(bar);
|
||||
}
|
||||
|
||||
public synchronized void cancelInternalBossBar() {
|
||||
/**
|
||||
* De-initialize the performance analyzer.
|
||||
*/
|
||||
public synchronized void deinit() {
|
||||
tpsBar.cancel();
|
||||
memoryBar.cancel();
|
||||
}
|
||||
@ -178,7 +200,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public synchronized void onTickStart(ServerTickStartEvent event) {
|
||||
synchronized void onTickStart(ServerTickStartEvent event) {
|
||||
tickStartNanoTime = System.nanoTime();
|
||||
tickStartCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0;
|
||||
|
||||
@ -186,7 +208,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public synchronized void onTickEnd(ServerTickEndEvent event) {
|
||||
synchronized void onTickEnd(ServerTickEndEvent event) {
|
||||
tickEndNanoTime = System.nanoTime();
|
||||
long tickEndCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0;
|
||||
|
||||
@ -213,7 +235,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
void onPlayerJoin(PlayerJoinEvent event) {
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer> playerManager = (AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer>) AbstractPlayerManager.getInstance();
|
||||
@ -233,7 +255,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
void onPlayerQuit(PlayerQuitEvent event) {
|
||||
removePlayerToBars(event.getPlayer());
|
||||
}
|
||||
|
||||
@ -376,6 +398,10 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
private Chat alteredTPSTitle = null;
|
||||
|
||||
/**
|
||||
* Temporary change the title of the TPS boss bar.
|
||||
* @param title the title override. null to restore to the normal TPS title.
|
||||
*/
|
||||
public synchronized void setAlteredTPSTitle(Chat title) {
|
||||
alteredTPSTitle = title;
|
||||
}
|
||||
@ -386,17 +412,19 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
|
||||
|
||||
|
||||
// special case where the getTPS method always returns a whole number when retrieving the TPS for 1 sec
|
||||
/**
|
||||
* Gets the number of tick in the last second.
|
||||
* @return the number of tick in the last second.
|
||||
*/
|
||||
public int getTPS1s() {
|
||||
return (int) getTPS(1_000);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param nbTicks number of ticks when the avg value is computed from history
|
||||
* @return the avg number of TPS in the interval
|
||||
*/
|
||||
public synchronized float getAvgNano(List<Long> data, int nbTicks) {
|
||||
private synchronized float getAvgNano(List<Long> data, int nbTicks) {
|
||||
if (data.isEmpty())
|
||||
return 0;
|
||||
|
||||
@ -410,7 +438,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the average number of tick per second in the n last milliseconds.
|
||||
* @param nbMillis number of milliseconds when the avg TPS is computed from history
|
||||
* @return the avg number of TPS in the interval
|
||||
*/
|
||||
@ -431,6 +459,10 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the history of TPS performance.
|
||||
* @return an array of TPS values from the last minute. The value at 0 is in the last second (current second on the clock - 1), the value at index 1 is now - 2, ...
|
||||
*/
|
||||
public synchronized int[] getTPSHistory() {
|
||||
int[] history = new int[60];
|
||||
|
||||
@ -448,15 +480,22 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current server's target tick rate.
|
||||
* Usually 20 but the server can be configured to tick at a different rate.
|
||||
* @return the current server's target tick rate.
|
||||
*/
|
||||
public static int getTargetTickRate() {
|
||||
return Math.round(Bukkit.getServerTickManager().getTickRate());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Runs the garbage collector on the server.
|
||||
* Depending on the server load and the used memory, this can freeze the server for a second.
|
||||
* @param sender the command sender that triggers the garbase collector. Can be null (the report will be sent to the
|
||||
* console)
|
||||
*/
|
||||
public static void gc(CommandSender sender) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
long alloc1 = Runtime.getRuntime().totalMemory();
|
||||
@ -478,7 +517,7 @@ public class PerformanceAnalysisManager implements Listener {
|
||||
Log.info(finalMessage.getLegacyText());
|
||||
}
|
||||
|
||||
public static String displayRound10(double val) {
|
||||
private static String displayRound10(double val) {
|
||||
long v = (long) Math.ceil(val * 10);
|
||||
return "" + (v / 10f);
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
package fr.pandacube.lib.paper.players;
|
||||
|
||||
import fr.pandacube.lib.paper.reflect.util.PrimaryWorlds;
|
||||
import fr.pandacube.lib.paper.world.PrimaryWorlds;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.NbtIo;
|
||||
import fr.pandacube.lib.paper.util.PlayerDataWrapper;
|
||||
import fr.pandacube.lib.paper.util.PlayerDataWrapper.PlayerDataLoadException;
|
||||
import fr.pandacube.lib.paper.players.PlayerDataWrapper.PlayerDataLoadException;
|
||||
import fr.pandacube.lib.paper.world.WorldUtil;
|
||||
import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
|
||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||
@ -116,26 +115,31 @@ public interface PaperOffPlayer extends AbstractOffPlayer {
|
||||
* Player config
|
||||
*/
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default String getConfig(String key) throws Exception {
|
||||
return PaperPlayerConfigStorage.get(getUniqueId(), key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default String getConfig(String key, String deflt) throws Exception {
|
||||
return PaperPlayerConfigStorage.get(getUniqueId(), key, deflt);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default void setConfig(String key, String value) throws Exception {
|
||||
PaperPlayerConfigStorage.set(getUniqueId(), key, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default void updateConfig(String key, String deflt, UnaryOperator<String> updater) throws Exception {
|
||||
PaperPlayerConfigStorage.update(getUniqueId(), key, deflt, updater);
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||
@Override
|
||||
default void unsetConfig(String key) throws Exception {
|
||||
PaperPlayerConfigStorage.unset(getUniqueId(), key);
|
||||
|
@ -3,7 +3,7 @@ package fr.pandacube.lib.paper.players;
|
||||
import com.destroystokyo.paper.ClientOption;
|
||||
import com.destroystokyo.paper.ClientOption.ChatVisibility;
|
||||
import com.destroystokyo.paper.SkinParts;
|
||||
import fr.pandacube.lib.paper.players.PlayerNonPersistentConfig.Expiration;
|
||||
import fr.pandacube.lib.paper.players.PlayerNonPersistentConfig.ExpirationPolicy;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftPlayer;
|
||||
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
|
||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||
@ -304,18 +304,39 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer
|
||||
* Player config
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the non-persistent value of the provided configuration key of this player.
|
||||
* @param key the configuration key.
|
||||
* @return the value of the configuration, or null if the configuration is not set.
|
||||
*/
|
||||
default String getNonPersistentConfig(String key) {
|
||||
return PlayerNonPersistentConfig.getData(getUniqueId(), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the non-persistent value of the provided configuration key of this player.
|
||||
* @param key the configuration key.
|
||||
* @param deflt the default value if the configuration is not set.
|
||||
* @return the value of the configuration, or {@code deflt} if the configuration is not set.
|
||||
*/
|
||||
default String getNonPersistentConfig(String key, String deflt) {
|
||||
return PlayerNonPersistentConfig.getData(getUniqueId(), key);
|
||||
}
|
||||
|
||||
default void setNonPersistentConfig(String key, String value, Expiration expiration) {
|
||||
PlayerNonPersistentConfig.setData(getUniqueId(), key, value, expiration);
|
||||
/**
|
||||
* Sets the non-persistent value of the provided configuration key for this player.
|
||||
* @param key the configuration key to set.
|
||||
* @param value the new value.
|
||||
* @param expirationPolicy the expiration policy.
|
||||
*/
|
||||
default void setNonPersistentConfig(String key, String value, ExpirationPolicy expirationPolicy) {
|
||||
PlayerNonPersistentConfig.setData(getUniqueId(), key, value, expirationPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets the non-persistent value of the provided configuration key for this player.
|
||||
* @param key the configuration key to update.
|
||||
*/
|
||||
default void unsetNonPersistentConfig(String key) {
|
||||
PlayerNonPersistentConfig.unsetData(getUniqueId(), key);
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ import java.util.UUID;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Provides rudimentary player data storage using a file in the plugin configuration.
|
||||
* The file is loaded on the first access, and is auto-saved if needed every 30 seconds.
|
||||
*/
|
||||
public class PaperPlayerConfigStorage {
|
||||
|
||||
static final File storageFile = new File(PandaLibPaper.getPlugin().getDataFolder(), "playerdata.yml");
|
||||
@ -77,6 +81,8 @@ public class PaperPlayerConfigStorage {
|
||||
|
||||
|
||||
private static synchronized void save() {
|
||||
if (!changed)
|
||||
return;
|
||||
YamlConfiguration config = new YamlConfiguration();
|
||||
for (UUID pId : playerSortedData.keySet()) {
|
||||
String pIdStr = pId.toString();
|
||||
@ -109,11 +115,17 @@ public class PaperPlayerConfigStorage {
|
||||
}
|
||||
|
||||
|
||||
public static synchronized void set(UUID player, String key, String newValue) {
|
||||
/**
|
||||
* Sets the value of the provided configuration key for the player.
|
||||
* @param player the player.
|
||||
* @param key the configuration key to set.
|
||||
* @param value the new value.
|
||||
*/
|
||||
public static synchronized void set(UUID player, String key, String value) {
|
||||
initIfNeeded();
|
||||
ConfigKey cKey = new ConfigKey(player, key);
|
||||
ConfigEntry e = data.get(cKey);
|
||||
if (e != null && newValue == null) { // delete
|
||||
if (e != null && value == null) { // delete
|
||||
data.remove(cKey);
|
||||
if (playerSortedData.containsKey(player))
|
||||
playerSortedData.get(player).remove(e);
|
||||
@ -121,50 +133,91 @@ public class PaperPlayerConfigStorage {
|
||||
keySortedData.get(key).remove(e);
|
||||
changed = true;
|
||||
}
|
||||
else if (e == null && newValue != null) { // create
|
||||
create(player, key, newValue);
|
||||
else if (e == null && value != null) { // create
|
||||
create(player, key, value);
|
||||
changed = true;
|
||||
}
|
||||
else if (e != null && !newValue.equals(e.value)) { // update
|
||||
e.value = newValue;
|
||||
else if (e != null && !value.equals(e.value)) { // update
|
||||
e.value = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the provided configuration key of the player.
|
||||
* @param player the player.
|
||||
* @param key the configuration key.
|
||||
* @return the value of the configuration, or null if the configuration is not set.
|
||||
*/
|
||||
public static synchronized String get(UUID player, String key) {
|
||||
initIfNeeded();
|
||||
ConfigEntry e = data.get(new ConfigKey(player, key));
|
||||
return e != null ? e.value : null;
|
||||
}
|
||||
|
||||
public static String get(UUID p, String k, String deflt) {
|
||||
String value = get(p, k);
|
||||
/**
|
||||
* Gets the value of the provided configuration key of the player.
|
||||
* @param player the player.
|
||||
* @param key the configuration key.
|
||||
* @param deflt the default value if the configuration is not set.
|
||||
* @return the value of the configuration, or {@code deflt} if the configuration is not set.
|
||||
*/
|
||||
public static String get(UUID player, String key, String deflt) {
|
||||
String value = get(player, key);
|
||||
return value == null ? deflt : value;
|
||||
}
|
||||
|
||||
public static synchronized void update(UUID p, String k, String deflt, UnaryOperator<String> updater) {
|
||||
String oldValue = get(p, k, deflt);
|
||||
set(p, k, updater.apply(oldValue));
|
||||
/**
|
||||
* Updates the value of the provided configuration key for the player, using the provided updater.
|
||||
* @param player the player.
|
||||
* @param key the configuration key to update.
|
||||
* @param deflt the default value to use if the configuration is not already set.
|
||||
* @param updater the unary operator to use to update th value. The old value is used as the parameter of the updater,
|
||||
* and it returns the new value of the configuration.
|
||||
*/
|
||||
public static synchronized void update(UUID player, String key, String deflt, UnaryOperator<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();
|
||||
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) {
|
||||
initIfNeeded();
|
||||
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();
|
||||
return getAllWithKeys(k).stream()
|
||||
return getAllWithKeys(key).stream()
|
||||
.filter(c -> c.value.equals(v))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
@ -173,25 +226,46 @@ public class PaperPlayerConfigStorage {
|
||||
|
||||
private record ConfigKey(UUID playerId, String key) { }
|
||||
|
||||
/**
|
||||
* Class holding the playerId-key-value triplet.
|
||||
*/
|
||||
public static class ConfigEntry {
|
||||
private final UUID playerId;
|
||||
private final String key;
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConfigEntry}.
|
||||
* @param playerId the player id.
|
||||
* @param key the key.
|
||||
* @param value the value.
|
||||
*/
|
||||
private ConfigEntry(UUID playerId, String key, String value) {
|
||||
this.playerId = playerId;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player id.
|
||||
* @return the player id.
|
||||
*/
|
||||
public UUID getPlayerId() {
|
||||
return playerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config key.
|
||||
* @return the config key.
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config value.
|
||||
* @return the config value.
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
@ -208,4 +282,7 @@ public class PaperPlayerConfigStorage {
|
||||
&& Objects.equals(key, o.key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private PaperPlayerConfigStorage() {}
|
||||
}
|
||||
|
@ -1,39 +1,33 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
package fr.pandacube.lib.paper.players;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import fr.pandacube.lib.paper.inventory.DummyPlayerInventory;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftItemStack;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.ListTag;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.Tag;
|
||||
import fr.pandacube.lib.paper.util.ExperienceUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
|
||||
/**
|
||||
* A wrapper to easily manipulate vanilla player data.
|
||||
* A wrapper to easily manipulate the player data file.
|
||||
*
|
||||
* @param data The data as they are stored in the player file.
|
||||
* @param data The NBT data structure as it is stored in the player file.
|
||||
*/
|
||||
public record PlayerDataWrapper(CompoundTag data) {
|
||||
|
||||
/**
|
||||
* Creates a new wrapper for the provided player data.
|
||||
* @param data the data to wrap.
|
||||
* @param data the NBT data to wrap.
|
||||
*/
|
||||
public PlayerDataWrapper(CompoundTag data) {
|
||||
this.data = data == null ? new CompoundTag() : data;
|
||||
@ -41,8 +35,9 @@ public record PlayerDataWrapper(CompoundTag data) {
|
||||
|
||||
/**
|
||||
* Gets a snapshot of the inventory of this player.
|
||||
* If modified, call the {@link #setInventory(PlayerInventory)} to update the data.
|
||||
* @return the player inventory
|
||||
* If the inventory is modified, the {@link #setInventory(PlayerInventory)} method should be called to update the
|
||||
* data in this wrapper.
|
||||
* @return the player inventory.
|
||||
*/
|
||||
public PlayerInventory getInventory() {
|
||||
return new DummyPlayerInventory(
|
||||
@ -67,6 +62,11 @@ public record PlayerDataWrapper(CompoundTag data) {
|
||||
throw new IllegalArgumentException("Unrecognized NBT player inventory slot " + nbtSlot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the player inventory to the content of the provided one.
|
||||
* The internal data of this wrapper will be updated.
|
||||
* @param inv the inventory to store in this player data in place of the old one.
|
||||
*/
|
||||
public void setInventory(PlayerInventory inv) {
|
||||
setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot);
|
||||
setHeldItemSlot(inv.getHeldItemSlot());
|
||||
@ -86,10 +86,21 @@ public record PlayerDataWrapper(CompoundTag data) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a snapshot of the enderchest of this player.
|
||||
* If the enderchest is modified, the {@link #setEnderChest(Inventory)} method should be called to update the
|
||||
* data in this wrapper.
|
||||
* @return the player enderchest.
|
||||
*/
|
||||
public Inventory getEnderChest() {
|
||||
return getBukkitInventory("EnderItems", InventoryType.ENDER_CHEST, IntUnaryOperator.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the player enderchest to the content of the provided one.
|
||||
* The internal data of this wrapper will be updated.
|
||||
* @param inv the enderchest content to store in this player data in place of the old enderchest.
|
||||
*/
|
||||
public void setEnderChest(Inventory inv) {
|
||||
setBukkitInventory("EnderItems", inv, IntUnaryOperator.identity());
|
||||
}
|
||||
@ -171,6 +182,10 @@ public record PlayerDataWrapper(CompoundTag data) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the score of the player, as stored in the data with the key {@code Score}.
|
||||
* @return the value of Score.
|
||||
*/
|
||||
public int getScore() {
|
||||
if (!data.contains("Score"))
|
||||
return 0;
|
||||
@ -178,17 +193,29 @@ public record PlayerDataWrapper(CompoundTag data) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the score of the player, as stored in the data with the key {@code Score}.
|
||||
* @param score the value of Score to set.
|
||||
*/
|
||||
public void setScore(int score) {
|
||||
data.putInt("Score", score);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the total experience of the player, as stored in the data with the key {@code XpTotal}.
|
||||
* @return the value of XpTotal.
|
||||
*/
|
||||
public int getTotalExperience() {
|
||||
if (!data.contains("XpTotal"))
|
||||
return 0;
|
||||
return data.getInt("XpTotal");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total experience of the player, as stored in the data with the key {@code XpTotal}.
|
||||
* @param xp the value of XpTotal to set.
|
||||
*/
|
||||
public void setTotalExperience(int xp) {
|
||||
data.putInt("XpTotal", xp);
|
||||
double levelAndExp = ExperienceUtil.getLevelFromExp(xp);
|
||||
@ -198,178 +225,11 @@ public record PlayerDataWrapper(CompoundTag data) {
|
||||
data.putFloat("XpP", (float) expProgress);
|
||||
}
|
||||
|
||||
|
||||
private static class DummyPlayerInventory extends InventoryWrapper implements PlayerInventory {
|
||||
|
||||
private int heldItemSlot;
|
||||
|
||||
public DummyPlayerInventory(Inventory base, int heldItemSlot) {
|
||||
super(base);
|
||||
this.heldItemSlot = heldItemSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemStack @NotNull [] getStorageContents() {
|
||||
return Arrays.copyOfRange(getContents(), 0, 36);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemStack @NotNull [] getArmorContents() {
|
||||
return Arrays.copyOfRange(getContents(), 36, 40);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setArmorContents(@Nullable ItemStack[] items) {
|
||||
this.setSlots(items, 36, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemStack @NotNull [] getExtraContents() {
|
||||
return Arrays.copyOfRange(getContents(), 40, getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtraContents(@Nullable ItemStack[] items) {
|
||||
this.setSlots(items, 40, getSize() - 40);
|
||||
}
|
||||
|
||||
private void setSlots(ItemStack[] items, int baseSlot, int length) {
|
||||
if (items == null) {
|
||||
items = new ItemStack[length];
|
||||
}
|
||||
Preconditions.checkArgument(items.length <= length, "items.length must be < %s", length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (i >= items.length) {
|
||||
this.setItem(baseSlot + i, null);
|
||||
} else {
|
||||
this.setItem(baseSlot + i, items[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getHelmet() {
|
||||
return getItem(39);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelmet(@Nullable ItemStack helmet) {
|
||||
setItem(39, helmet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getChestplate() {
|
||||
return getItem(38);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChestplate(@Nullable ItemStack chestplate) {
|
||||
setItem(38, chestplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getLeggings() {
|
||||
return getItem(37);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLeggings(@Nullable ItemStack leggings) {
|
||||
setItem(37, leggings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getBoots() {
|
||||
return getItem(36);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoots(@Nullable ItemStack boots) {
|
||||
setItem(36, boots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(EquipmentSlot slot, ItemStack item) {
|
||||
Preconditions.checkArgument(slot != null, "slot must not be null");
|
||||
|
||||
switch (slot) {
|
||||
case HAND -> this.setItemInMainHand(item);
|
||||
case OFF_HAND -> this.setItemInOffHand(item);
|
||||
case FEET -> this.setBoots(item);
|
||||
case LEGS -> this.setLeggings(item);
|
||||
case CHEST -> this.setChestplate(item);
|
||||
case HEAD -> this.setHelmet(item);
|
||||
default -> throw new IllegalArgumentException("Not implemented. This is a bug");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItem(@NotNull EquipmentSlot slot) {
|
||||
return switch (slot) {
|
||||
case HAND -> this.getItemInMainHand();
|
||||
case OFF_HAND -> this.getItemInOffHand();
|
||||
case FEET -> Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(Material.AIR));
|
||||
case LEGS -> Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(Material.AIR));
|
||||
case CHEST -> Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(Material.AIR));
|
||||
case HEAD -> Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(Material.AIR));
|
||||
case BODY -> new ItemStack(Material.AIR); // for horses/wolves armor
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInMainHand() {
|
||||
return Objects.requireNonNullElse(getItem(heldItemSlot), new ItemStack(Material.AIR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInMainHand(@Nullable ItemStack item) {
|
||||
setItem(heldItemSlot, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInOffHand() {
|
||||
return Objects.requireNonNullElse(getItem(40), new ItemStack(Material.AIR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInOffHand(@Nullable ItemStack item) {
|
||||
setItem(40, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ItemStack getItemInHand() {
|
||||
return getItemInMainHand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItemInHand(@Nullable ItemStack stack) {
|
||||
setItemInMainHand(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeldItemSlot() {
|
||||
return heldItemSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeldItemSlot(int slot) {
|
||||
if (slot < 0 || slot > 8)
|
||||
throw new IllegalArgumentException("Slot is not between 0 and 8 inclusive");
|
||||
heldItemSlot = slot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable HumanEntity getHolder() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Thrown to indicate that an error occurred while loading the data of the player from the file.
|
||||
*/
|
||||
public static class PlayerDataLoadException extends RuntimeException {
|
||||
public PlayerDataLoadException(String playerName, UUID playerId, Throwable cause) {
|
||||
/* package */ PlayerDataLoadException(String playerName, UUID playerId, Throwable cause) {
|
||||
super("Unable to load data of player " + playerName + " (" + playerId + ")", cause);
|
||||
}
|
||||
}
|
@ -12,6 +12,9 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Handles the player related configuration that is not persisted to disk.
|
||||
*/
|
||||
public class PlayerNonPersistentConfig {
|
||||
private static final Map<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"),
|
||||
new ConfigEntry(Objects.requireNonNull(value, "value"),
|
||||
Objects.requireNonNull(expiration, "expiration")
|
||||
Objects.requireNonNull(expirationPolicy, "expiration")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static void unsetData(UUID playerId, String key) {
|
||||
data.getOrDefault(Objects.requireNonNull(playerId, "playerId"), new HashMap<>())
|
||||
/**
|
||||
* Unsets the value of the provided configuration key for the player.
|
||||
* @param player the player.
|
||||
* @param key the configuration key to update.
|
||||
*/
|
||||
public static void unsetData(UUID player, String key) {
|
||||
data.getOrDefault(Objects.requireNonNull(player, "playerId"), new HashMap<>())
|
||||
.remove(Objects.requireNonNull(key, "key"));
|
||||
}
|
||||
|
||||
public static String getData(UUID playerId, String key) {
|
||||
Map<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"));
|
||||
if (ce == null)
|
||||
return null;
|
||||
if (!ce.expiration.valid(playerId, key)) {
|
||||
if (!ce.expirationPolicy.valid(player, key)) {
|
||||
playerData.remove(key);
|
||||
return null;
|
||||
}
|
||||
return ce.value;
|
||||
}
|
||||
|
||||
public static boolean isDataSet(UUID playerId, String key) {
|
||||
return getData(playerId, key) != null;
|
||||
/**
|
||||
* Tells if the provided config key is set for the player.
|
||||
* @param player the player.
|
||||
* @param key the configuration key.
|
||||
* @return true if the value is set, false otherwise.
|
||||
*/
|
||||
public static boolean isDataSet(UUID player, String key) {
|
||||
return getData(player, key) != null;
|
||||
}
|
||||
|
||||
|
||||
@ -63,34 +90,76 @@ public class PlayerNonPersistentConfig {
|
||||
|
||||
|
||||
|
||||
private record ConfigEntry(String value, Expiration expiration) { }
|
||||
private record ConfigEntry(String value, ExpirationPolicy expirationPolicy) { }
|
||||
|
||||
|
||||
/**
|
||||
* Super class for all expiration policies.
|
||||
*/
|
||||
public static abstract class ExpirationPolicy {
|
||||
/**
|
||||
* Creates an expiration policy.
|
||||
*/
|
||||
public ExpirationPolicy() {}
|
||||
|
||||
|
||||
public static abstract class Expiration {
|
||||
/**
|
||||
* Tests if the associated configuration is still valid (not expired).
|
||||
* @param player the player.
|
||||
* @param key the configuration key.
|
||||
* @return true if the associated configuration is still valid, false otherwise.
|
||||
*/
|
||||
abstract boolean valid(UUID player, String key);
|
||||
}
|
||||
|
||||
public static class ExpiresLogout extends Expiration {
|
||||
/**
|
||||
* Expiration policy for a config that expires when the player logs out.
|
||||
*/
|
||||
public static class ExpiresLogout extends ExpirationPolicy {
|
||||
/**
|
||||
* Creates a logout expiration policy.
|
||||
*/
|
||||
public ExpiresLogout() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean valid(UUID player, String key) {
|
||||
return Bukkit.getPlayer(player) != null; // should not be call if player reconnects because it is removed on player quit
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExpiresTick extends Expiration {
|
||||
/**
|
||||
* Expiration policy for a config that expires after a certain amount of game tick.
|
||||
*/
|
||||
public static class ExpiresTick extends ExpirationPolicy {
|
||||
final long expirationTick;
|
||||
|
||||
/**
|
||||
* Creates a delay expiration policy.
|
||||
* @param expirationDelayTick the number of tick after which the config will expire. If 0, will expire immediately ; 1 to expire on the next tick.
|
||||
*/
|
||||
public ExpiresTick(long expirationDelayTick) {
|
||||
expirationTick = tick + expirationDelayTick;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean valid(UUID player, String key) {
|
||||
return tick < expirationTick;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExpiresServerStop extends Expiration {
|
||||
/**
|
||||
* Expiration policy for a config that expires when the server stops.
|
||||
*/
|
||||
public static class ExpiresServerStop extends ExpirationPolicy {
|
||||
/**
|
||||
* Creates a server stop expiration policy.
|
||||
*/
|
||||
public ExpiresServerStop() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean valid(UUID player, String key) {
|
||||
return true;
|
||||
}
|
||||
@ -103,7 +172,7 @@ public class PlayerNonPersistentConfig {
|
||||
|
||||
|
||||
private static class ConfigListeners implements Listener {
|
||||
public ConfigListeners() {
|
||||
private ConfigListeners() {
|
||||
Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin());
|
||||
}
|
||||
|
||||
@ -111,7 +180,7 @@ public class PlayerNonPersistentConfig {
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
data.getOrDefault(event.getPlayer().getUniqueId(), new HashMap<>())
|
||||
.entrySet()
|
||||
.removeIf(e -> e.getValue().expiration instanceof ExpiresLogout);
|
||||
.removeIf(e -> e.getValue().expirationPolicy instanceof ExpiresLogout);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@ -119,4 +188,9 @@ public class PlayerNonPersistentConfig {
|
||||
tick++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private PlayerNonPersistentConfig() {}
|
||||
}
|
||||
|
@ -23,4 +23,6 @@ public class OBCReflect {
|
||||
return Reflect.ofClass(CRAFTBUKKIT_PACKAGE + "." + obcClass);
|
||||
}
|
||||
|
||||
private OBCReflect() { }
|
||||
|
||||
}
|
||||
|
@ -226,4 +226,6 @@ public class PandalibPaperReflect {
|
||||
thAcc.throwCaught();
|
||||
|
||||
}
|
||||
|
||||
private PandalibPaperReflect() {}
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
package fr.pandacube.lib.paper.reflect.util;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.chat.ChatConfig.PandaTheme;
|
||||
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftWorld;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ChunkMap;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerLevel;
|
||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.util.ProgressListener;
|
||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
|
||||
/**
|
||||
* Provides static methods to save worlds presumably in a better way than Bukkit provides (better flushing, more released RAM).
|
||||
*/
|
||||
public class WorldSaveUtil {
|
||||
|
||||
private static ChunkMap getChunkMap(World w) {
|
||||
return ReflectWrapper.wrapTyped(w, CraftWorld.class).getHandle().getChunkSource().chunkMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the provided world using the NMS {@link ServerLevel#save(ProgressListener, boolean, boolean)} method.
|
||||
* @param w the world to save.
|
||||
*/
|
||||
public static void nmsSaveFlush(World w) {
|
||||
PerformanceAnalysisManager.getInstance().setAlteredTPSTitle(
|
||||
Chat.text("Sauvegarde map ").color(PandaTheme.CHAT_BROWN_2_SAT).thenData(w.getName()).thenText(" ...")
|
||||
@ -28,8 +31,13 @@ public class WorldSaveUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the loaded worlds, using {@link #nmsSaveFlush(World)}.
|
||||
*/
|
||||
public static void nmsSaveAllFlush() {
|
||||
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;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.kyori.adventure.bossbar.BossBar.Color;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@ -14,16 +16,22 @@ import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.util.log.Log;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.kyori.adventure.bossbar.BossBar.Color;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* A {@link BossBar} capable of automatically updating itself by registering a {@link BukkitTask} or using a {@link Timer}.
|
||||
*/
|
||||
public class AutoUpdatedBossBar implements Listener {
|
||||
|
||||
/**
|
||||
* The boss bar itself.
|
||||
*/
|
||||
public final BossBar bar;
|
||||
/**
|
||||
* the function executed to update the boss bar.
|
||||
*/
|
||||
public final BarUpdater updater;
|
||||
|
||||
private Timer timer = null;
|
||||
@ -31,9 +39,14 @@ public class AutoUpdatedBossBar implements Listener {
|
||||
|
||||
private boolean scheduled = false;
|
||||
|
||||
private boolean followPlayerList = false;
|
||||
private LoginLogoutListener followPlayerList = 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) {
|
||||
this.bar = bar;
|
||||
this.updater = updater;
|
||||
@ -66,10 +79,9 @@ public class AutoUpdatedBossBar implements Listener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the update of this boss bar with synchronisation with the main Thread of the
|
||||
* current Minecraft server (follow the tick count progress).
|
||||
* Schedule the update of this boss bar with synchronisation with the ticking of this Minecraft server.
|
||||
* The underlying method called is {@link BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)}.
|
||||
* The updater is executed by the Server Thread.
|
||||
* The updater is executed by the main Server Thread.
|
||||
* @param tickDelay number of server tick before running the first update of this boss bar
|
||||
* @param tickPeriod number of server tick between each call of the updater
|
||||
*/
|
||||
@ -92,45 +104,53 @@ public class AutoUpdatedBossBar implements Listener {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
playerCondition = condition;
|
||||
if (followPlayerList)
|
||||
if (followPlayerList != null)
|
||||
return;
|
||||
followPlayerList = true;
|
||||
BukkitEvent.register(this);
|
||||
Bukkit.getServer().getOnlinePlayers().forEach(p -> onPlayerJoin(new PlayerJoinEvent(p, Component.text(""))));
|
||||
followPlayerList = new LoginLogoutListener();
|
||||
BukkitEvent.register(followPlayerList);
|
||||
Bukkit.getServer().getOnlinePlayers().forEach(p -> followPlayerList.onPlayerJoin(new PlayerJoinEvent(p, Component.text(""))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the auto-update on player join and quit.
|
||||
*/
|
||||
public synchronized void unfollowPlayerList() {
|
||||
if (!followPlayerList)
|
||||
if (followPlayerList == null)
|
||||
return;
|
||||
followPlayerList = false;
|
||||
playerCondition = null;
|
||||
PlayerJoinEvent.getHandlerList().unregister(this);
|
||||
PlayerQuitEvent.getHandlerList().unregister(this);
|
||||
BukkitEvent.unregister(followPlayerList);
|
||||
followPlayerList = null;
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.MONITOR)
|
||||
public synchronized void onPlayerJoin(PlayerJoinEvent event) {
|
||||
if (!followPlayerList)
|
||||
return;
|
||||
if (playerCondition != null && !playerCondition.test(event.getPlayer()))
|
||||
return;
|
||||
synchronized (bar) {
|
||||
event.getPlayer().showBossBar(bar);
|
||||
private class LoginLogoutListener implements Listener {
|
||||
|
||||
@EventHandler(priority=EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
if (playerCondition != null && !playerCondition.test(event.getPlayer()))
|
||||
return;
|
||||
synchronized (bar) {
|
||||
event.getPlayer().showBossBar(bar);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.HIGH)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
synchronized (bar) {
|
||||
event.getPlayer().hideBossBar(bar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.HIGH)
|
||||
public synchronized void onPlayerQuit(PlayerQuitEvent event) {
|
||||
if (!followPlayerList)
|
||||
return;
|
||||
synchronized (bar) {
|
||||
event.getPlayer().hideBossBar(bar);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this boss bar from all players.
|
||||
*/
|
||||
public void removeAll() {
|
||||
synchronized (bar) {
|
||||
for (Player p : Bukkit.getOnlinePlayers())
|
||||
@ -138,7 +158,9 @@ public class AutoUpdatedBossBar implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancel any auto-updating of this boss bar.
|
||||
*/
|
||||
public synchronized void cancel() {
|
||||
if (!scheduled)
|
||||
return;
|
||||
@ -151,13 +173,19 @@ public class AutoUpdatedBossBar implements Listener {
|
||||
bukkitTask.cancel();
|
||||
bukkitTask = null;
|
||||
}
|
||||
|
||||
unfollowPlayerList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Functional interface taking an instance of {@link AutoUpdatedBossBar} to update it. Returns nothing.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BarUpdater {
|
||||
/**
|
||||
* Updates the boss bar.
|
||||
* @param bar the auto-updated boss bar instance.
|
||||
*/
|
||||
void update(AutoUpdatedBossBar bar);
|
||||
}
|
||||
|
||||
@ -165,6 +193,7 @@ public class AutoUpdatedBossBar implements Listener {
|
||||
|
||||
/**
|
||||
* Utility method to update the title of the boss bar without unnecessary packet.
|
||||
* @param title the new title.
|
||||
*/
|
||||
public void setTitle(Chat title) {
|
||||
synchronized (bar) {
|
||||
@ -174,6 +203,7 @@ public class AutoUpdatedBossBar implements Listener {
|
||||
|
||||
/**
|
||||
* Utility method to update the color of the boss bar without unnecessary packet.
|
||||
* @param color the new color.
|
||||
*/
|
||||
public void setColor(Color color) {
|
||||
synchronized (bar) {
|
||||
@ -183,6 +213,7 @@ public class AutoUpdatedBossBar implements Listener {
|
||||
|
||||
/**
|
||||
* Utility method to update the progress of the boss bar without unnecessary packet.
|
||||
* @param progress the new progress value.
|
||||
*/
|
||||
public void setProgress(double progress) {
|
||||
synchronized (bar) {
|
||||
|
@ -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,37 +16,90 @@ import org.bukkit.scheduler.BukkitTask;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import fr.pandacube.lib.reflect.Reflect;
|
||||
|
||||
/**
|
||||
* Utility class to more concisely handle Bukkit Events
|
||||
*/
|
||||
public class BukkitEvent {
|
||||
|
||||
/**
|
||||
* Register a single event executor.
|
||||
* <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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
Bukkit.getPluginManager().registerEvent(eventClass, eventExecutor, priority, eventExecutor, PandaLibPaper.getPlugin(), ignoreCancelled);
|
||||
return eventExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener.
|
||||
* This is equivalent to calling {@code Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin());}
|
||||
* @param l the listener.
|
||||
*/
|
||||
public static void register(Listener l) {
|
||||
Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Unregister a listener
|
||||
* @param listener the listener to unregister.
|
||||
*/
|
||||
public static void unregister(Listener listener) {
|
||||
HandlerList.unregisterAll(listener);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Lists all existing subclasses of {@link Event}.
|
||||
* @return a list of all existing subclasses of {@link Event}.
|
||||
*/
|
||||
public static List<Class<? extends Event>> getAllEventClasses() {
|
||||
List<Class<? extends Event>> classes = Reflect.ofClass(Event.class).getAllSubclasses(false);
|
||||
classes.removeIf(e -> getHandlerList(e) == null);
|
||||
@ -54,7 +107,11 @@ public class BukkitEvent {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the handlerList of the provided Event class.
|
||||
* @param type the event class.
|
||||
* @return the handlerList.
|
||||
*/
|
||||
// method retrieved from OB.plugin.SimplePluginManager#getEventListeners
|
||||
public static HandlerList getHandlerList(Class<? extends Event> type) {
|
||||
try {
|
||||
@ -83,9 +140,16 @@ public class BukkitEvent {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* The event handler.
|
||||
* @param event the event.
|
||||
*/
|
||||
void onEvent(E event);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -99,12 +163,17 @@ public class BukkitEvent {
|
||||
* Abstract implementation of {@link EventListener} that ensure as good as it can,
|
||||
* that it is the last listener called to handle the event.
|
||||
*
|
||||
* @param <E> the type of the event
|
||||
* @param <E> the type of the event.
|
||||
*/
|
||||
public static abstract class EnforcedLastListener<E extends Event> implements EventListener<E> {
|
||||
private final Class<E> eventClass;
|
||||
private final boolean ignoreCancelled;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EnforcedLastListener}.
|
||||
* @param eventClass the event to listen.
|
||||
* @param ignoreCancelled whether to pass cancelled events or not.
|
||||
*/
|
||||
public EnforcedLastListener(Class<E> eventClass, boolean ignoreCancelled) {
|
||||
this.eventClass = eventClass;
|
||||
this.ignoreCancelled = ignoreCancelled;
|
||||
@ -145,4 +214,7 @@ public class BukkitEvent {
|
||||
}
|
||||
|
||||
|
||||
private BukkitEvent() {}
|
||||
|
||||
|
||||
}
|
||||
|
@ -3,8 +3,13 @@ package fr.pandacube.lib.paper.util;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.DyeColor;
|
||||
|
||||
/**
|
||||
* Color related utility class.
|
||||
*/
|
||||
public class ColorUtil {
|
||||
|
||||
/*
|
||||
@ -13,9 +18,10 @@ public class ColorUtil {
|
||||
private static final List<Color> rainbowColors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Get a rainbow color
|
||||
* Gets a rainbow color.
|
||||
* @param variation from 0 (include) to 1 (exclude).
|
||||
* 0 is red, 1/6 is yellow, 2/6 is green, 3/6 is cyan, 4/6 is blue, 5/6 is magenta
|
||||
* @return the computed rainbow color.
|
||||
*/
|
||||
public static Color getRainbowColor(double variation) {
|
||||
synchronized (rainbowColors) {
|
||||
@ -37,13 +43,39 @@ public class ColorUtil {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*
|
||||
* "The formulas for figuring out how many experience orbs you need to
|
||||
* <p>
|
||||
* <q>The formulas for figuring out how many experience orbs you need to
|
||||
* get to the next level are as follows:
|
||||
* Experience Required = 2[Current Level] + 7 (at levels 0-15)
|
||||
* 5[Current Level] - 38 (at levels 16-30)
|
||||
* 9[Current Level] - 158 (at level 31+)"
|
||||
* 9[Current Level] - 158 (at level 31+)</q>
|
||||
*/
|
||||
private static int getExpToNext(int level) {
|
||||
if (level > 30) return 9 * level - 158;
|
||||
@ -89,4 +89,7 @@ public class ExperienceUtil {
|
||||
player.setExp((float) (levelAndExp - level));
|
||||
}
|
||||
|
||||
|
||||
private ExperienceUtil() {}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import fr.pandacube.lib.util.RandomUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
@ -17,20 +12,35 @@ import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import fr.pandacube.lib.util.RandomUtil;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Utility class related to {@link Location}.
|
||||
*/
|
||||
public class LocationUtil {
|
||||
|
||||
/**
|
||||
* Gets a concise {@link String} representation of a {@link Location}.
|
||||
* <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) {
|
||||
String world = loc.getWorld() == null ? "null" : loc.getWorld().getName();
|
||||
return "(" + world + ", " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")";
|
||||
}
|
||||
/**
|
||||
* Return a random secure location in the provided world, inside the current
|
||||
* WorldBorder. Will be on the surface, for non-nether world, or below the roof of the nether world
|
||||
* @param w the world in which to pick a location
|
||||
* @param extraSecureCheck provides extra checks to determine location security
|
||||
* Return a random secure location in the provided world, inside the current WorldBorder of the world.
|
||||
* Will be on the surface, for non-nether world, or below the roof of the nether world.
|
||||
* @param w the world in which to pick a location.
|
||||
* @param extraSecureCheck provides extra checks to determine location security.
|
||||
* @return a future that will provide a random secure location.
|
||||
*/
|
||||
public static CompletableFuture<Location> getRandomSecureLocation(World w, Predicate<Location> extraSecureCheck) {
|
||||
|
||||
@ -48,6 +58,17 @@ public class LocationUtil {
|
||||
|
||||
|
||||
private static final int maxTryBeforeCancelRandomLocation = 75;
|
||||
|
||||
/**
|
||||
* Return a random secure location in the provided world, inside the bounding box defined by min and max.
|
||||
* Will be on the surface (the height limits of the bounding box are ignored), for non-nether world, or below the
|
||||
* roof of the nether world.
|
||||
* @param w the world in which to pick a location.
|
||||
* @param min the min of the bounding box.
|
||||
* @param max the max of the bounding box.
|
||||
* @param extraSecureCheck provides extra checks to determine location security.
|
||||
* @return a future that will provide a random secure location.
|
||||
*/
|
||||
public static CompletableFuture<Location> getRandomSecureLocation(World w, Location min, Location max, Predicate<Location> extraSecureCheck) {
|
||||
|
||||
CompletableFuture<Location> future = new CompletableFuture<>();
|
||||
@ -94,12 +115,12 @@ public class LocationUtil {
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param l the source location
|
||||
* @return a secure location with the same X and Z coordinate as the
|
||||
* Try to get a secure location with the same X and Z coordinate as the
|
||||
* provided location, but Y modified to ensure security for player
|
||||
* who will be teleported to this location.
|
||||
* May return null if it is impossible to find a secure location.
|
||||
* @param l the source location
|
||||
* @return a secure location, or null if not found around the provided location.
|
||||
*/
|
||||
public static Location getSecureLocationOrNull(Location l) {
|
||||
l = l.clone();
|
||||
@ -113,6 +134,12 @@ public class LocationUtil {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the provided block is a safe block to spawn/be teleported in.
|
||||
* More specifically, this block and its block above is air, and the block below is a non-lethal solid block.
|
||||
* @param b the block to test.
|
||||
* @return true if the provided block is a safe block to spawn/be teleported in, false otherwise.
|
||||
*/
|
||||
public static boolean currPosSafe(Block b) {
|
||||
return b.getY() >= b.getWorld().getMinHeight() + 1 && b.getY() <= b.getWorld().getMaxHeight()
|
||||
&& isSecureFloor(b.getRelative(BlockFace.DOWN))
|
||||
@ -120,10 +147,10 @@ public class LocationUtil {
|
||||
&& isAir(b.getRelative(BlockFace.UP));
|
||||
}
|
||||
|
||||
public static boolean isAir(Block b) { return b.getType() == Material.AIR; }
|
||||
public static boolean isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); }
|
||||
private static boolean isAir(Block b) { return b.getType() == Material.AIR; }
|
||||
private static boolean isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); }
|
||||
|
||||
public static final Set<Material> dangerousBlocks = EnumSet.of(
|
||||
private static final Set<Material> dangerousBlocks = Set.of(
|
||||
Material.LAVA,
|
||||
Material.WATER,
|
||||
Material.COBWEB,
|
||||
@ -142,25 +169,12 @@ public class LocationUtil {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return a new location based on the linear interpolation between p0 and p1, according to the value c.
|
||||
* @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. Other finite numbers are allowed, but the returned location won't be part of the {@code [p0;p1]} segment.
|
||||
* @param p0 the first location.
|
||||
* @param p1 the second location.
|
||||
* @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. All finite numbers are allowed.
|
||||
* @return The location, linearly interpolated between p0 and p1 with the value c. The yaw and pitch in the returned location are those of p0.
|
||||
* @throws IllegalArgumentException if the provided locations are not in the same world.
|
||||
*/
|
||||
@ -169,5 +183,7 @@ public class LocationUtil {
|
||||
}
|
||||
|
||||
|
||||
private LocationUtil() {}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,29 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.HangingSign;
|
||||
import org.bukkit.block.data.type.Sign;
|
||||
import org.bukkit.block.data.type.WallSign;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utility class around {@link Material}.
|
||||
*/
|
||||
public class MaterialUtil {
|
||||
|
||||
private static final Set<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) {
|
||||
return WallSign.class.equals(m.data) || Sign.class.equals(m.data);
|
||||
return signBlockDataTypes.contains(m.data);
|
||||
}
|
||||
|
||||
private MaterialUtil() {}
|
||||
|
||||
}
|
||||
|
@ -142,4 +142,6 @@ public class ScoreboardUtil {
|
||||
|
||||
|
||||
|
||||
private ScoreboardUtil() {}
|
||||
|
||||
}
|
||||
|
@ -24,40 +24,73 @@ import fr.pandacube.lib.chat.Chat;
|
||||
*/
|
||||
public enum Skull {
|
||||
|
||||
/** Standard skull of player MHF_ArrowLeft. */
|
||||
ARROW_LEFT("MHF_ArrowLeft"),
|
||||
/** Standard skull of player MHF_ArrowRight. */
|
||||
ARROW_RIGHT("MHF_ArrowRight"),
|
||||
/** Standard skull of player MHF_ArrowUp. */
|
||||
ARROW_UP("MHF_ArrowUp"),
|
||||
/** Standard skull of player MHF_ArrowDown. */
|
||||
ARROW_DOWN("MHF_ArrowDown"),
|
||||
/** Standard skull of player MHF_Question. */
|
||||
QUESTION("MHF_Question"),
|
||||
/** Standard skull of player MHF_Exclamation. */
|
||||
EXCLAMATION("MHF_Exclamation"),
|
||||
/** Standard skull of player FHG_Cam. */
|
||||
CAMERA("FHG_Cam"),
|
||||
|
||||
/** Standard skull of player MHF_PigZombie. */
|
||||
ZOMBIE_PIGMAN("MHF_PigZombie"),
|
||||
/** Standard skull of player MHF_Pig. */
|
||||
PIG("MHF_Pig"),
|
||||
/** Standard skull of player MHF_Sheep. */
|
||||
SHEEP("MHF_Sheep"),
|
||||
/** Standard skull of player MHF_Blaze. */
|
||||
BLAZE("MHF_Blaze"),
|
||||
/** Standard skull of player MHF_Chicken. */
|
||||
CHICKEN("MHF_Chicken"),
|
||||
/** Standard skull of player MHF_Cow. */
|
||||
COW("MHF_Cow"),
|
||||
/** Standard skull of player MHF_Slime. */
|
||||
SLIME("MHF_Slime"),
|
||||
/** Standard skull of player MHF_Spider. */
|
||||
SPIDER("MHF_Spider"),
|
||||
/** Standard skull of player MHF_Squid. */
|
||||
SQUID("MHF_Squid"),
|
||||
/** Standard skull of player MHF_Villager. */
|
||||
VILLAGER("MHF_Villager"),
|
||||
/** Standard skull of player MHF_Ocelot. */
|
||||
OCELOT("MHF_Ocelot"),
|
||||
/** Standard skull of player MHF_Herobrine. */
|
||||
HEROBRINE("MHF_Herobrine"),
|
||||
/** Standard skull of player MHF_LavaSlime. */
|
||||
LAVA_SLIME("MHF_LavaSlime"),
|
||||
/** Standard skull of player MHF_MushroomCow. */
|
||||
MOOSHROOM("MHF_MushroomCow"),
|
||||
/** Standard skull of player MHF_Golem. */
|
||||
GOLEM("MHF_Golem"),
|
||||
/** Standard skull of player MHF_Ghast. */
|
||||
GHAST("MHF_Ghast"),
|
||||
/** Standard skull of player MHF_Enderman. */
|
||||
ENDERMAN("MHF_Enderman"),
|
||||
/** Standard skull of player MHF_CaveSpider. */
|
||||
CAVE_SPIDER("MHF_CaveSpider"),
|
||||
|
||||
/** Standard skull of player MHF_Cactus. */
|
||||
CACTUS("MHF_Cactus"),
|
||||
/** Standard skull of player MHF_Cake. */
|
||||
CAKE("MHF_Cake"),
|
||||
/** Standard skull of player MHF_Chest. */
|
||||
CHEST("MHF_Chest"),
|
||||
/** Standard skull of player MHF_Melon. */
|
||||
MELON("MHF_Melon"),
|
||||
/** Standard skull of player MHF_OakLog. */
|
||||
LOG("MHF_OakLog"),
|
||||
/** Standard skull of player MHF_Pumpkin. */
|
||||
PUMPKIN("MHF_Pumpkin"),
|
||||
/** Standard skull of player MHF_TNT. */
|
||||
TNT("MHF_TNT"),
|
||||
/** Standard skull of player MHF_TNT2. */
|
||||
DYNAMITE("MHF_TNT2");
|
||||
|
||||
private final String name;
|
||||
@ -76,7 +109,8 @@ public enum Skull {
|
||||
}
|
||||
/**
|
||||
* Return the item based on this Skull enum, with the provided display name and lore.
|
||||
*
|
||||
* @param displayName the display name to add to the returned skull.
|
||||
* @param lore the lore to add to the returned skull.
|
||||
* @return item stack
|
||||
*/
|
||||
public ItemStack get(Chat displayName, List<Chat> lore) {
|
||||
@ -89,6 +123,8 @@ public enum Skull {
|
||||
* Return a skull of a player based on their name.
|
||||
*
|
||||
* @param name player's name
|
||||
* @param displayName the display name to add to the returned skull.
|
||||
* @param lore the lore to add to the returned skull.
|
||||
* @return item stack
|
||||
*/
|
||||
public static ItemStack getFromPlayerName(String name, Chat displayName, List<Chat> lore) {
|
||||
@ -120,8 +156,7 @@ public enum Skull {
|
||||
|
||||
/**
|
||||
* Return a skull that has a custom texture specified by url.
|
||||
*
|
||||
* @param url skin url
|
||||
* @param url skin url.
|
||||
* @return item stack
|
||||
*/
|
||||
public static ItemStack getFromSkinURL(String url) {
|
||||
@ -131,11 +166,13 @@ public enum Skull {
|
||||
/**
|
||||
* Return a skull that has a custom texture specified by url.
|
||||
*
|
||||
* @param url the skin full url
|
||||
* @param url the skin full url.
|
||||
* @param displayName the display name to add to the returned skull.
|
||||
* @param lore the lore to add to the returned skull.
|
||||
* @return item stack
|
||||
*/
|
||||
public static ItemStack getFromSkinURL(String url, Chat name, List<Chat> lore) {
|
||||
return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", url).getBytes()), name, lore);
|
||||
public static ItemStack getFromSkinURL(String url, Chat displayName, List<Chat> lore) {
|
||||
return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", url).getBytes()), displayName, lore);
|
||||
}
|
||||
|
||||
|
||||
@ -148,7 +185,7 @@ public enum Skull {
|
||||
/**
|
||||
* Return a skull that has a custom texture specified by a base64 String.
|
||||
*
|
||||
* @param str the base64 string from game profile information
|
||||
* @param str the base64 string from game profile information.
|
||||
* @return item stack
|
||||
*/
|
||||
public static ItemStack getFromBase64String(String str) {
|
||||
@ -159,7 +196,9 @@ public enum Skull {
|
||||
/**
|
||||
* Return a skull that has a custom texture specified by a base64 String.
|
||||
*
|
||||
* @param str the base64 string from game profile information
|
||||
* @param str the base64 string from game profile information.
|
||||
* @param displayName the display name to add to the returned skull.
|
||||
* @param lore the lore to add to the returned skull.
|
||||
* @return item stack
|
||||
*/
|
||||
public static ItemStack getFromBase64String(String str, Chat displayName, List<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.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.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Provides utility methods to manage and analyze world's directory.
|
||||
*/
|
||||
public class WorldUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Determine the {@link Environment} of the provided world.
|
||||
* @param world the name of the world.
|
||||
* @return the {@link Environment}.
|
||||
* @throws IllegalStateException if the provided world is not valid (cannot determine its environment)
|
||||
*/
|
||||
public static Environment determineEnvironment(String world) {
|
||||
World bWorld = Bukkit.getWorld(world);
|
||||
if (bWorld != null) {
|
||||
@ -41,6 +48,11 @@ public class WorldUtil {
|
||||
throw new IllegalStateException("Unable to determine the type of the world " + world + ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of all the regions of the provided world, based on the name of the region files.
|
||||
* @param world the world.
|
||||
* @return a {@link List} of {@link RegionCoord}.
|
||||
*/
|
||||
public static List<RegionCoord> getExistingRegions(String world) {
|
||||
Environment env = determineEnvironment(world);
|
||||
|
||||
@ -72,10 +84,21 @@ public class WorldUtil {
|
||||
|
||||
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) {
|
||||
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) {
|
||||
Pattern mapFilePattern = Pattern.compile("map_\\d+.dat");
|
||||
return List.of(dataDir(world).listFiles((dir, name) -> mapFilePattern.matcher(name).find() || "idcounts.dat".equals(name)));
|
||||
@ -88,18 +111,35 @@ public class WorldUtil {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory of the provided world.
|
||||
* @param world the world.
|
||||
* @return the directory of the world.
|
||||
*/
|
||||
public static File worldDir(String world) {
|
||||
return new File(Bukkit.getWorldContainer(), world);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data directory of the provided world.
|
||||
* @param world the world.
|
||||
* @return the data directory of the world.
|
||||
*/
|
||||
public static File dataDir(String world) {
|
||||
return new File(worldDir(world), "data");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the provided world is a valid one (has a directory and contains a file named level.dat).
|
||||
* @param world the world.
|
||||
* @return true if the world exists, false otherwise.
|
||||
*/
|
||||
public static boolean isValidWorld(String world) {
|
||||
File d = worldDir(world);
|
||||
return d.isDirectory() && new File(d, "level.dat").isFile();
|
||||
}
|
||||
|
||||
|
||||
private WorldUtil() {}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user