renamed modules dir

This commit is contained in:
2022-07-20 13:28:01 +02:00
parent 7dcd92f72d
commit d2ca501203
205 changed files with 12 additions and 12 deletions

View File

@@ -0,0 +1,144 @@
package fr.pandacube.lib.paper.util;
import java.util.Iterator;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.util.BlockVector;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import fr.pandacube.lib.util.RandomUtil;
/**
* Checkpoint represented as a 3D Axis and Block Aligned Bounding Box (sort of AABB).
* Represent the littelest cuboid selection of blocks that contains the bounding box
* passed to the constructor.
*/
public class AABBBlock implements Iterable<BlockVector> {
public final Vector pos1, pos2;
private final Vector center;
private final long volume;
public AABBBlock(Vector p1, Vector p2) {
this(p1.getBlockX(), p1.getBlockY(), p1.getBlockZ(), p2.getBlockX(), p2.getBlockY(), p2.getBlockZ());
}
public AABBBlock(Location l1, Location l2) {
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
}
public AABBBlock(BlockVector l1, BlockVector l2) {
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
}
public AABBBlock(int p1x, int p1y, int p1z, int p2x, int p2y, int p2z) {
/*
* Prends les points extérieurs permettant de former un bouding box englobant
* celui représenté par v1 et v2, et étant aligné au quadrillage des blocs.
*/
int p1x_ = Math.min(p1x, p2x);
int p1y_ = Math.min(p1y, p2y);
int p1z_ = Math.min(p1z, p2z);
int p2x_ = Math.max(p1x, p2x) + 1;
int p2y_ = Math.max(p1y, p2y) + 1;
int p2z_ = Math.max(p1z, p2z) + 1;
pos1 = new Vector(p1x_, p1y_, p1z_);
pos2 = new Vector(p2x_, p2y_, p2z_);
center = new Vector((p1x_ + p2x_) / 2d, (p1y_ + p2y_) / 2d, (p1z_ + p2z_) / 2d);
volume = (long) Math.abs(p2x_ - p1x_) * Math.abs(p2x_ - p1x_) * Math.abs(p2x_ - p1x_);
}
public boolean overlaps(Entity e) {
return overlaps(e.getBoundingBox());
}
public boolean overlaps(BoundingBox bb) {
return asBukkitBoundingBox().overlaps(bb);
}
public boolean isInside(Vector v) {
return v.isInAABB(pos1, pos2);
}
public boolean isInside(Location l) {
return isInside(l.toVector());
}
public boolean isInside(Entity p) {
return isInside(p.getLocation());
}
public Vector getCenter() {
return center.clone();
}
public long getVolume() {
return volume;
}
public BoundingBox asBukkitBoundingBox() {
return new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(),
pos2.getX(), pos2.getY(), pos2.getZ());
}
public Vector getRandomPosition() {
double x = RandomUtil.nextDoubleBetween(pos1.getX(), pos2.getX());
double y = RandomUtil.nextDoubleBetween(pos1.getY(), pos2.getY());
double z = RandomUtil.nextDoubleBetween(pos1.getZ(), pos2.getZ());
return new Vector(x, y, z);
}
@Override
public Iterator<BlockVector> iterator() {
return new Iterator<>() {
private int x = pos1.getBlockX(),
y = pos1.getBlockY(),
z = pos1.getBlockZ();
@Override
public boolean hasNext() {
return x < pos2.getBlockX();
}
@Override
public BlockVector next() {
BlockVector bv = new BlockVector(x, y, z);
z++;
if (z >= pos2.getBlockZ()) {
y++;
z = pos1.getBlockZ();
if (y >= pos2.getBlockY()) {
x++;
y = pos1.getBlockY();
}
}
return bv;
}
};
}
public Iterable<Block> asBlockIterable(World w) {
return () -> new Iterator<>() {
final Iterator<BlockVector> nested = AABBBlock.this.iterator();
@Override
public boolean hasNext() {
return nested.hasNext();
}
@Override
public Block next() {
BlockVector bv = nested.next();
return w.getBlockAt(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ());
}
};
}
}

View File

@@ -0,0 +1,59 @@
package fr.pandacube.lib.paper.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;
import fr.pandacube.lib.util.IteratorIterator;
import fr.pandacube.lib.util.RandomUtil;
public class AABBBlockGroup implements Iterable<BlockVector> {
public final List<AABBBlock> aabbBlocks;
public AABBBlockGroup(Collection<AABBBlock> in) {
aabbBlocks = List.copyOf(in);
}
public AABBBlockGroup(AABBBlock... in) {
aabbBlocks = List.of(in);
}
public boolean isInside(Vector v) {
for (AABBBlock b : aabbBlocks)
if (b.isInside(v))
return true;
return false;
}
public boolean isInside(Location l) {
return isInside(l.toVector());
}
public boolean isInside(Entity p) {
return isInside(p.getLocation());
}
public Vector getRandomPosition() {
double[] freq = aabbBlocks.stream().mapToDouble(AABBBlock::getVolume).toArray();
int i = RandomUtil.randomIndexOfFrequencies(freq);
return aabbBlocks.get(i).getRandomPosition();
}
public long getVolume() {
long v = 0;
for (AABBBlock b : aabbBlocks)
v += b.getVolume();
return v;
}
@Override
public Iterator<BlockVector> iterator() {
return IteratorIterator.ofCollectionOfIterator(aabbBlocks.stream().map(AABBBlock::iterator).toList());
}
}

View File

@@ -0,0 +1,198 @@
package fr.pandacube.lib.paper.util;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Predicate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
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;
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;
public class AutoUpdatedBossBar implements Listener {
public final BossBar bar;
public final BarUpdater updater;
private Timer timer = null;
private BukkitTask bukkitTask = null;
private boolean scheduled = false;
private boolean followPlayerList = false;
private Predicate<Player> playerCondition = null;
public AutoUpdatedBossBar(BossBar bar, BarUpdater updater) {
this.bar = bar;
this.updater = updater;
}
/**
* Schedule the update of this bossbar with synchronisation with the system clock.
* The underlying method called is {@link Timer#schedule(TimerTask, long, long)}.
* The updater is executed in a separate Thread.
* @param msDelay ms before running the first update of this bossbar
* @param msPeriod ms between each call of the updater
*/
public synchronized void scheduleUpdateTimeSyncThreadAsync(long msDelay, long msPeriod) {
if (scheduled)
throw new IllegalStateException("Can't schedule an already scheduled bossbar update");
scheduled = true;
timer = new Timer("Panda BossBarUpdater - " + Chat.chatComponent(bar.name()).getPlainText());
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
updater.update(AutoUpdatedBossBar.this);
} catch(Throwable e) {
Log.severe("Error while updating an AutoUpdatedBossBar", e);
}
}
}, msDelay, msPeriod);
}
/**
* Schedule the update of this bossbar with synchronisation with the main Thread of the
* current Minecraft server (follow the tick count progress).
* The underlying method called is {@link BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)}.
* The updater is executed by the Server Thread.
* @param tickDelay number of server tick before running the first update of this bossbar
* @param tickPeriod number of server tick between each call of the updater
*/
public synchronized void scheduleUpdateTickSyncThreadSync(long tickDelay, long tickPeriod) {
if (scheduled)
throw new IllegalStateException("Can't schedule an already scheduled bossbar update");
scheduled = true;
bukkitTask = Bukkit.getServer().getScheduler()
.runTaskTimer(PandaLibPaper.getPlugin(), () -> {
synchronized (bar) {
try {
updater.update(this);
} catch(Throwable e) {
Log.severe("Error while updating an AutoUpdatedBossBar", e);
}
}
}, tickDelay, tickPeriod);
}
public synchronized void followLoginLogout(Predicate<Player> condition) {
playerCondition = condition;
if (followPlayerList)
return;
followPlayerList = true;
BukkitEvent.register(this);
Bukkit.getServer().getOnlinePlayers().forEach(p -> onPlayerJoin(new PlayerJoinEvent(p, Component.text(""))));
}
public synchronized void unfollowPlayerList() {
if (!followPlayerList)
return;
followPlayerList = false;
playerCondition = null;
PlayerJoinEvent.getHandlerList().unregister(this);
PlayerQuitEvent.getHandlerList().unregister(this);
}
@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);
}
}
@EventHandler(priority=EventPriority.HIGH)
public synchronized void onPlayerQuit(PlayerQuitEvent event) {
if (!followPlayerList)
return;
synchronized (bar) {
event.getPlayer().hideBossBar(bar);
}
}
public void removeAll() {
synchronized (bar) {
for (Player p : Bukkit.getOnlinePlayers())
p.hideBossBar(bar);
}
}
public synchronized void cancel() {
if (!scheduled)
throw new IllegalStateException("Can't cancel a not scheduled bossbar update");
scheduled = false;
if (timer != null) {
timer.cancel();
timer = null;
}
if (bukkitTask != null) {
bukkitTask.cancel();
bukkitTask = null;
}
}
@FunctionalInterface
public interface BarUpdater {
void update(AutoUpdatedBossBar bar);
}
/**
* Utility method to update the title of the bossbar without unnecessary packet.
*/
public void setTitle(Chat title) {
synchronized (bar) {
bar.name(title); // already check if the title is the same
}
}
/**
* Utility method to update the color of the bossbar without unnecessary packet.
*/
public void setColor(Color color) {
synchronized (bar) {
bar.color(color);
}
}
/**
* Utility method to update the progress of the bossbar without unnecessary packet.
*/
public void setProgress(double progress) {
synchronized (bar) {
bar.progress((float) progress);
}
}
}

View File

@@ -0,0 +1,65 @@
package fr.pandacube.lib.paper.util;
import org.bukkit.DyeColor;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.md_5.bungee.api.ChatColor;
public class BukkitChatColorUtil {
/**
* Returns the {@link ChatColor} that is visually the closest from the provided {@link DyeColor} when used on a sign.
*
* Multiple {@link DyeColor} may return the same
* @param dye the provided dye color
* @return the closest chat color from {@code dye}
*/
public static ChatColor fromDyeToSignColor(DyeColor dye) {
//org.bukkit.Color col = dye.getColor();
//return ChatColor.of(new Color(col.asRGB()));
// hmmm this is not that simple, of course
// black
return switch (dye) {
case BLACK -> ChatColor.of("#000000");
case RED -> ChatColor.of("#650000");
case GREEN -> ChatColor.of("#006500");
case BROWN -> ChatColor.of("#361B07");
case BLUE -> ChatColor.of("#000065");
case PURPLE -> ChatColor.of("#3F0C5F");
case CYAN -> ChatColor.of("#006565");
case LIGHT_GRAY -> ChatColor.of("#535353");
case GRAY -> ChatColor.of("#323232");
case PINK -> ChatColor.of("#652947");
case LIME -> ChatColor.of("#4B6500");
case YELLOW -> ChatColor.of("#656500");
case LIGHT_BLUE -> ChatColor.of("#3C4B51");
case MAGENTA -> ChatColor.of("#650065");
case ORANGE -> ChatColor.of("#65280C");
case WHITE -> ChatColor.of("#656565");
};
}
public static org.bukkit.ChatColor toBukkit(ChatColor color) {
return org.bukkit.ChatColor.valueOf(color.getName().toUpperCase());
}
public static org.bukkit.ChatColor toBukkit(TextColor color) {
return toBukkit(NamedTextColor.nearestTo(color));
}
public static org.bukkit.ChatColor toBukkit(NamedTextColor color) {
return org.bukkit.ChatColor.valueOf(color.toString().toUpperCase());
}
public static NamedTextColor toAdventure(org.bukkit.ChatColor color) {
return NamedTextColor.NAMES.value(color.name().toLowerCase());
}
}

View File

@@ -0,0 +1,144 @@
package fr.pandacube.lib.paper.util;
import fr.pandacube.lib.reflect.Reflect;
import fr.pandacube.lib.paper.PandaLibPaper;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.RegisteredListener;
import org.bukkit.scheduler.BukkitTask;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class BukkitEvent {
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor) {
return register(eventClass, eventExecutor, EventPriority.NORMAL, false);
}
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority) {
return register(eventClass, eventExecutor, priority, false);
}
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, boolean ignoreCancelled) {
return register(eventClass, eventExecutor, EventPriority.NORMAL, ignoreCancelled);
}
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority, boolean ignoreCancelled) {
Bukkit.getPluginManager().registerEvent(eventClass, eventExecutor, priority, eventExecutor, PandaLibPaper.getPlugin(), ignoreCancelled);
return eventExecutor;
}
public static void register(Listener l) {
Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin());
}
public static void unregister(Listener listener) {
HandlerList.unregisterAll(listener);
}
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;
}
// method retrieved from OB.plugin.SimplePluginManager#getEventListeners
public static HandlerList getHandlerList(Class<? extends Event> type) {
try {
return (HandlerList) Reflect.ofClass(getRegistrationClass(type)).method("getHandlerList").invokeStatic();
}
catch (ReflectiveOperationException e) {
return null;
}
}
// method retrieved from OB.plugin.SimplePluginManager
private static Class<? extends Event> getRegistrationClass(Class<? extends Event> clazz) {
try {
clazz.getDeclaredMethod("getHandlerList");
return clazz;
}
catch (NoSuchMethodException e) {
if (clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Event.class) && Event.class.isAssignableFrom(clazz.getSuperclass())) {
return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class));
}
return null;
}
}
public interface EventListener<E extends Event> extends Listener, EventExecutor {
void onEvent(E event);
@SuppressWarnings("unchecked")
@Override
default void execute(Listener var1, Event var2) throws EventException {
onEvent((E)var2);
}
}
/**
* Abstract implementation of {@link EventListener} that ensure as best as it can,
* that it is the last listener called to handle 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;
public EnforcedLastListener(Class<E> eventClass, boolean ignoreCancelled) {
this.eventClass = eventClass;
this.ignoreCancelled = ignoreCancelled;
register();
}
private void register() {
BukkitEvent.register(eventClass, this, EventPriority.MONITOR, ignoreCancelled);
}
@Override
public void execute(Listener var1, Event var2) throws EventException {
EventListener.super.execute(var1, var2);
checkIfListenerIsLast();
}
private final AtomicReference<BukkitTask> listenerCheckTask = new AtomicReference<>();
private void checkIfListenerIsLast() {
synchronized (listenerCheckTask) {
if (listenerCheckTask.get() != null)
return;
HandlerList hList = BukkitEvent.getHandlerList(eventClass);
if (hList == null)
return;
RegisteredListener[] listeners = hList.getRegisteredListeners();
if (listeners[listeners.length - 1].getListener() != this) {
listenerCheckTask.set(Bukkit.getScheduler().runTask(PandaLibPaper.getPlugin(), () -> {
// need to re-register the event so we are last
BukkitEvent.unregister(this);
register();
listenerCheckTask.set(null);
}));
}
}
}
}
}

View File

@@ -0,0 +1,49 @@
package fr.pandacube.lib.paper.util;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Color;
public class ColorUtil {
/*
* Rainbow
*/
private static final List<Color> rainbowColors = new ArrayList<>();
/**
* Get 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
*/
public static Color getRainbowColor(double variation) {
synchronized (rainbowColors) {
if (rainbowColors.isEmpty()) {
for (int g=0; g<255; g++) rainbowColors.add(Color.fromRGB(255, g, 0));
for (int r=255; r>0; r--) rainbowColors.add(Color.fromRGB(r, 255, 0));
for (int b=0; b<255; b++) rainbowColors.add(Color.fromRGB(0, 255, b));
for (int g=255; g>0; g--) rainbowColors.add(Color.fromRGB(0, g, 255));
for (int r=0; r<255; r++) rainbowColors.add(Color.fromRGB(r, 0, 255));
for (int b=255; b>0; b--) rainbowColors.add(Color.fromRGB(255, 0, b));
}
}
while (variation >= 1) variation -= 1d;
while (variation < 0) variation += 1d;
return rainbowColors.get((int)(variation * rainbowColors.size()));
}
}

View File

@@ -0,0 +1,52 @@
package fr.pandacube.lib.paper.util;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
/**
* Permet de gérer les entités qui se transportent les uns les autres
*
* Ce groupement d'entité est appelé une "pile d'entité".
*
* Par exemple, un cheval et son monteur représente à eux deux une pile
* d'entité, dont
* l'élement tout en bas est le cheval
*/
public class EntityStackUtil {
/**
* Déplace une pile d'entité vers l'endroit défini
*
* @param e Une entité faisant parti de la pile d'entité à téléporter
* @param l La position vers lequel envoyer toute la pile
*/
public static void teleportStack(Entity e, Location l) {
// on se place sur l'entité tout en bas de la pile
Entity entTemp = e;
while (entTemp.getVehicle() != null)
entTemp = entTemp.getVehicle();
/* la possibilité d'avoir plusieurs passagers sur un entité rend le code
* commenté qui suit invalide. On le remplace temporairement (voire
* définitivement si ça suffit) par le code encore en dessous
List<Entity> stack = new ArrayList<>();
do {
stack.add(entTemp);
entTemp = entTemp.getPassenger();
} while (entTemp != null);
if (stack.size() == 1) {
stack.get(0).teleport(l);
return;
}
stack.get(0).eject();
stack.get(0).teleport(l);
stack.get(0).setPassenger(stack.get(1));
*/
entTemp.teleport(l); // entTemp est l'entité le plus en bas de la "pile" d'entité
}
}

View File

@@ -0,0 +1,92 @@
package fr.pandacube.lib.paper.util;
import org.bukkit.entity.Player;
/**
* A utility for managing Player experience properly.<br/>
* <a href="https://gist.github.com/Jikoo/30ec040443a4701b8980">Github Gist - Jikoo</a>
*
* @author Jikoo
*/
public class ExperienceUtil {
/**
* Calculates a player's total exp based on level and progress to next.
*
* @see <a href="http://minecraft.gamepedia.com/Experience#Leveling_up">Experience (#leveling up) - Minecraft Wiki</a>
*
* @param player the Player
*
* @return the amount of exp the Player has
*/
public static int getExp(Player player) {
return getExpFromLevel(player.getLevel()) + Math.round(getExpToNext(player.getLevel()) * player.getExp());
}
/**
* Calculates total experience based on level.
*
* @see <a href="http://minecraft.gamepedia.com/Experience#Leveling_up">Experience (#leveling up) - Minecraft Wiki</a>
*
* @param level the level
*
* @return the total experience calculated
*/
public static int getExpFromLevel(int level) {
if (level > 30) return (int) (4.5 * level * level - 162.5 * level + 2220);
if (level > 15) return (int) (2.5 * level * level - 40.5 * level + 360);
return level * level + 6 * level;
}
/**
* Calculates level based on total experience.
*
* @param exp the total experience
*
* @return the level calculated
*/
public static double getLevelFromExp(int exp) {
if (exp > 1395) return (Math.sqrt(72 * exp - 54215) + 325) / 18;
if (exp > 315) return Math.sqrt(40 * exp - 7839) / 10 + 8.1;
if (exp > 0) return Math.sqrt(exp + 9) - 3;
return 0;
}
/**
* @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
* 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+)"
*/
private static int getExpToNext(int level) {
if (level > 30) return 9 * level - 158;
if (level > 15) return 5 * level - 38;
return 2 * level + 7;
}
/**
* Change a Player's exp.
* <p>
* This method should be used in place of {@link Player#giveExp(int)}, which
* does not properly
* account for different levels requiring different amounts of experience.
*
* @param player the Player affected
* @param exp the amount of experience to add or remove
*/
public static void changeExp(Player player, int exp) {
exp += getExp(player);
if (exp < 0) exp = 0;
double levelAndExp = getLevelFromExp(exp);
int level = (int) levelAndExp;
player.setLevel(level);
player.setExp((float) (levelAndExp - level));
}
}

View File

@@ -0,0 +1,97 @@
package fr.pandacube.lib.paper.util;
import java.io.File;
import java.io.IOException;
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.event.Listener;
import fr.pandacube.lib.util.BiMap;
import fr.pandacube.lib.util.FileUtils;
import fr.pandacube.lib.util.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));
return ret;
}
return true;
}
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.nextIntBetween(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));
w.setAutoSave(false);
gameWorld.put(world, w);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "mvm set hidden true "+copiedName);
operationOnLoad.accept(w);
return w;
}
}

View File

@@ -0,0 +1,410 @@
package fr.pandacube.lib.paper.util;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class GeometryUtil {
/**
* Value equal to <code>{@link Math#PI}</code>.
*/
public static final double PI = Math.PI;
/**
* Value equal to <code>{@link Math#PI} / 2</code>.
*/
public static final double PId2 = PI/2;
/**
* Value equal to <code>{@link Math#PI} * 2</code>.
*/
public static final double PIx2 = PI*2;
/*
* Player geometry
*/
/**
* The visual height of a Minecraft player skin, when he is standing up and not sneaking,
* from the ground where the player is standing on, to the above of the first layer of the head skin.
* It doesn't correspond to the player hitbox height.<br/>
* <br/>
* <code>1.88</code> is an approximated value, estimated by ingame tests.
*/
public static final double PLAYER_SKIN_HEIGHT = 1.88;
/**
* Value provided by Craftbukkit's {@code CraftPlayer#getEyeHeight(boolean)} source code
*/
public static final double PLAYER_EYE_HEIGHT = 1.62;
/**
* The visual height of a Minecraft player skin, when he is standing up and sneaking,
* from the ground where the player is standing on, to the above of the first layer of the head skin.
* It may not correspond to the player hitbox height.<br/>
* <br/>
* The current value is the height of the player's hitbox when sneaking. Even if this
* is close to the real value (tested in game), this is not the exact value.
*/
public static final double PLAYER_SKIN_HEIGHT_SNEAK = 1.65;
/**
* Value provided by Craftbukkit's {@code CraftPlayer#getEyeHeight(boolean)} source code
*/
public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.54;
public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32;
public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24;
public static final double PLAYER_HEAD_ROTATION_HEIGHT_SNEAK = PLAYER_HEAD_ROTATION_HEIGHT - (PLAYER_SKIN_HEIGHT - PLAYER_SKIN_HEIGHT_SNEAK);
public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8;
/**
* Get the {@link Location}s of the 8 vertex of the player head<br/>
* This method only work if the player is standing up
* (not dead, not glyding, not sleeping).
* @param playerLocation the location of the player, generally provided by {@link Player#getLocation()}
* @param isSneaking if the player is sneaking. Generally {@link Player#isSneaking()}
* @return an array of 8 {@link Location}s with x, y, and z values filled (yaw and pitch are ignored).
* <pre>return[0] // top front left
*return[1] // top front right
*return[2] // bottom front left
*return[3] // bottom front right
*return[4] // top back left
*return[5] // top back right
*return[6] // bottom back left
*return[7] // bottom back right
*/
public static Location[] getPlayerHeadGeometry(Location playerLocation, boolean isSneaking) {
Location[] headAnglesPoints = new Location[8];
Location playerHeadRotationLocation = playerLocation.clone()
.add(0, isSneaking ? PLAYER_HEAD_ROTATION_HEIGHT_SNEAK : PLAYER_HEAD_ROTATION_HEIGHT, 0);
DirectionalVector frontDirection = new DirectionalVector(playerHeadRotationLocation);
Vector frontHalfVector = frontDirection.toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector backHalfDirection = frontDirection.getBackDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector leftHalfVector = frontDirection.getLeftDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector rightHalfVector = frontDirection.getRightDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector topVector = frontDirection.getTopDirection().toVector().multiply(PLAYER_HEAD_SIZE);
Location bottomFrontMiddle = playerHeadRotationLocation.clone().add(frontHalfVector);
Location bottomBackMiddle = playerHeadRotationLocation.clone().add(backHalfDirection);
Location topFrontMiddle = bottomFrontMiddle.clone().add(topVector);
Location topBackMiddle = bottomBackMiddle.clone().add(topVector);
headAnglesPoints[0] = topFrontMiddle.clone().add(leftHalfVector);
headAnglesPoints[1] = topFrontMiddle.clone().add(rightHalfVector);
headAnglesPoints[2] = bottomFrontMiddle.clone().add(leftHalfVector);
headAnglesPoints[3] = bottomFrontMiddle.clone().add(rightHalfVector);
headAnglesPoints[4] = topBackMiddle.clone().add(leftHalfVector);
headAnglesPoints[5] = topBackMiddle.clone().add(rightHalfVector);
headAnglesPoints[6] = bottomBackMiddle.clone().add(leftHalfVector);
headAnglesPoints[7] = bottomBackMiddle.clone().add(rightHalfVector);
return headAnglesPoints;
}
/**
* Check if the path from <i>start</i> location to <i>end</i> pass through
* the axis aligned bounding box defined by <i>min</i> and <i>max</i>.
*/
public static boolean hasIntersection(Vector start, Vector end, Vector min, Vector max) {
final double epsilon = 0.0001f;
Vector d = end.clone().subtract(start).multiply(0.5);
Vector e = max.clone().subtract(min).multiply(0.5);
Vector c = start.clone().add(d).subtract(min.clone().add(max).multiply(0.5));
Vector ad = d.clone();
ad.setX(Math.abs(ad.getX()));
ad.setY(Math.abs(ad.getY()));
ad.setZ(Math.abs(ad.getZ()));
if (Math.abs(c.getX()) > e.getX() + ad.getX()){
return false;
}
if (Math.abs(c.getY()) > e.getY() + ad.getY()){
return false;
}
if (Math.abs(c.getZ()) > e.getX() + ad.getZ()){
return false;
}
if (Math.abs(d.getY() * c.getZ() - d.getZ() * c.getY()) > e.getY() * ad.getZ() + e.getZ() * ad.getY() + epsilon){
return false;
}
if (Math.abs(d.getZ() * c.getX() - d.getX() * c.getZ()) > e.getZ() * ad.getX() + e.getX() * ad.getZ() + epsilon){
return false;
}
if (Math.abs(d.getX() * c.getY() - d.getY() * c.getX()) > e.getX() * ad.getY() + e.getY() * ad.getX() + epsilon){
return false;
}
return true;
}
/**
* This vector consider Minecraft X Y Z axis orientation,
* but consider standard (not Minecraft) radian values for yaw and pitch.<br/>
* The length of this Vector (based on {@link #x}, {@link #y} and {@link #z} values)
* Is always 1.
*
* <pre>Yaw :
* North (-z) = -PI/2
* East (+x) = 0
* South (+z) = PI/2
* West (-x) = ±PI
*
* Pitch :
* Up (+y) = PI/2
* Down (-y) = -PI/2</pre>
*/
public static class DirectionalVector {
/**
* The X cartesian coordinate of this {@link DirectionalVector}.
* It correspond to the X (west to east) axis in a Minecraft world.
*/
public final double x;
/**
* The Y cartesian coordinate of this {@link DirectionalVector}.
* It correspond to the Y (bottom to top) axis in a Minecraft world.
*/
public final double y;
/**
* The Z cartesian coordinate of this {@link DirectionalVector}.
* It correspond to the Z (north to south) axis in a Minecraft world.
*/
public final double z;
/**
* The azimuthal angle φ (phi) of this {@link DirectionalVector}, in radian.
* It correspond with Minecraft world as follow :
* <pre>Yaw :
* North (-z) = -PI/2
* East (+x) = 0
* South (+z) = PI/2
* West (-x) = ±PI</pre>
*/
public final double yaw;
/**
* The polar angle θ (theta) of this {@link DirectionalVector}, in radian.
* It correspond with Minecraft world as follow :
* <pre>Pitch :
* Down (-y) = -PI/2
* Up (+y) = PI/2</pre>
*/
public final double pitch;
/**
* Initialize this {@link DirectionalVector} with the yaw and pitch
* contained in the provided {@link Location}.
* {@link Location#getYaw()} and {@link Location#getPitch()} values are automatically
* converted to conform {@link #yaw} and {@link #pitch} specification.
*/
public DirectionalVector(Location l) {
this(
Math.toRadians(((l.getYaw()+90)%360) > 180 ? ((l.getYaw()+90)%360)-360 : ((l.getYaw()+90)%360)),
-Math.toRadians(l.getPitch())
);
/* MC : +90 : %360 : >180 -> -360
* South (+z) = 0, 360 : 90-450 : 90 : 90 : PI/2
* West (-x) = 90 : 180 : 180 : ±180 : ±PI
* North (-z) = 180 : 270 : 270 : -90 : -PI/2
* East (+x) = 270 : 360 : 0-360 : 0 : 0
*/
}
/**
* @param v the vector representing the direction. If v.getX() && v.getZ() are 0,
* the yaw will be 0. This may have inconsistence if the vector is calculated
* from a {@link Location}'s yaw and pitch. In this case, prefer using
* {@link #DirectionalVector(Location)}. The {@link Vector} is
* normalized if necessary (does not modify provided {@link Vector}).
*/
public DirectionalVector(Vector v) {
this(v.getX(), v.getY(), v.getZ());
// this((v = v.clone().normalize()).getX(), v.getY(), v.getZ());
}
private DirectionalVector(double x, double y, double z) {
double vectSize = Math.sqrt(x*x + y*y + z*z);
this.x = x/vectSize;
this.y = y/vectSize;
this.z = z/vectSize;
if (x == 0.0 && z == 0.0) {
pitch = y > 0.0 ? PId2 : -PId2;
yaw = 0;
}
else {
yaw = Math.atan2(z, x);
pitch = Math.atan(y / Math.sqrt(x*x + z*z));
}
}
private DirectionalVector(double x, double y, double z, double yaw, double pitch) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
}
private DirectionalVector(double yaw, double pitch) {
this.yaw = yaw;
this.pitch = pitch;
y = Math.sin(pitch);
double cosPitch = Math.cos(pitch);
x = cosPitch * Math.cos(yaw);
z = cosPitch * Math.sin(yaw);
}
public Vector toVector() {
return new Vector(x, y, z);
}
/**
* Set the yaw and the pitch of the provided {@link Location}
* with the values inside the current {@link DirectionalVector}
* after conversion of these values
*/
public void putIntoLocation(Location l) {
/* std : -PI/2 : <0 ? +2PI : MC
* South (+z) = PI/2 : 0 : 0 : 0, 360
* West (-x) = ±PI : -3PI/2 - PI/2 : PI/2 : 90
* North (-z) = -PI/2 : -PI : PI : 180
* East (+x) = 0 : -PI/2 : 3PI/2 : 270
*/
l.setYaw((float)Math.toDegrees(yaw < PId2 ? yaw + PIx2 - PId2 : yaw - PId2));
l.setPitch((float)Math.toDegrees(-pitch));
}
public DirectionalVector getOpposite() {
return new DirectionalVector(
-x,
-y,
-z,
(yaw > 0 ? (yaw - PI) : (yaw + PI)),
-pitch
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the back of the head.
* This is an alias of {@link #getOpposite()}
*/
public DirectionalVector getBackDirection() {
return getOpposite();
}
/**
* If the current direction is the player face direction,
* this method return the direction of the bottom of the head.
*/
public DirectionalVector getBottomDirection() {
return new DirectionalVector(
(pitch > 0 ? yaw : (yaw > 0 ? (yaw - PI) : (yaw + PI))),
(pitch > 0 ? (pitch - PId2) : (-PId2 - pitch))
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the top of the head.
*/
public DirectionalVector getTopDirection() {
return new DirectionalVector(
(pitch < 0 ? yaw : (yaw > 0 ? (yaw - PI) : (yaw + PI))),
(pitch < 0 ? (pitch + PId2) : (PId2 - pitch))
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the left of the head.
*/
public DirectionalVector getLeftDirection() {
return new DirectionalVector(
yaw > -PId2 ? (yaw - PId2) : (yaw - PId2 + PIx2),
0
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the right of the head.
*/
public DirectionalVector getRightDirection() {
return new DirectionalVector(
yaw < PId2 ? (yaw + PId2) : (yaw + PId2 - PIx2),
0
);
}
}
}

View File

@@ -0,0 +1,192 @@
package fr.pandacube.lib.paper.util;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
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 com.google.common.collect.Streams;
import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.chat.Chat.FormatableChat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.format.TextDecoration.State;
public class ItemStackBuilder {
/**
* Create a builder with a clone of the provided ItemStack.
*
* 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 builded 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.
*
* The {@link #build()} method of thez returned builder will return the same instance
* of ItemStack as the parameter of this method.
*
* To create a builder that doesnt modify the provided ItemStack, use {@link #of(ItemStack)}.
* @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());
}
private void updateMeta() {
stack.setItemMeta(cachedMeta);
}
public ItemStackBuilder amount(int a) {
stack.setAmount(a);
return this;
}
public ItemStackBuilder rawDisplayName(Component displayName) {
getOrInitMeta().displayName(displayName);
updateMeta();
return this;
}
public ItemStackBuilder displayName(Chat displayName) {
if (displayName != null) {
if (displayName.getAdv().style().decoration(TextDecoration.ITALIC) == State.NOT_SET)
((FormatableChat)displayName).italic(false);
return rawDisplayName(displayName.getAdv());
}
else
return rawDisplayName(null);
}
public ItemStackBuilder rawLore(List<Component> lore) {
getOrInitMeta().lore(lore);
updateMeta();
return this;
}
public ItemStackBuilder lore(List<Chat> lore) {
if (lore != null) {
return rawLore(lore.stream()
.map(line -> Chat.italicFalseIfNotSet(line).getAdv())
.collect(Collectors.toList()));
}
else
return rawLore(Collections.emptyList());
}
public ItemStackBuilder addLoreAfter(List<Chat> 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(line).getAdv())
)
.collect(Collectors.toList()));
}
else
return this;
}
public ItemStackBuilder addLoreAfter(Chat... lores) {
if (lores == null || lores.length == 0)
return this;
return addLoreAfter(Arrays.asList(lores));
}
public ItemStackBuilder enchant(Enchantment ench, int level) {
getOrInitMeta().addEnchant(ench, level, true);
updateMeta();
return this;
}
public ItemStackBuilder flags(ItemFlag... flags) {
getOrInitMeta().addItemFlags(flags);
updateMeta();
return this;
}
public ItemStackBuilder hideEnchants() {
return flags(ItemFlag.HIDE_ENCHANTS);
}
public ItemStackBuilder hideAttributes() {
return flags(ItemFlag.HIDE_ATTRIBUTES);
}
public ItemStackBuilder fakeEnchant() {
enchant(Enchantment.DURABILITY, 1);
return hideEnchants();
}
public ItemStackBuilder damage(int d) {
ItemMeta m = getOrInitMeta();
if (m instanceof Damageable)
((Damageable)m).setDamage(d);
updateMeta();
return this;
}
public ItemStack build() {
return stack;
}
}

View File

@@ -0,0 +1,177 @@
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 org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.WorldBorder;
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;
public class LocationUtil {
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
*/
public static CompletableFuture<Location> getRandomSecureLocation(World w, Predicate<Location> extraSecureCheck) {
WorldBorder wb = w.getWorldBorder();
Location minWorld = wb.getCenter().clone().add(-wb.getSize()/2, 0, -wb.getSize()/2);
Location maxWorld = wb.getCenter().clone().add(wb.getSize()/2, 0, wb.getSize()/2);
return getRandomSecureLocation(w, minWorld, maxWorld, extraSecureCheck);
}
private static final int maxTryBeforeCancelRandomLocation = 75;
public static CompletableFuture<Location> getRandomSecureLocation(World w, Location min, Location max, Predicate<Location> extraSecureCheck) {
CompletableFuture<Location> future = new CompletableFuture<>();
AtomicReference<BukkitTask> t = new AtomicReference<>();
AtomicInteger count = new AtomicInteger(0);
t.set(Bukkit.getScheduler().runTaskTimer(PandaLibPaper.getPlugin(), () -> {
count.incrementAndGet();
if (count.get() > maxTryBeforeCancelRandomLocation) {
future.complete(null);
t.get().cancel();
}
// generate a random (x,z) coordinate
Location ret = new Location(w,
RandomUtil.nextIntBetween(min.getBlockX(), max.getBlockX()) + 0.5,
w.getMaxHeight() - 1,
RandomUtil.nextIntBetween(min.getBlockZ(), max.getBlockZ()) + 0.5);
// find a secure y value
ret = getSecureLocationOrNull(ret);
if (ret == null)
// there is no secure y position for the provided (x,z) values
return;
if (extraSecureCheck != null && !extraSecureCheck.test(ret))
return; // extra checks didnt validate the location
//if (checkCubo && PandacubePaper.getPlugin().cuboManager != null)
// if (PandacubePaper.getPlugin().cuboManager.getCuboFromLocation(ret) != null)
// return; // il y a un cubo à l'endroit aléatoire sélectionnée
// tout est bon
future.complete(ret);
t.get().cancel();
}, 1, 1));
return future;
}
/**
*
* @param l the source location
* @return a secure location with the same X and Z coordinate as the
* provided location, but Y modified to ensure security for player
* who will be teleported to this location.
* May return null if it is impossible to securize find a secure location.
*/
public static Location getSecureLocationOrNull(Location l) {
l = l.clone();
l.setY(l.getWorld().getEnvironment() == Environment.NETHER ? 126 : 256);
Block b = l.getBlock();
while (b.getY() >= 0 && !currPosSafe(b))
b = b.getRelative(BlockFace.DOWN);
return currPosSafe(b) ? b.getLocation().add(0.5, 0, 0.5) : null;
}
public static boolean currPosSafe(Block b) {
return b.getY() >= b.getWorld().getMinHeight() + 1 && b.getY() <= b.getWorld().getMaxHeight()
&& isSecureFloor(b.getRelative(BlockFace.DOWN))
&& isAir(b)
&& 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()); }
public static final Set<Material> dangerousBlocks = EnumSet.of(
Material.LAVA,
Material.WATER,
Material.COBWEB,
Material.MAGMA_BLOCK,
Material.CAMPFIRE,
Material.SOUL_CAMPFIRE,
Material.FIRE,
Material.SOUL_FIRE,
Material.WITHER_ROSE,
Material.END_PORTAL,
Material.NETHER_PORTAL,
Material.END_GATEWAY
);
/**
* Check if the {@link Location} l is inside the cuboïd formed by the 2 others
* Locations min and max.
* @return true if l is inside the cuboid min-max
*/
public static boolean isIn(Location l, Location min, Location max) {
return (l.getWorld().equals(min.getWorld()) && l.getWorld().equals(max.getWorld()) && l.getX() >= min.getX()
&& l.getX() <= max.getX() && l.getY() >= min.getY() && l.getY() <= max.getY() && l.getZ() >= min.getZ()
&& l.getZ() <= max.getZ());
}
/**
* Return a new location based on the linear interpolation between p0 and p1, according to the value c.
* @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. Other finite numbers are allowed, but the returned location wont be part of the {@code [p0;p1]} segment.
* @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));
}
}

View File

@@ -0,0 +1,13 @@
package fr.pandacube.lib.paper.util;
import org.bukkit.Material;
import org.bukkit.block.data.type.Sign;
import org.bukkit.block.data.type.WallSign;
public class MaterialUtil {
public static boolean isSign(Material m) {
return WallSign.class.equals(m.data) || Sign.class.equals(m.data);
}
}

View File

@@ -0,0 +1,138 @@
package fr.pandacube.lib.paper.util;
import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Score;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;
import fr.pandacube.lib.chat.Chat;
import net.kyori.adventure.text.Component;
public class ScoreboardUtil {
/**
* Update the sidebar of the provided scoreboard, with the given title and lines.
*
* @param scBrd the scoreboard
* @param title the title of the sidebar
* @param lines the lines that have to be displayed. Null values are treated as empty lines.
* The lines support legacy formatting only, and will be truncated to 40 characters.
* Lines present multiple times will have hidden characters appended to make them different.
* Vanilla Java Edition clients only display the 15 first lines.
*/
public static void updateScoreboardSidebar(Scoreboard scBrd, Component title, Component[] lines) {
Objective obj = scBrd.getObjective("sidebar_autogen");
if (obj != null && !obj.getCriteria().equalsIgnoreCase("dummy")) {
// objective present but with wrong criteria, removing it
obj.unregister();
obj = null;
}
if (obj == null) {
obj = scBrd.registerNewObjective("sidebar_autogen", "dummy", title);
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
}
else {
// only update title if needed
if (!title.equals(obj.displayName())) {
obj.displayName(title);
}
// fix display slot if someone else changed it
if (DisplaySlot.SIDEBAR != obj.getDisplaySlot()) {
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
}
}
ChatColor[] colors = ChatColor.values();
/*
* Scanning lines from last to first, keeping only the 15 first lines
*/
int score = 1, i = 0;
for (int lineIndex = Math.min(lines.length, 15) - 1; lineIndex >= 0; i++, score++, lineIndex--) {
String teamId = "sidebar_team" + score;
String sbEntry = colors[i].toString();
Team tLine = scBrd.getTeam(teamId);
if (tLine == null) {
tLine = scBrd.registerNewTeam(teamId);
}
if (!tLine.hasEntry(sbEntry)) {
tLine.addEntry(sbEntry);
}
if (!tLine.prefix().equals(lines[lineIndex])) {
tLine.prefix(lines[lineIndex]);
}
Score scoreEntry = obj.getScore(sbEntry);
if (scoreEntry.getScore() != score) {
scoreEntry.setScore(score);
}
}
// clean older data when we are reducing the number of line displayed
for (; i < colors.length; i++, score++) {
String teamId = "sidebar_team" + score;
String sbEntry = colors[i].toString();
if (obj.getScore(sbEntry).isScoreSet()) {
scBrd.resetScores(sbEntry);
}
Team tLine = scBrd.getTeam(teamId);
if (tLine != null && !tLine.prefix().equals(Component.empty())) {
tLine.prefix(Component.empty());
}
}
}
/**
* Update the sidebar of the provided scoreboard, with the given title and lines.
*
* @param scBrd the scoreboard
* @param title the title of the sidebar
* @param lines the lines that have to be displayed. Null values are treated as empty lines.
* The lines support legacy formatting only, and will be truncated to 40 characters.
* Lines present multiple times will have hidden characters appended to make them different.
* Vanilla Java Edition clients only display the 15 first lines.
*/
public static void updateScoreboardSidebar(Scoreboard scBrd, Chat title, Chat[] lines) {
Component[] cmpLines = new Component[lines.length];
for (int i = 0; i < lines.length; i++) {
cmpLines[i] = lines[i].getAdv();
}
updateScoreboardSidebar(scBrd, title.getAdv(), cmpLines);
}
/**
* Update the sidebar of the provided scoreboard, with the given title and lines.
*
* @param scBrd the scoreboard
* @param title the title of the sidebar
* @param lines the lines that have to be displayed. Null values are treated as empty lines.
* The lines support legacy formatting only, and will be truncated to 40 characters.
* Lines present multiple times will have hidden characters appended to make them different.
* Vanilla Java Edition clients only display the 15 first lines.
*/
public static void updateScoreboardSidebar(Scoreboard scBrd, Chat title, List<Chat> lines) {
Component[] cmpLines = new Component[lines.size()];
for (int i = 0; i < cmpLines.length; i++) {
cmpLines[i] = lines.get(i).getAdv();
}
updateScoreboardSidebar(scBrd, title.getAdv(), cmpLines);
}
}

View File

@@ -0,0 +1,191 @@
package fr.pandacube.lib.paper.util;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import fr.pandacube.lib.chat.Chat;
/**
* Represents some special mob heads, also support creating player skulls and custom skulls.
*
* @author xigsag, SBPrime
*
* @see <a href="https://github.com/TigerHix/Hex-Utils/blob/9954159a323d12733b29c287a56980991cee2948/hex/util/Skull.java">github.com/TigerHix/Hex-Utils/hex/util/Skull.java</a>
*/
public enum Skull {
ARROW_LEFT("MHF_ArrowLeft"),
ARROW_RIGHT("MHF_ArrowRight"),
ARROW_UP("MHF_ArrowUp"),
ARROW_DOWN("MHF_ArrowDown"),
QUESTION("MHF_Question"),
EXCLAMATION("MHF_Exclamation"),
CAMERA("FHG_Cam"),
ZOMBIE_PIGMAN("MHF_PigZombie"),
PIG("MHF_Pig"),
SHEEP("MHF_Sheep"),
BLAZE("MHF_Blaze"),
CHICKEN("MHF_Chicken"),
COW("MHF_Cow"),
SLIME("MHF_Slime"),
SPIDER("MHF_Spider"),
SQUID("MHF_Squid"),
VILLAGER("MHF_Villager"),
OCELOT("MHF_Ocelot"),
HEROBRINE("MHF_Herobrine"),
LAVA_SLIME("MHF_LavaSlime"),
MOOSHROOM("MHF_MushroomCow"),
GOLEM("MHF_Golem"),
GHAST("MHF_Ghast"),
ENDERMAN("MHF_Enderman"),
CAVE_SPIDER("MHF_CaveSpider"),
CACTUS("MHF_Cactus"),
CAKE("MHF_Cake"),
CHEST("MHF_Chest"),
MELON("MHF_Melon"),
LOG("MHF_OakLog"),
PUMPKIN("MHF_Pumpkin"),
TNT("MHF_TNT"),
DYNAMITE("MHF_TNT2");
private final String name;
Skull(String mcName) {
name = mcName;
}
/**
* Return the item based on this Skull enum.
*
* @return itemstack
*/
public ItemStack get() {
return get(null, null);
}
/**
* Return the item based on this Skull enum, with the provided display name and lore.
*
* @return itemstack
*/
public ItemStack get(Chat dispName, List<Chat> lore) {
return getFromPlayerName(name, dispName, lore);
}
/**
* Return a skull of a player based on his name.
*
* @param name player's name
* @return itemstack
*/
public static ItemStack getFromPlayerName(String name, Chat dispName, List<Chat> lore) {
ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD, 1);
SkullMeta meta = (SkullMeta) itemStack.getItemMeta();
@SuppressWarnings({ "deprecation", "unused" })
boolean b = meta.setOwner(name);
if (dispName != null)
meta.displayName(dispName.getAdv());
if (lore != null)
meta.lore(lore.stream().map(Chat::getAdv).collect(Collectors.toList()));
itemStack.setItemMeta(meta);
return itemStack;
}
/**
* Return a skull that has a custom texture specified by url.
*
* @param url skin url
* @return itemstack
*/
public static ItemStack getFromSkinURL(String url) {
return getFromSkinURL(url, null, null);
}
/**
* Return a skull that has a custom texture specified by url.
*
* @param url skin url
* @return itemstack
*/
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);
}
/**
* Return a skull that has a custom texture specified by a base64 String.
*
* @param str the base64 string from gameprofile informations
* @return itemstack
*/
public static ItemStack getFromBase64String(String str) {
return getFromBase64String(str, null, null);
}
/**
* Return a skull that has a custom texture specified by a base64 String.
*
* @param str the base64 string from gameprofile informations
* @return itemstack
*/
public static ItemStack getFromBase64String(String str, Chat dispName, List<Chat> lore) {
ItemStack head = new ItemStack(Material.PLAYER_HEAD, 1);
SkullMeta headMeta = (SkullMeta) head.getItemMeta();
PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID());
profile.setProperty(new ProfileProperty("textures", str));
headMeta.setPlayerProfile(profile);
if (dispName != null)
headMeta.displayName(dispName.getAdv());
if (lore != null)
headMeta.lore(lore.stream().map(Chat::getAdv).collect(Collectors.toList()));
head.setItemMeta(headMeta);
return head;
}
}