Refactoring
This commit is contained in:
20
Paper/.classpath
Normal file
20
Paper/.classpath
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
1
Paper/.gitignore
vendored
Normal file
1
Paper/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
23
Paper/.project
Normal file
23
Paper/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>pandalib-paper</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
3
Paper/.settings/org.eclipse.core.resources.prefs
Normal file
3
Paper/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,3 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding/<project>=UTF-8
|
8
Paper/.settings/org.eclipse.jdt.core.prefs
Normal file
8
Paper/.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,8 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
|
||||
org.eclipse.jdt.core.compiler.compliance=11
|
||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=11
|
4
Paper/.settings/org.eclipse.m2e.core.prefs
Normal file
4
Paper/.settings/org.eclipse.m2e.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
38
Paper/pom.xml
Normal file
38
Paper/pom.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pandalib-paper</artifactId>
|
||||
|
||||
<name>PandaLib-Paper</name>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Paper -->
|
||||
<dependency>
|
||||
<groupId>com.destroystokyo.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>${paper.version}-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@@ -0,0 +1,17 @@
|
||||
package fr.pandacube.lib.paper;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class PandaLibPaper {
|
||||
|
||||
private static Plugin plugin;
|
||||
|
||||
public static void init(Plugin plugin) {
|
||||
PandaLibPaper.plugin = plugin;
|
||||
}
|
||||
|
||||
public static Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
}
|
224
Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java
Normal file
224
Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIHotBar.java
Normal file
@@ -0,0 +1,224 @@
|
||||
package fr.pandacube.lib.paper.gui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
|
||||
import fr.pandacube.lib.core.util.Log;
|
||||
import fr.pandacube.lib.paper.util.BukkitEvent;
|
||||
|
||||
/**
|
||||
* Managed a « lobby » type hotbar menu/inventory. It represents items in the player inventory on which you can right click on it.
|
||||
* The player can't move or drop these items.
|
||||
*
|
||||
*/
|
||||
public class GUIHotBar implements Listener {
|
||||
|
||||
private Map<ItemStack, BiConsumer<PlayerInventory, ItemStack>> itemsAndSetters = new HashMap<>();
|
||||
|
||||
private Map<ItemStack, Consumer<Player>> itemsAndRunnables = new HashMap<>();
|
||||
|
||||
private final int defltSlot;
|
||||
|
||||
private List<Player> currentPlayers = new ArrayList<>();
|
||||
|
||||
public GUIHotBar(int defaultSlot) {
|
||||
defltSlot = Math.max(0, Math.min(8, defaultSlot));
|
||||
|
||||
BukkitEvent.register(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the item to this hotbar menu. if there is already players hooked to this hotbar, the item will be directly added to
|
||||
* their inventories.
|
||||
* @param i the item stack
|
||||
* @param setter code executed to put the item in the inventory. Additionally check for permission before doing the addition.
|
||||
* @param run the Runnable to run when the user right click on the item in the hotbar.
|
||||
* @return
|
||||
*/
|
||||
public GUIHotBar addItem(ItemStack i, BiConsumer<PlayerInventory, ItemStack> setter, Consumer<Player> run) {
|
||||
itemsAndSetters.put(i, setter);
|
||||
itemsAndRunnables.put(i, run);
|
||||
|
||||
for (Player p : currentPlayers)
|
||||
addItemToPlayer(p, i);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the hotbar elements to this player.
|
||||
*
|
||||
* The players is automatically removed when it quit. You can remove it before by calling {@link #removePlayer(Player)}.
|
||||
* @param p
|
||||
*/
|
||||
public void addPlayer(Player p) {
|
||||
if (!currentPlayers.contains(p))
|
||||
currentPlayers.add(p);
|
||||
|
||||
for (ItemStack is : itemsAndSetters.keySet()) {
|
||||
addItemToPlayer(p, is);
|
||||
}
|
||||
|
||||
p.getInventory().setHeldItemSlot(defltSlot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach this player from this hotbar manager and removes the managed items from the players inventory.
|
||||
* @param p
|
||||
*/
|
||||
public void removePlayer(Player p) {
|
||||
if (!currentPlayers.contains(p))
|
||||
return;
|
||||
|
||||
for (ItemStack is : itemsAndSetters.keySet()) {
|
||||
removeItemFromPlayer(p, is);
|
||||
}
|
||||
|
||||
currentPlayers.remove(p);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean containsPlayer(Player p) {
|
||||
return currentPlayers.contains(p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void removeAllPlayers() {
|
||||
for (Player p : new ArrayList<>(currentPlayers))
|
||||
removePlayer(p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void addItemToPlayer(Player p, ItemStack is) {
|
||||
if (!itemsAndSetters.containsKey(is))
|
||||
throw new IllegalArgumentException("The provided ItemStack is not registered in this HotbarMenu");
|
||||
if (!currentPlayers.contains(p))
|
||||
throw new IllegalArgumentException("The provided Player is not registered in this HotbarMenu");
|
||||
itemsAndSetters.get(is).accept(p.getInventory(), is.clone());
|
||||
}
|
||||
|
||||
public void removeItemFromPlayer(Player p, ItemStack is) {
|
||||
p.getInventory().remove(is);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
ItemStack item = event.getItemDrop().getItemStack();
|
||||
for (ItemStack managed : itemsAndSetters.keySet()) {
|
||||
if (item != null && item.isSimilar(managed)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR)
|
||||
return;
|
||||
|
||||
ItemStack item = event.getItem();
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
Player p = event.getPlayer();
|
||||
|
||||
for (ItemStack is : itemsAndRunnables.keySet()) {
|
||||
if (item.isSimilar(is)) {
|
||||
try {
|
||||
itemsAndRunnables.get(is).accept(p);
|
||||
} catch (Exception e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (event.getClickedInventory() == null || !(event.getClickedInventory() instanceof PlayerInventory))
|
||||
return;
|
||||
|
||||
PlayerInventory inv = (PlayerInventory) event.getClickedInventory();
|
||||
|
||||
if (!currentPlayers.contains(inv.getHolder()))
|
||||
return;
|
||||
|
||||
ItemStack item = event.getCurrentItem();
|
||||
|
||||
for (ItemStack is : itemsAndSetters.keySet()) {
|
||||
if (item != null && item.isSimilar(is)) {
|
||||
try {
|
||||
itemsAndRunnables.get(is).accept((Player) inv.getHolder());
|
||||
} catch (Exception e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
removePlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||
if (!currentPlayers.contains(event.getEntity()))
|
||||
return;
|
||||
event.getDrops().removeAll(itemsAndSetters.keySet());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
currentPlayers.remove(event.getPlayer());
|
||||
addPlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
|
||||
}
|
251
Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java
Normal file
251
Paper/src/main/java/fr/pandacube/lib/paper/gui/GUIInventory.java
Normal file
@@ -0,0 +1,251 @@
|
||||
package fr.pandacube.lib.paper.gui;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import fr.pandacube.lib.core.chat.Chat;
|
||||
import fr.pandacube.lib.core.util.Callback;
|
||||
import fr.pandacube.lib.paper.util.ItemStackBuilder;
|
||||
|
||||
public class GUIInventory implements Listener {
|
||||
|
||||
public static final Map<Enchantment, Integer> FAKE_ENCHANT = ImmutableMap.of(Enchantment.DURABILITY, 1);
|
||||
|
||||
private Player player;
|
||||
private Inventory inv;
|
||||
private Callback<InventoryCloseEvent> onCloseEvent;
|
||||
private boolean isOpened = false;
|
||||
private Map<Integer, Callback<InventoryClickEvent>> onClickEvents;
|
||||
|
||||
public GUIInventory(Player p, int nbLines, String title, Callback<InventoryCloseEvent> closeEventAction,
|
||||
Plugin pl) {
|
||||
inv = Bukkit.createInventory(null, nbLines * 9, title);
|
||||
|
||||
setCloseEvent(closeEventAction);
|
||||
|
||||
onClickEvents = new HashMap<>();
|
||||
|
||||
player = p;
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(this, pl);
|
||||
|
||||
}
|
||||
|
||||
protected void setCloseEvent(Callback<InventoryCloseEvent> closeEventAction) {
|
||||
onCloseEvent = closeEventAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clickEventActions (l'event passé en paramètre de la méthode done a
|
||||
* été pré-annulée. Pour la rétablir, faites un
|
||||
* event.setCancelled(false)).
|
||||
*/
|
||||
public void setButtonIfEmpty(int p, ItemStack iStack, Callback<InventoryClickEvent> clickEventActions) {
|
||||
if (inv.getItem(p) == null)
|
||||
setButton(p, iStack, clickEventActions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clickEventActions (l'event passé en paramètre de la méthode done a
|
||||
* été pré-annulée. Pour la rétablir, faites un
|
||||
* event.setCancelled(false)).
|
||||
*/
|
||||
public void setButton(int p, ItemStack iStack, Callback<InventoryClickEvent> clickEventActions) {
|
||||
inv.setItem(p, iStack);
|
||||
changeClickEventAction(p, clickEventActions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clickEventActions (l'event passé en paramètre de la méthode done a
|
||||
* été pré-annulée. Pour la rétablir, faites un
|
||||
* event.setCancelled(false)).
|
||||
*/
|
||||
public void changeClickEventAction(int p, Callback<InventoryClickEvent> clickEventActions) {
|
||||
onClickEvents.put(p, clickEventActions);
|
||||
}
|
||||
|
||||
public ItemStack getItemStack(int p) {
|
||||
return inv.getItem(p);
|
||||
}
|
||||
|
||||
public void open() {
|
||||
if (isOpened) return;
|
||||
player.openInventory(inv);
|
||||
isOpened = true;
|
||||
}
|
||||
|
||||
public void forceClose() {
|
||||
if (!isOpened) return;
|
||||
player.closeInventory(); // internally calls the InventoryCloseEvent
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return isOpened;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
onClickEvents.clear();
|
||||
inv.clear();
|
||||
}
|
||||
|
||||
public void clear(int firstElement, int nbElement) {
|
||||
for (int i = firstElement; i < firstElement + nbElement; i++) {
|
||||
inv.setItem(i, null);
|
||||
onClickEvents.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
public Inventory getInventory() {
|
||||
return inv;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (!event.getWhoClicked().equals(player)) return;
|
||||
if (!isOpened) return;
|
||||
if (!event.getView().getTopInventory().equals(inv)) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
// on ne réagit pas aux clics hors de l'inventaire du dessus.
|
||||
if (event.getClickedInventory() != event.getView().getTopInventory()) return;
|
||||
|
||||
Callback<InventoryClickEvent> callback = onClickEvents.get(event.getSlot());
|
||||
if (callback != null) callback.done(event);
|
||||
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClose(InventoryCloseEvent event) {
|
||||
if (!event.getPlayer().equals(player)) return;
|
||||
if (!isOpened) return;
|
||||
if (!event.getView().getTopInventory().equals(inv)) return;
|
||||
|
||||
HandlerList.unregisterAll(this);
|
||||
|
||||
if (onCloseEvent != null)
|
||||
onCloseEvent.done(event);
|
||||
isOpened = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static ItemStack buildButton(ItemStack base, Integer amount, Chat displayName,
|
||||
List<Chat> lores, Map<Enchantment, Integer> enchantments) {
|
||||
|
||||
ItemStackBuilder iStackBuilder = ItemStackBuilder.of(base);
|
||||
|
||||
if (amount != null)
|
||||
iStackBuilder.amount(amount);
|
||||
if (displayName != null)
|
||||
iStackBuilder.displayName(displayName);
|
||||
if (lores != null)
|
||||
iStackBuilder.lore(lores);
|
||||
if (enchantments != null) {
|
||||
if (enchantments == FAKE_ENCHANT)
|
||||
iStackBuilder.fakeEnchant();
|
||||
else {
|
||||
for (Entry<Enchantment, Integer> e : enchantments.entrySet()) {
|
||||
iStackBuilder.enchant(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iStackBuilder.build();
|
||||
}
|
||||
public static ItemStack buildButton(ItemStack base, Chat displayName, List<Chat> lores, Map<Enchantment, Integer> enchantments) {
|
||||
return buildButton(base, null, displayName, lores, enchantments);
|
||||
}
|
||||
public static ItemStack buildButton(ItemStack base, Integer amount, Chat displayName, Map<Enchantment, Integer> enchantments) {
|
||||
return buildButton(base, amount, displayName, null, enchantments);
|
||||
}
|
||||
public static ItemStack buildButton(ItemStack base, Chat displayName, Map<Enchantment, Integer> enchantments) {
|
||||
return buildButton(base, null, displayName, null, enchantments);
|
||||
}
|
||||
public static ItemStack buildButton(ItemStack base, Integer amount, Chat displayName, List<Chat> lores) {
|
||||
return buildButton(base, amount, displayName, lores, null);
|
||||
}
|
||||
public static ItemStack buildButton(ItemStack base, Chat displayName, List<Chat> lores) {
|
||||
return buildButton(base, null, displayName, lores, null);
|
||||
}
|
||||
public static ItemStack buildButton(ItemStack base, Integer amount, Chat displayName) {
|
||||
return buildButton(base, amount, displayName, null, null);
|
||||
}
|
||||
public static ItemStack buildButton(ItemStack base, Chat displayName) {
|
||||
return buildButton(base, null, displayName, null, null);
|
||||
}
|
||||
public static ItemStack buildButton(ItemStack base, Integer amount) {
|
||||
return buildButton(base, amount, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static ItemStack buildButton(Material m, int amount, Chat displayName, List<Chat> lores, Map<Enchantment, Integer> enchantments) {
|
||||
return buildButton(new ItemStack(m, amount), displayName, lores, enchantments);
|
||||
}
|
||||
public static ItemStack buildButton(Material m, Chat displayName, List<Chat> lores, Map<Enchantment, Integer> enchantments) {
|
||||
return buildButton(m, 1, displayName, lores, enchantments);
|
||||
}
|
||||
public static ItemStack buildButton(Material m, int amount, Chat displayName, Map<Enchantment, Integer> enchantments) {
|
||||
return buildButton(m, amount, displayName, null, enchantments);
|
||||
}
|
||||
public static ItemStack buildButton(Material m, Chat displayName, Map<Enchantment, Integer> enchantments) {
|
||||
return buildButton(m, 1, displayName, null, enchantments);
|
||||
}
|
||||
public static ItemStack buildButton(Material m, int amount, Chat displayName, List<Chat> lores) {
|
||||
return buildButton(m, amount, displayName, lores, null);
|
||||
}
|
||||
public static ItemStack buildButton(Material m, Chat displayName, List<Chat> lores) {
|
||||
return buildButton(m, 1, displayName, lores, null);
|
||||
}
|
||||
public static ItemStack buildButton(Material m, int amount, Chat displayName) {
|
||||
return buildButton(m, amount, displayName, null, null);
|
||||
}
|
||||
public static ItemStack buildButton(Material m, Chat displayName) {
|
||||
return buildButton(m, 1, displayName, null, null);
|
||||
}
|
||||
public static ItemStack buildButton(Material m, int amount) {
|
||||
return buildButton(m, amount, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static int nbLineForNbElements(int nb) {
|
||||
return nb / 9 + ((nb % 9 == 0) ? 0 : 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
100
Paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java
Normal file
100
Paper/src/main/java/fr/pandacube/lib/paper/util/AABBBlock.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import fr.pandacube.lib.core.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;
|
||||
|
||||
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(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.
|
||||
*/
|
||||
pos1 = new Vector(Math.min(p1x, p2x),
|
||||
Math.min(p1y, p2y),
|
||||
Math.min(p1z, p2z));
|
||||
pos2 = new Vector(Math.max(p1x, p2x) + 1,
|
||||
Math.max(p1y, p2y) + 1,
|
||||
Math.max(p1z, p2z) + 1);
|
||||
}
|
||||
|
||||
|
||||
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 pos1.clone().add(pos2).multiply(0.5);
|
||||
}
|
||||
|
||||
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<BlockVector>() {
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,199 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BossBar;
|
||||
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.core.util.Log;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
|
||||
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 - " + ChatColor.stripColor(bar.getTitle()));
|
||||
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 == true)
|
||||
return;
|
||||
followPlayerList = true;
|
||||
BukkitEvent.register(this);
|
||||
Bukkit.getServer().getOnlinePlayers().forEach(p -> {
|
||||
onPlayerJoin(new PlayerJoinEvent(p, ""));
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized void unfollowPlayerList() {
|
||||
if (followPlayerList == false)
|
||||
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 == false)
|
||||
return;
|
||||
if (playerCondition != null && !playerCondition.test(event.getPlayer()))
|
||||
return;
|
||||
synchronized (bar) {
|
||||
bar.addPlayer(event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority=EventPriority.HIGH)
|
||||
public synchronized void onPlayerQuit(PlayerQuitEvent event) {
|
||||
if (followPlayerList == false)
|
||||
return;
|
||||
synchronized (bar) {
|
||||
bar.removePlayer(event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
public void update(AutoUpdatedBossBar bar);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Utility method to update the title of the bossbar without unnecessary packet.
|
||||
* @param title
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
synchronized (bar) {
|
||||
if (!Objects.equals(title, bar.getTitle()))
|
||||
bar.setTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to update the color of the bossbar without unnecessary packet.
|
||||
* @param title
|
||||
*/
|
||||
public void setColor(BarColor color) {
|
||||
synchronized (bar) {
|
||||
if (color != bar.getColor())
|
||||
bar.setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to update the progress of the bossbar without unnecessary packet.
|
||||
* @param title
|
||||
*/
|
||||
public void setProgress(double progress) {
|
||||
synchronized (bar) {
|
||||
if (progress != bar.getProgress())
|
||||
bar.setProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import org.bukkit.DyeColor;
|
||||
|
||||
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
|
||||
switch (dye) {
|
||||
case BLACK:
|
||||
return ChatColor.of("#000000");
|
||||
case RED:
|
||||
return ChatColor.of("#650000");
|
||||
case GREEN:
|
||||
return ChatColor.of("#006500");
|
||||
case BROWN:
|
||||
return ChatColor.of("#361B07");
|
||||
case BLUE:
|
||||
return ChatColor.of("#000065");
|
||||
case PURPLE:
|
||||
return ChatColor.of("#3F0C5F");
|
||||
case CYAN:
|
||||
return ChatColor.of("#006565");
|
||||
case LIGHT_GRAY:
|
||||
return ChatColor.of("#535353");
|
||||
case GRAY:
|
||||
return ChatColor.of("#323232");
|
||||
case PINK:
|
||||
return ChatColor.of("#652947");
|
||||
case LIME:
|
||||
return ChatColor.of("#4B6500");
|
||||
case YELLOW:
|
||||
return ChatColor.of("#656500");
|
||||
case LIGHT_BLUE:
|
||||
return ChatColor.of("#3C4B51");
|
||||
case MAGENTA:
|
||||
return ChatColor.of("#650065");
|
||||
case ORANGE:
|
||||
return ChatColor.of("#65280C");
|
||||
case WHITE:
|
||||
return ChatColor.of("#656565");
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown DyeColor: " + dye);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static org.bukkit.ChatColor fromBungeeToBukkit(ChatColor color) {
|
||||
return org.bukkit.ChatColor.valueOf(color.getName().toUpperCase());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,97 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
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.IllegalPluginAccessException;
|
||||
|
||||
import fr.pandacube.lib.core.util.ReflexionUtil;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
|
||||
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 = ReflexionUtil.getAllSubclasses(Event.class);
|
||||
classes.removeIf(e -> getHandlerList(e) == null);
|
||||
return classes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// method retrieved from OB.plugin.SimplePluginManager#getEventListeners
|
||||
public static HandlerList getHandlerList(Class<? extends Event> type) throws IllegalPluginAccessException {
|
||||
try {
|
||||
Method method = getRegistrationClass(type).getDeclaredMethod("getHandlerList", new Class[0]);
|
||||
method.setAccessible(true);
|
||||
return (HandlerList)method.invoke(null, new Object[0]);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// method retrieved from OB.plugin.SimplePluginManager
|
||||
private static Class<? extends Event> getRegistrationClass(Class<? extends Event> clazz) throws IllegalPluginAccessException {
|
||||
try {
|
||||
clazz.getDeclaredMethod("getHandlerList", new Class[0]);
|
||||
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 {
|
||||
|
||||
public abstract void onEvent(E event);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
default void execute(Listener var1, Event var2) throws EventException {
|
||||
onEvent((E)var2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Color;
|
||||
|
||||
public class ColorUtil {
|
||||
|
||||
/*
|
||||
* Rainbow
|
||||
*/
|
||||
private static 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
|
||||
* @return
|
||||
*/
|
||||
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()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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é
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* A utility for managing Player experience properly.<br/>
|
||||
* https://gist.github.com/Jikoo/30ec040443a4701b8980
|
||||
*
|
||||
* @author Jikoo
|
||||
*/
|
||||
public class ExperienceUtil {
|
||||
|
||||
/**
|
||||
* Calculates a player's total exp based on level and progress to next.
|
||||
*
|
||||
* @see http://minecraft.gamepedia.com/Experience#Leveling_up
|
||||
*
|
||||
* @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 http://minecraft.gamepedia.com/Experience#Leveling_up
|
||||
*
|
||||
* "One can determine how much experience has been collected to reach a
|
||||
* level using the equations:
|
||||
*
|
||||
* Total Experience = [Level]2 + 6[Level] (at levels 0-15)
|
||||
* 2.5[Level]2 - 40.5[Level] + 360 (at levels 16-30)
|
||||
* 4.5[Level]2 - 162.5[Level] + 2220 (at level 31+)"
|
||||
*
|
||||
* @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 http://minecraft.gamepedia.com/Experience#Leveling_up
|
||||
*
|
||||
* "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));
|
||||
}
|
||||
|
||||
}
|
@@ -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.core.util.BiMap;
|
||||
import fr.pandacube.lib.core.util.FileUtils;
|
||||
import fr.pandacube.lib.core.util.Log;
|
||||
import fr.pandacube.lib.core.util.RandomUtil;
|
||||
|
||||
public class GameWorldUtils implements Listener {
|
||||
|
||||
private static 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,422 @@
|
||||
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>.
|
||||
* @param start
|
||||
* @param end
|
||||
* @param min
|
||||
* @param max
|
||||
* @return
|
||||
*/
|
||||
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.
|
||||
* @param l
|
||||
*/
|
||||
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
|
||||
* @param l
|
||||
*/
|
||||
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()}
|
||||
* @return
|
||||
*/
|
||||
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.
|
||||
* @return
|
||||
*/
|
||||
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.
|
||||
* @return
|
||||
*/
|
||||
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.
|
||||
* @return
|
||||
*/
|
||||
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.
|
||||
* @return
|
||||
*/
|
||||
public DirectionalVector getRightDirection() {
|
||||
return new DirectionalVector(
|
||||
yaw < PId2 ? (yaw + PId2) : (yaw + PId2 - PIx2),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
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.core.chat.Chat;
|
||||
import fr.pandacube.lib.core.chat.Chat.FormatableChat;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
public class ItemStackBuilder {
|
||||
|
||||
public static ItemStackBuilder of(ItemStack base) {
|
||||
return new ItemStackBuilder(base.clone());
|
||||
}
|
||||
|
||||
public static ItemStackBuilder of(Material mat) {
|
||||
return new ItemStackBuilder(new ItemStack(mat));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private 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(BaseComponent[] displayName) {
|
||||
if (displayName != null)
|
||||
getOrInitMeta().setDisplayNameComponent(displayName);
|
||||
else
|
||||
getOrInitMeta().setDisplayName(null);
|
||||
updateMeta();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemStackBuilder displayName(Chat displayName) {
|
||||
if (displayName != null) {
|
||||
if (displayName.get().isItalicRaw() == null)
|
||||
((FormatableChat)displayName).italic(false);
|
||||
return rawDisplayName(displayName.getAsArray());
|
||||
}
|
||||
else
|
||||
return rawDisplayName(null);
|
||||
}
|
||||
|
||||
public ItemStackBuilder rawLore(List<BaseComponent[]> lore) {
|
||||
getOrInitMeta().setLoreComponents(lore);
|
||||
updateMeta();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemStackBuilder lore(List<Chat> lore) {
|
||||
if (lore != null) {
|
||||
return rawLore(lore.stream()
|
||||
.map(line -> {
|
||||
if (line.get().isItalicRaw() == null)
|
||||
((FormatableChat)line).italic(false);
|
||||
return line.getAsArray();
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
else
|
||||
return rawLore(Collections.emptyList());
|
||||
}
|
||||
|
||||
public ItemStackBuilder addLoreAfter(List<Chat> lore) {
|
||||
if (lore != null) {
|
||||
List<BaseComponent[]> baseLore = getOrInitMeta().getLoreComponents();
|
||||
if (baseLore == null) baseLore = Collections.emptyList();
|
||||
return rawLore(
|
||||
Streams.concat(
|
||||
baseLore.stream(),
|
||||
lore.stream()
|
||||
.map(line -> {
|
||||
if (line.get().isItalicRaw() == null)
|
||||
((FormatableChat)line).italic(false);
|
||||
return line.getAsArray();
|
||||
})
|
||||
)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
else
|
||||
return this;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.scoreboard.DisplaySlot;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.Score;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
|
||||
import fr.pandacube.lib.core.chat.ChatUtil;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
|
||||
public class ScoreBoardUtil {
|
||||
|
||||
/**
|
||||
* Met à jour la Sidebar d'un Scoreboard donné
|
||||
*
|
||||
* @param scBrd Le Scoreboard à mettre à jour (ne doit pas être null)
|
||||
* @param title Le titre de la Sidebar, limité à 32 caractères
|
||||
* @param lines Les lignes qui doivent être affichés. Si un éléments du
|
||||
* tableau est null, il sera compté comme une chaine vide. Toutes les
|
||||
* lignes seront rognés aux 40 premiers caractères
|
||||
*/
|
||||
public static void updateScoreboardSidebar(Scoreboard scBrd, String title, String[] lines) {
|
||||
if (scBrd == null) throw new IllegalArgumentException("scBrd doit être non null");
|
||||
if (lines == null) lines = new String[0];
|
||||
|
||||
Objective obj = scBrd.getObjective("sidebar_autogen");
|
||||
if (obj != null && !obj.getCriteria().equalsIgnoreCase("dummy")) {
|
||||
obj.unregister();
|
||||
obj = null;
|
||||
}
|
||||
|
||||
title = title == null ? "" : ChatUtil.truncateAtLengthWithoutReset(title, 32);
|
||||
|
||||
if (obj == null)
|
||||
obj = scBrd.registerNewObjective("sidebar_autogen", "dummy", title);
|
||||
|
||||
if (!title.equals(obj.getDisplayName()))
|
||||
obj.setDisplayName(title);
|
||||
if (!DisplaySlot.SIDEBAR.equals(obj.getDisplaySlot()))
|
||||
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
|
||||
filterLines(lines);
|
||||
|
||||
List<String> listLines = Arrays.asList(lines);
|
||||
|
||||
// remove lines that are not in the array
|
||||
Objective fObj = obj;
|
||||
scBrd.getEntries().stream()
|
||||
.filter(e -> !listLines.contains(e))
|
||||
.filter(e -> fObj.getScore(e).isScoreSet())
|
||||
.forEach(scBrd::resetScores);
|
||||
|
||||
// add/update others lines
|
||||
int boardPos = lines.length;
|
||||
for (String line : lines) {
|
||||
if (line == null) line = "";
|
||||
|
||||
Score score = obj.getScore(line);
|
||||
|
||||
if (score.getScore() != boardPos)
|
||||
score.setScore(boardPos);
|
||||
|
||||
boardPos--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la Sidebar d'un Scoreboard donné
|
||||
*
|
||||
* @param scBrd Le Scoreboard à mettre à jour
|
||||
* @param title Le titre de la Sidebar, limité à 32 caractères
|
||||
* @param lines Les lignes qui doivent être affichés. Si un éléments du
|
||||
* tableau est null, il sera compté comme une chaine vide. Toutes les
|
||||
* lignes seront rognés aux 40 premiers caractères
|
||||
*/
|
||||
public static void updateScoreboardSidebar(Scoreboard scBrd, String title, List<String> lines) {
|
||||
updateScoreboardSidebar(scBrd, title, lines.toArray(new String[lines.size()]));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void filterLines(String[] lines) {
|
||||
List<String> previous = new ArrayList<>();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
String line = lines[i] == null ? "" : ChatUtil.truncateAtLengthWithoutReset(lines[i], 40);
|
||||
if (previous.contains(line)) {
|
||||
for (ChatColor c : ChatColor.values()) {
|
||||
line = ChatUtil.truncateAtLengthWithoutReset(lines[i], 38) + c;
|
||||
if (!previous.contains(line)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lines[i] = line;
|
||||
previous.add(lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
191
Paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java
Normal file
191
Paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java
Normal 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.core.chat.Chat;
|
||||
|
||||
/**
|
||||
* Represents some special mob heads, also support creating player skulls and custom skulls.
|
||||
*
|
||||
* @author xigsag, SBPrime
|
||||
*
|
||||
* @see https://github.com/TigerHix/Hex-Utils/blob/9954159a323d12733b29c287a56980991cee2948/hex/util/Skull.java
|
||||
*/
|
||||
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 String name;
|
||||
|
||||
private 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.setDisplayNameComponent(dispName.getAsArray());
|
||||
|
||||
if (lore != null)
|
||||
meta.setLoreComponents(lore.stream().map(c -> c.getAsArray()).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.setDisplayNameComponent(dispName.getAsArray());
|
||||
|
||||
if (lore != null)
|
||||
headMeta.setLoreComponents(lore.stream().map(c -> c.getAsArray()).collect(Collectors.toList()));
|
||||
|
||||
head.setItemMeta(headMeta);
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,35 @@
|
||||
package fr.pandacube.lib.paper.util;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
|
||||
public class ThreadUtil {
|
||||
|
||||
public static void runOnServerThread(Runnable task) {
|
||||
if (Bukkit.isPrimaryThread())
|
||||
task.run();
|
||||
|
||||
Bukkit.getScheduler().runTask(PandaLibPaper.getPlugin(), task);
|
||||
}
|
||||
|
||||
public static <T> T runOnServerThreadAndWait(Callable<T> task) throws Exception {
|
||||
if (Bukkit.isPrimaryThread())
|
||||
return task.call();
|
||||
|
||||
return Bukkit.getScheduler().callSyncMethod(PandaLibPaper.getPlugin(), task).get();
|
||||
}
|
||||
|
||||
public static void runOnServerThreadAndWait(Runnable task) throws Exception {
|
||||
runOnServerThreadAndWait((Callable<Void>)() -> {
|
||||
task.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user