Big Javadoc update + cleaned/refactored some code
This commit is contained in:
@@ -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) {
|
||||
|
@@ -1,72 +0,0 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.DyeColor;
|
||||
|
||||
/**
|
||||
* Utility class around chat coloring.
|
||||
*/
|
||||
public class BukkitChatColorUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@link TextColor} that is visually the closest from the provided {@link DyeColor} when used on a sign.
|
||||
* @param dye the provided dye color
|
||||
* @return the closest chat color from {@code dye}
|
||||
*/
|
||||
public static TextColor fromDyeToSignColor(DyeColor dye) {
|
||||
// following code invalid due to text color on sign does not use rgb value of dye color.
|
||||
//org.bukkit.Color col = dye.getColor();
|
||||
//return ChatColor.of(new Color(col.asRGB()));
|
||||
|
||||
// the color values are extracted from IG screenshot of dyed text signs.
|
||||
return switch (dye) {
|
||||
case BLACK -> TextColor.fromHexString("#000000");
|
||||
case RED -> TextColor.fromHexString("#650000");
|
||||
case GREEN -> TextColor.fromHexString("#006500");
|
||||
case BROWN -> TextColor.fromHexString("#361B07");
|
||||
case BLUE -> TextColor.fromHexString("#000065");
|
||||
case PURPLE -> TextColor.fromHexString("#3F0C5F");
|
||||
case CYAN -> TextColor.fromHexString("#006565");
|
||||
case LIGHT_GRAY -> TextColor.fromHexString("#535353");
|
||||
case GRAY -> TextColor.fromHexString("#323232");
|
||||
case PINK -> TextColor.fromHexString("#652947");
|
||||
case LIME -> TextColor.fromHexString("#4B6500");
|
||||
case YELLOW -> TextColor.fromHexString("#656500");
|
||||
case LIGHT_BLUE -> TextColor.fromHexString("#3C4B51");
|
||||
case MAGENTA -> TextColor.fromHexString("#650065");
|
||||
case ORANGE -> TextColor.fromHexString("#65280C");
|
||||
case WHITE -> TextColor.fromHexString("#656565");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static ChatColor toBukkit(net.md_5.bungee.api.ChatColor color) {
|
||||
return ChatColor.valueOf(color.getName().toUpperCase());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static ChatColor toBukkit(TextColor color) {
|
||||
return toBukkit(NamedTextColor.nearestTo(color));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static ChatColor toBukkit(NamedTextColor color) {
|
||||
return ChatColor.valueOf(color.toString().toUpperCase());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static NamedTextColor toAdventure(ChatColor color) {
|
||||
return NamedTextColor.NAMES.value(color.name().toLowerCase());
|
||||
}
|
||||
|
||||
public static NamedTextColor toAdventure(net.md_5.bungee.api.ChatColor color) {
|
||||
return NamedTextColor.NAMES.value(color.getName());
|
||||
}
|
||||
|
||||
}
|
@@ -16,45 +16,102 @@ import org.bukkit.scheduler.BukkitTask;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import fr.pandacube.lib.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() {}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -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() {}
|
||||
|
||||
}
|
||||
|
@@ -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,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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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() {}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -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() {}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -140,6 +140,8 @@ public class ScoreboardUtil {
|
||||
updateScoreboardSidebar(scBrd, title.get(), cmpLines);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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) {
|
||||
|
Reference in New Issue
Block a user