Big Javadoc update + cleaned/refactored some code

This commit is contained in:
2025-01-11 00:17:44 +01:00
parent 1925dd9b36
commit 3fe4a1b244
38 changed files with 1408 additions and 1086 deletions

View File

@@ -1,9 +1,11 @@
package fr.pandacube.lib.paper.util;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Predicate;
import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.paper.PandaLibPaper;
import fr.pandacube.lib.util.log.Log;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.bossbar.BossBar.Color;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -14,26 +16,37 @@ import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.util.log.Log;
import fr.pandacube.lib.paper.PandaLibPaper;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.bossbar.BossBar.Color;
import net.kyori.adventure.text.Component;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Predicate;
/**
* A {@link BossBar} capable of automatically updating itself by registering a {@link BukkitTask} or using a {@link Timer}.
*/
public class AutoUpdatedBossBar implements Listener {
/**
* The boss bar itself.
*/
public final BossBar bar;
/**
* the function executed to update the boss bar.
*/
public final BarUpdater updater;
private Timer timer = null;
private BukkitTask bukkitTask = null;
private boolean scheduled = false;
private boolean followPlayerList = false;
private LoginLogoutListener followPlayerList = null;
private Predicate<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
*/
@@ -90,55 +102,65 @@ public class AutoUpdatedBossBar implements Listener {
}, tickDelay, tickPeriod);
}
/**
* Auto-update the boss bar on player join and quit.
* @param condition an additional test that if it's true on player join/quit, will actually run the update. Can be null.
*/
public synchronized void followLoginLogout(Predicate<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 synchronized void onPlayerQuit(PlayerQuitEvent event) {
if (!followPlayerList)
return;
synchronized (bar) {
event.getPlayer().hideBossBar(bar);
@EventHandler(priority=EventPriority.HIGH)
public void onPlayerQuit(PlayerQuitEvent event) {
synchronized (bar) {
event.getPlayer().hideBossBar(bar);
}
}
}
/**
* Hides this boss bar from all players.
*/
public void removeAll() {
synchronized (bar) {
for (Player p : Bukkit.getOnlinePlayers())
p.hideBossBar(bar);
}
}
/**
* Cancel any auto-updating of this boss bar.
*/
public synchronized void cancel() {
if (!scheduled)
return;
@@ -151,13 +173,19 @@ public class AutoUpdatedBossBar implements Listener {
bukkitTask.cancel();
bukkitTask = null;
}
unfollowPlayerList();
}
/**
* Functional interface taking an instance of {@link AutoUpdatedBossBar} to update it. Returns nothing.
*/
@FunctionalInterface
public interface BarUpdater {
/**
* Updates the boss bar.
* @param bar the auto-updated boss bar instance.
*/
void update(AutoUpdatedBossBar bar);
}
@@ -165,6 +193,7 @@ public class AutoUpdatedBossBar implements Listener {
/**
* Utility method to update the title of the boss bar without unnecessary packet.
* @param title the new title.
*/
public void setTitle(Chat title) {
synchronized (bar) {
@@ -174,6 +203,7 @@ public class AutoUpdatedBossBar implements Listener {
/**
* Utility method to update the color of the boss bar without unnecessary packet.
* @param color the new color.
*/
public void setColor(Color color) {
synchronized (bar) {
@@ -183,6 +213,7 @@ public class AutoUpdatedBossBar implements Listener {
/**
* Utility method to update the progress of the boss bar without unnecessary packet.
* @param progress the new progress value.
*/
public void setProgress(double progress) {
synchronized (bar) {

View File

@@ -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());
}
}

View File

@@ -16,45 +16,102 @@ import org.bukkit.scheduler.BukkitTask;
import fr.pandacube.lib.paper.PandaLibPaper;
import fr.pandacube.lib.reflect.Reflect;
/**
* Utility class to more concisely handle Bukkit Events
*/
public class BukkitEvent {
/**
* Register a single event executor.
* <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);
return classes;
}
/**
* Gets the handlerList of the provided Event class.
* @param type the event class.
* @return the handlerList.
*/
// method retrieved from OB.plugin.SimplePluginManager#getEventListeners
public static HandlerList getHandlerList(Class<? extends Event> type) {
try {
@@ -81,11 +138,18 @@ public class BukkitEvent {
return null;
}
}
/**
* An single executor event listener. Used for the {@link #register(Class, EventListener)} static method and the other variants.
* @param <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;
@@ -143,6 +212,9 @@ public class BukkitEvent {
}
}
}
private BukkitEvent() {}
}

View File

@@ -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) {
@@ -34,16 +40,42 @@ public class ColorUtil {
return rainbowColors.get((int)(variation * rainbowColors.size()));
}
/**
* Returns the {@link TextColor} that is visually the closest from the provided {@link DyeColor} when used on a sign.
* @param dye the provided dye color
* @return the closest chat color from {@code dye}
*/
public static TextColor getTextColorOfDyedSign(DyeColor dye) {
// following code invalid due to text color on sign does not use rgb value of dye color.
//org.bukkit.Color col = dye.getColor();
//return ChatColor.of(new Color(col.asRGB()));
// the color values are extracted from IG screenshot of dyed text signs.
return switch (dye) {
case BLACK -> TextColor.fromHexString("#000000");
case RED -> TextColor.fromHexString("#650000");
case GREEN -> TextColor.fromHexString("#006500");
case BROWN -> TextColor.fromHexString("#361B07");
case BLUE -> TextColor.fromHexString("#000065");
case PURPLE -> TextColor.fromHexString("#3F0C5F");
case CYAN -> TextColor.fromHexString("#006565");
case LIGHT_GRAY -> TextColor.fromHexString("#535353");
case GRAY -> TextColor.fromHexString("#323232");
case PINK -> TextColor.fromHexString("#652947");
case LIME -> TextColor.fromHexString("#4B6500");
case YELLOW -> TextColor.fromHexString("#656500");
case LIGHT_BLUE -> TextColor.fromHexString("#3C4B51");
case MAGENTA -> TextColor.fromHexString("#650065");
case ORANGE -> TextColor.fromHexString("#65280C");
case WHITE -> TextColor.fromHexString("#656565");
};
}
private ColorUtil() {}
}

View File

@@ -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() {}
}

View File

@@ -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;
}
}

View File

@@ -1,199 +0,0 @@
package fr.pandacube.lib.paper.util;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
public class InventoryWrapper implements Inventory {
private final Inventory base;
public InventoryWrapper(Inventory base) {
this.base = base;
}
@Override
public int getSize() {
return base.getSize();
}
@Override
public int getMaxStackSize() {
return base.getMaxStackSize();
}
@Override
public void setMaxStackSize(int size) {
base.setMaxStackSize(size);
}
@Override
public @Nullable ItemStack getItem(int index) {
return base.getItem(index);
}
@Override
public void setItem(int index, @Nullable ItemStack item) {
base.setItem(index, item);
}
@Override
public @NotNull HashMap<Integer, ItemStack> addItem(@NotNull ItemStack... items) throws IllegalArgumentException {
return base.addItem(items);
}
@Override
public @NotNull HashMap<Integer, ItemStack> removeItem(@NotNull ItemStack... items) throws IllegalArgumentException {
return base.removeItem(items);
}
@Override
public @NotNull HashMap<Integer, ItemStack> removeItemAnySlot(@NotNull ItemStack... items) throws IllegalArgumentException {
return base.removeItemAnySlot(items);
}
@Override
public @Nullable ItemStack @NotNull [] getContents() {
return base.getContents();
}
@Override
public void setContents(@Nullable ItemStack @NotNull [] items) throws IllegalArgumentException {
base.setContents(items);
}
@Override
public @Nullable ItemStack @NotNull [] getStorageContents() {
return base.getStorageContents();
}
@Override
public void setStorageContents(@Nullable ItemStack @NotNull [] items) throws IllegalArgumentException {
base.setStorageContents(items);
}
@Override
public boolean contains(@NotNull Material material) throws IllegalArgumentException {
return base.contains(material);
}
@Override
public boolean contains(@Nullable ItemStack item) {
return base.contains(item);
}
@Override
public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException {
return base.contains(material, amount);
}
@Override
public boolean contains(@Nullable ItemStack item, int amount) {
return base.contains(item, amount);
}
@Override
public boolean containsAtLeast(@Nullable ItemStack item, int amount) {
return base.containsAtLeast(item, amount);
}
@Override
public @NotNull HashMap<Integer, ? extends ItemStack> all(@NotNull Material material) throws IllegalArgumentException {
return base.all(material);
}
@Override
public @NotNull HashMap<Integer, ? extends ItemStack> all(@Nullable ItemStack item) {
return base.all(item);
}
@Override
public int first(@NotNull Material material) throws IllegalArgumentException {
return base.first(material);
}
@Override
public int first(@NotNull ItemStack item) {
return base.first(item);
}
@Override
public int firstEmpty() {
return base.firstEmpty();
}
@Override
public boolean isEmpty() {
return base.isEmpty();
}
@Override
public void remove(@NotNull Material material) throws IllegalArgumentException {
base.remove(material);
}
@Override
public void remove(@NotNull ItemStack item) {
base.remove(item);
}
@Override
public void clear(int index) {
base.clear(index);
}
@Override
public void clear() {
base.clear();
}
@Override
public int close() {
return base.close();
}
@Override
public @NotNull List<HumanEntity> getViewers() {
return base.getViewers();
}
@Override
public @NotNull InventoryType getType() {
return base.getType();
}
@Override
public @Nullable InventoryHolder getHolder() {
return base.getHolder();
}
@Override
public @Nullable InventoryHolder getHolder(boolean useSnapshot) {
return base.getHolder(useSnapshot);
}
@Override
public @NotNull ListIterator<ItemStack> iterator() {
return base.iterator();
}
@Override
public @NotNull ListIterator<ItemStack> iterator(int index) {
return base.iterator(index);
}
@Override
public @Nullable Location getLocation() {
return base.getLocation();
}
}

View File

@@ -1,214 +0,0 @@
package fr.pandacube.lib.paper.util;
import com.google.common.collect.Streams;
import fr.pandacube.lib.chat.Chat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import static fr.pandacube.lib.chat.ChatStatic.chatComponent;
public class ItemStackBuilder {
/**
* Create a builder with a clone of the provided ItemStack.
* <p>
* The returned builder will not alter the provided ItemStack.
* If you want to modify the ItemStack with the builder, please use {@link #wrap(ItemStack)}.
* @param base the original ItemStack.
* @return the builder
*/
public static ItemStackBuilder of(ItemStack base) {
return wrap(base.clone());
}
/**
* Create a builder of a new ItemStack with the specified Material.
* @param mat the material of the new built ItemStack
* @return the builder
*/
public static ItemStackBuilder of(Material mat) {
return wrap(new ItemStack(mat));
}
/**
* Create a builder that will alter the data of the provided ItemStack.
* <p>
* The {@link #build()} method of the returned builder will return the same instance
* of ItemStack as the parameter of this method.
* <p>
* To create a builder that doesn't modify the provided ItemStack, use {@link #of(ItemStack)}.
* @param stack the wrapped item stack.
* @return the builder
*/
public static ItemStackBuilder wrap(ItemStack stack) {
return new ItemStackBuilder(stack);
}
private final ItemStack stack;
private ItemMeta cachedMeta;
private ItemStackBuilder(ItemStack base) {
stack = base;
}
private ItemMeta getOrInitMeta() {
return (cachedMeta != null) ? cachedMeta : (cachedMeta = stack.getItemMeta());
}
public ItemStackBuilder meta(Consumer<ItemMeta> metaUpdater) {
return meta(metaUpdater, ItemMeta.class);
}
public <T extends ItemMeta> ItemStackBuilder meta(Consumer<T> metaUpdater, Class<T> metaType) {
stack.editMeta(metaType, m -> {
metaUpdater.accept(m);
cachedMeta = m;
});
return this;
}
public ItemStackBuilder amount(int a) {
stack.setAmount(a);
return this;
}
public ItemStackBuilder rawDisplayName(Component displayName) {
return meta(m -> m.displayName(displayName));
}
public ItemStackBuilder displayName(ComponentLike displayName) {
if (displayName != null) {
return rawDisplayName(Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent());
}
else
return rawDisplayName(null);
}
public ItemStackBuilder rawLore(List<Component> lore) {
return meta(m -> m.lore(lore));
}
public ItemStackBuilder lore(List<? extends ComponentLike> lore) {
if (lore != null) {
return rawLore(lore.stream()
.map(line -> Chat.italicFalseIfNotSet(chatComponent(line)).get())
.toList());
}
else
return rawLore(Collections.emptyList());
}
public ItemStackBuilder addLoreAfter(List<? extends ComponentLike> lores) {
if (lores != null) {
List<Component> baseLore = getOrInitMeta().lore();
if (baseLore == null) baseLore = Collections.emptyList();
return rawLore(
Streams.concat(
baseLore.stream(),
lores.stream()
.map(line -> Chat.italicFalseIfNotSet(chatComponent(line)).get())
)
.toList());
}
else
return this;
}
public ItemStackBuilder addLoreAfter(ComponentLike... lores) {
if (lores == null || lores.length == 0)
return this;
return addLoreAfter(Arrays.asList(lores));
}
/**
* Supports unsafe enchants.
*/
public ItemStackBuilder enchant(Enchantment enchantment, int level) {
return meta(m -> m.addEnchant(enchantment, level, true));
}
public ItemStackBuilder flags(ItemFlag... flags) {
return flags(true, flags);
}
public ItemStackBuilder flags(boolean add, ItemFlag... flags) {
return add ? meta(m -> m.addItemFlags(flags))
: meta(m -> m.removeItemFlags(flags));
}
public ItemStackBuilder hideEnchants() {
return hideEnchants(true);
}
public ItemStackBuilder hideEnchants(boolean hide) {
return flags(hide, ItemFlag.HIDE_ENCHANTS);
}
public ItemStackBuilder hideAttributes() {
return hideAttributes(true);
}
public ItemStackBuilder hideAttributes(boolean hide) {
return flags(hide, ItemFlag.HIDE_ATTRIBUTES);
}
public ItemStackBuilder fakeEnchant() {
return fakeEnchant(true);
}
public ItemStackBuilder fakeEnchant(boolean apply) {
if (apply) {
enchant(Enchantment.UNBREAKING, 1);
return hideEnchants();
}
return this;
}
public ItemStackBuilder unbreakable() {
return unbreakable(true);
}
public ItemStackBuilder unbreakable(boolean unbreakable) {
return meta(m -> m.setUnbreakable(unbreakable));
}
public ItemStackBuilder damage(int d) {
return meta(m -> m.setDamage(d), Damageable.class);
}
public ItemStack build() {
return stack;
}
}

View File

@@ -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();
@@ -112,7 +133,13 @@ public class LocationUtil {
return currPosSafe(b) ? b.getLocation().add(0.5, 0, 0.5) : null;
}
/**
* Tells if the provided block is a safe block to spawn/be teleported in.
* More specifically, this block and its block above is air, and the block below is a non-lethal solid block.
* @param b the block to test.
* @return true if the provided block is a safe block to spawn/be teleported in, false otherwise.
*/
public static boolean currPosSafe(Block b) {
return b.getY() >= b.getWorld().getMinHeight() + 1 && b.getY() <= b.getWorld().getMaxHeight()
&& isSecureFloor(b.getRelative(BlockFace.DOWN))
@@ -120,10 +147,10 @@ public class LocationUtil {
&& isAir(b.getRelative(BlockFace.UP));
}
public static boolean isAir(Block b) { return b.getType() == Material.AIR; }
public static boolean isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); }
private static boolean isAir(Block b) { return b.getType() == Material.AIR; }
private static boolean isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); }
public static final Set<Material> dangerousBlocks = EnumSet.of(
private static final Set<Material> dangerousBlocks = Set.of(
Material.LAVA,
Material.WATER,
Material.COBWEB,
@@ -137,21 +164,6 @@ public class LocationUtil {
Material.NETHER_PORTAL,
Material.END_GATEWAY
);
/**
* Check if the {@link Location} l is inside the cuboid formed by the 2 others
* Locations min and max.
* @return true if l is inside the cuboid min-max
*/
public static boolean isIn(Location l, Location min, Location max) {
return (l.getWorld().equals(min.getWorld()) && l.getWorld().equals(max.getWorld()) && l.getX() >= min.getX()
&& l.getX() <= max.getX() && l.getY() >= min.getY() && l.getY() <= max.getY() && l.getZ() >= min.getZ()
&& l.getZ() <= max.getZ());
}
@@ -160,14 +172,18 @@ public class LocationUtil {
/**
* Return a new location based on the linear interpolation between p0 and p1, according to the value c.
* @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. Other finite numbers are allowed, but the returned location won't be part of the {@code [p0;p1]} segment.
* @param p0 the first location.
* @param p1 the second location.
* @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. All finite numbers are allowed.
* @return The location, linearly interpolated between p0 and p1 with the value c. The yaw and pitch in the returned location are those of p0.
* @throws IllegalArgumentException if the provided locations are not in the same world.
*/
public static Location lerp(Location p0, Location p1, float c) {
return p0.clone().add(p1.clone().subtract(p0).multiply(c));
}
private LocationUtil() {}
}

View File

@@ -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() {}
}

View File

@@ -1,376 +0,0 @@
package fr.pandacube.lib.paper.util;
import com.google.common.base.Preconditions;
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 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.
*
* @param data The data as they are 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.
*/
public PlayerDataWrapper(CompoundTag data) {
this.data = data == null ? new 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
*/
public PlayerInventory getInventory() {
return new DummyPlayerInventory(
getBukkitInventory("Inventory", InventoryType.PLAYER, this::fromNBTtoBukkitInventorySlot),
getHeldItemSlot());
}
private int fromNBTtoBukkitInventorySlot(int nbtSlot) {
// cat nbEl NBTSlot bukkitSlot NBT->Bukkit
// items 36 0-35 ==
// armor 4 starts at 100 36-39 -100 + 36
// offhand 1 starts at 150 40 -150 + 40
if (nbtSlot >= 0 && nbtSlot < 36) { // regular inventory slots
return nbtSlot;
}
if (nbtSlot >= 100 && nbtSlot < 104) { // armor slots
return nbtSlot - 100 + 36;
}
if (nbtSlot == 150) { // second hand
return 40;
}
throw new IllegalArgumentException("Unrecognized NBT player inventory slot " + nbtSlot);
}
public void setInventory(PlayerInventory inv) {
setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot);
setHeldItemSlot(inv.getHeldItemSlot());
}
private int fromBukkitToNBTInventorySlot(int bukkitSlot) {
if (bukkitSlot >= 0 && bukkitSlot < 36) { // regular inventory slots
return bukkitSlot;
}
if (bukkitSlot >= 36 && bukkitSlot < 40) { // armor slots
return bukkitSlot + 100 - 36;
}
if (bukkitSlot == 40) { // second hand
return 150;
}
throw new IllegalArgumentException("Unrecognized Bukkit player inventory slot " + bukkitSlot);
}
public Inventory getEnderChest() {
return getBukkitInventory("EnderItems", InventoryType.ENDER_CHEST, IntUnaryOperator.identity());
}
public void setEnderChest(Inventory inv) {
setBukkitInventory("EnderItems", inv, IntUnaryOperator.identity());
}
private Inventory getBukkitInventory(String nbtKey, InventoryType bukkitType, IntUnaryOperator nbtToBukkitSlotConverter) {
Map<Integer, ItemStack> stacks = getRawInventoryContent(nbtKey);
Inventory inv = Bukkit.createInventory(null, bukkitType);
if (stacks.isEmpty())
return inv;
for (Entry<Integer, ItemStack> is : stacks.entrySet()) {
inv.setItem(nbtToBukkitSlotConverter.applyAsInt(is.getKey()), is.getValue());
}
return inv;
}
private Map<Integer, ItemStack> getRawInventoryContent(String key) {
if (!data.contains(key, Tag.TAG_LIST()))
return Map.of();
ListTag list = data.getList(key, Tag.TAG_COMPOUND());
if (list == null)
return Map.of();
Map<Integer, ItemStack> stacks = new TreeMap<>();
for (int i = 0; i < list.size(); i++) {
CompoundTag itemTag = list.getCompound(i);
int nbtSlot = itemTag.getByte("Slot") & 255;
fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStack.parse(itemTag)
.map(nms -> filterStack(CraftItemStack.asCraftMirror(nms)))
.ifPresent(is -> stacks.put(nbtSlot, is));
}
return stacks;
}
private void setBukkitInventory(String nbtKey, Inventory inv, IntUnaryOperator bukkitToNBTSlotConverter) {
Map<Integer, ItemStack> stacks = new TreeMap<>();
if (inv == null) {
setRawInventoryContent(nbtKey, stacks);
return;
}
for (int bukkitSlot = 0; bukkitSlot < inv.getSize(); bukkitSlot++) {
ItemStack is = filterStack(inv.getItem(bukkitSlot));
if (is == null)
continue;
int nbtSlot = bukkitToNBTSlotConverter.applyAsInt(bukkitSlot);
stacks.put(nbtSlot, is);
}
setRawInventoryContent(nbtKey, stacks);
}
private void setRawInventoryContent(String key, Map<Integer, ItemStack> stacks) {
ListTag list = new ListTag();
for (Entry<Integer, ItemStack> is : stacks.entrySet()) {
ItemStack stack = filterStack(is.getValue());
if (stack == null)
continue;
CompoundTag itemTag = new CompoundTag();
itemTag.putByte("Slot", is.getKey().byteValue());
list.add(list.size(), CraftItemStack.asNMSCopy(is.getValue()).save(itemTag));
}
data.put(key, list);
}
private ItemStack filterStack(ItemStack is) {
return is == null || is.getType().isEmpty() || is.getAmount() == 0 ? null : is;
}
private int getHeldItemSlot() {
if (!data.contains("SelectedItemSlot"))
return 0;
return data.getInt("SelectedItemSlot");
}
private void setHeldItemSlot(int slot) {
data.putInt("SelectedItemSlot", slot);
}
public int getScore() {
if (!data.contains("Score"))
return 0;
return data.getInt("Score");
}
public void setScore(int score) {
data.putInt("Score", score);
}
public int getTotalExperience() {
if (!data.contains("XpTotal"))
return 0;
return data.getInt("XpTotal");
}
public void setTotalExperience(int xp) {
data.putInt("XpTotal", xp);
double levelAndExp = ExperienceUtil.getLevelFromExp(xp);
int level = (int) levelAndExp;
double expProgress = levelAndExp - level;
data.putInt("XpLevel", level);
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;
}
}
public static class PlayerDataLoadException extends RuntimeException {
public PlayerDataLoadException(String playerName, UUID playerId, Throwable cause) {
super("Unable to load data of player " + playerName + " (" + playerId + ")", cause);
}
}
}

View File

@@ -140,6 +140,8 @@ public class ScoreboardUtil {
updateScoreboardSidebar(scBrd, title.get(), cmpLines);
}
private ScoreboardUtil() {}
}

View File

@@ -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) {