Compare commits
22 Commits
3e6cf96040
...
medrias
Author | SHA1 | Date | |
---|---|---|---|
27c444f3b4 | |||
c589da2a14 | |||
3fe4a1b244 | |||
1925dd9b36 | |||
d637b92f6c | |||
af4bab0d12 | |||
44dc690736 | |||
c9af5ad308 | |||
27403a6e20 | |||
38a42dcca0 | |||
5782046b7a | |||
2b407d7f27 | |||
8f5f880754 | |||
3d92c3afb6 | |||
5e1f98ab70 | |||
276f5b2dc1 | |||
9c72b8cda4 | |||
ee023b5d2c | |||
974347cbde | |||
e6b77bcad6 | |||
36eb1227cf | |||
4be37945cb |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
/.idea
|
/.idea
|
||||||
/*/target
|
/*/target
|
||||||
dependency-reduced-pom.xml
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
*.iml
|
@@ -18,8 +18,9 @@ that are detailed in their respective Readme file (if any).
|
|||||||
- `pandalib-players` A library to handle classes representing online or offline players;
|
- `pandalib-players` A library to handle classes representing online or offline players;
|
||||||
- `pandalib-players-permissible` An extension of `pandalib-players` with support for the permission system `pandalib-permissions`;
|
- `pandalib-players-permissible` An extension of `pandalib-players` with support for the permission system `pandalib-permissions`;
|
||||||
- `pandalib-netapi` A poorly designed, but working TCP network library;
|
- `pandalib-netapi` A poorly designed, but working TCP network library;
|
||||||
|
- `pandalib-config` Utility and helper classes to handle configuration related files and folders;
|
||||||
- `pandalib-commands` An abstract command manager working on top of [Brigadier](https://github.com/Mojang/brigadier);
|
- `pandalib-commands` An abstract command manager working on top of [Brigadier](https://github.com/Mojang/brigadier);
|
||||||
- `pandalib-cli` Utility and helper classes for a standalone CLI Java application.
|
- `pandalib-cli` Utility and helper classes for a standalone CLI Java application;
|
||||||
- `pandalib-core` A catch-all module for some helper classes that didn't have their own module yet;
|
- `pandalib-core` A catch-all module for some helper classes that didn't have their own module yet;
|
||||||
|
|
||||||
### Use in your projects
|
### Use in your projects
|
||||||
|
1
pandalib-bungee-chat/.gitignore
vendored
Normal file
1
pandalib-bungee-chat/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target/
|
49
pandalib-bungee-chat/pom.xml
Normal file
49
pandalib-bungee-chat/pom.xml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<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">
|
||||||
|
<parent>
|
||||||
|
<artifactId>pandalib-parent</artifactId>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>pandalib-bungee-chat</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>bungeecord-repo</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-util</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-chat</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-chat</artifactId>
|
||||||
|
<version>${bungeecord.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.kyori</groupId>
|
||||||
|
<artifactId>adventure-platform-bungeecord</artifactId>
|
||||||
|
<version>4.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@@ -0,0 +1,77 @@
|
|||||||
|
package fr.pandacube.lib.bungee.chat;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.chat.Chat;
|
||||||
|
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
|
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to ease conversion between our Adventure backed Chat API and BungeeCord chat API.
|
||||||
|
*/
|
||||||
|
public class ChatBungee {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link FormatableChat} from the provided Bungee {@link BaseComponent}.
|
||||||
|
* @param c the {@link BaseComponent}.
|
||||||
|
* @return a new {@link FormatableChat}.
|
||||||
|
*/
|
||||||
|
public static FormatableChat chatComponent(BaseComponent c) {
|
||||||
|
return Chat.chatComponent(toAdventure(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link FormatableChat} from the provided Bungee {@link BaseComponent BaseComponent[]}.
|
||||||
|
* @param c the array of {@link BaseComponent}.
|
||||||
|
* @return a new {@link FormatableChat}.
|
||||||
|
*/
|
||||||
|
public static FormatableChat chatComponent(BaseComponent[] c) {
|
||||||
|
return Chat.chatComponent(toAdventure(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the Bungee {@link BaseComponent} array into Adventure {@link Component}.
|
||||||
|
* @param components the Bungee {@link BaseComponent} array.
|
||||||
|
* @return a {@link Component}.
|
||||||
|
*/
|
||||||
|
public static Component toAdventure(BaseComponent[] components) {
|
||||||
|
return BungeeComponentSerializer.get().deserialize(components);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Converts the Bungee {@link BaseComponent} into Adventure {@link Component}.
|
||||||
|
* @param component the Bungee {@link BaseComponent}.
|
||||||
|
* @return a {@link Component}.
|
||||||
|
*/
|
||||||
|
public static Component toAdventure(BaseComponent component) {
|
||||||
|
return toAdventure(new BaseComponent[] { component });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the Adventure {@link Component} into Bungee {@link BaseComponent} array.
|
||||||
|
* @param component the Adventure {@link Component}.
|
||||||
|
* @return a {@link BaseComponent} array.
|
||||||
|
*/
|
||||||
|
public static BaseComponent[] toBungeeArray(ComponentLike component) {
|
||||||
|
return BungeeComponentSerializer.get().serialize(component.asComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the Adventure {@link Component} into Bungee {@link BaseComponent}.
|
||||||
|
* @param component the Adventure {@link Component}.
|
||||||
|
* @return a {@link BaseComponent}.
|
||||||
|
*/
|
||||||
|
public static BaseComponent toBungee(ComponentLike component) {
|
||||||
|
BaseComponent[] arr = toBungeeArray(component);
|
||||||
|
return arr.length == 1 ? arr[0] : new net.md_5.bungee.api.chat.TextComponent(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ChatBungee() {}
|
||||||
|
}
|
@@ -33,7 +33,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
<artifactId>pandalib-chat</artifactId>
|
<artifactId>pandalib-bungee-chat</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package fr.pandacube.lib.bungee.commands;
|
package fr.pandacube.lib.bungee.commands;
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
import fr.pandacube.lib.bungee.chat.ChatBungee;
|
||||||
import fr.pandacube.lib.commands.BrigadierDispatcher;
|
import fr.pandacube.lib.commands.BrigadierDispatcher;
|
||||||
import net.kyori.adventure.text.ComponentLike;
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
import net.md_5.bungee.api.CommandSender;
|
import net.md_5.bungee.api.CommandSender;
|
||||||
@@ -71,6 +71,6 @@ public class BungeeBrigadierDispatcher extends BrigadierDispatcher<CommandSender
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendSenderMessage(CommandSender sender, ComponentLike message) {
|
protected void sendSenderMessage(CommandSender sender, ComponentLike message) {
|
||||||
sender.sendMessage(Chat.toBungee(message.asComponent()));
|
sender.sendMessage(ChatBungee.toBungee(message.asComponent()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package fr.pandacube.lib.bungee.players;
|
package fr.pandacube.lib.bungee.players;
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
import fr.pandacube.lib.bungee.chat.ChatBungee;
|
||||||
import fr.pandacube.lib.core.mc_version.ProtocolVersion;
|
import fr.pandacube.lib.core.mc_version.ProtocolVersion;
|
||||||
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
|
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
|
||||||
import fr.pandacube.lib.reflect.Reflect;
|
import fr.pandacube.lib.reflect.Reflect;
|
||||||
@@ -84,13 +84,13 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlaye
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void sendMessage(Component message) {
|
default void sendMessage(Component message) {
|
||||||
getBungeeProxiedPlayer().sendMessage(Chat.toBungee(message));
|
getBungeeProxiedPlayer().sendMessage(ChatBungee.toBungee(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void sendTitle(Component title, Component subtitle, int fadeIn, int stay, int fadeOut) {
|
default void sendTitle(Component title, Component subtitle, int fadeIn, int stay, int fadeOut) {
|
||||||
ProxyServer.getInstance().createTitle()
|
ProxyServer.getInstance().createTitle()
|
||||||
.title(Chat.toBungee(title)).subTitle(Chat.toBungee(subtitle))
|
.title(ChatBungee.toBungee(title)).subTitle(ChatBungee.toBungee(subtitle))
|
||||||
.fadeIn(fadeIn).stay(stay).fadeOut(fadeOut)
|
.fadeIn(fadeIn).stay(stay).fadeOut(fadeOut)
|
||||||
.send(getBungeeProxiedPlayer());
|
.send(getBungeeProxiedPlayer());
|
||||||
}
|
}
|
||||||
|
@@ -30,8 +30,13 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
<artifactId>adventure-platform-bungeecord</artifactId>
|
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.13.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.kyori</groupId>
|
||||||
|
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||||
|
<version>4.13.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.kyori</groupId>
|
<groupId>net.kyori</groupId>
|
||||||
@@ -46,13 +51,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.md-5</groupId>
|
|
||||||
<artifactId>bungeecord-chat</artifactId>
|
|
||||||
<version>${bungeecord.version}</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>com.google.code.gson</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
|
@@ -16,15 +16,13 @@ import net.kyori.adventure.text.format.TextColor;
|
|||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
import net.kyori.adventure.text.format.TextDecoration.State;
|
import net.kyori.adventure.text.format.TextDecoration.State;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
|
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
@@ -67,26 +65,10 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
* Builds the component into Adventure Component instance.
|
* Builds the component into Adventure Component instance.
|
||||||
* @return the {@link Component} built from this {@link Chat} component.
|
* @return the {@link Component} built from this {@link Chat} component.
|
||||||
*/
|
*/
|
||||||
public Component getAdv() {
|
public Component get() {
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the component into BungeeCord {@link BaseComponent} instance.
|
|
||||||
* @return the {@link BaseComponent} built from this {@link Chat} component.
|
|
||||||
*/
|
|
||||||
public BaseComponent get() {
|
|
||||||
return toBungee(getAdv());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the component into BungeeCord {@link BaseComponent} array.
|
|
||||||
* @return the {@link BaseComponent} array built from this {@link Chat} component.
|
|
||||||
*/
|
|
||||||
public BaseComponent[] getAsArray() {
|
|
||||||
return toBungeeArray(getAdv());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final LegacyComponentSerializer LEGACY_SERIALIZER_BUNGEE_FRIENDLY = LegacyComponentSerializer.builder()
|
private static final LegacyComponentSerializer LEGACY_SERIALIZER_BUNGEE_FRIENDLY = LegacyComponentSerializer.builder()
|
||||||
.hexColors()
|
.hexColors()
|
||||||
.useUnusualXRepeatedCharacterHexFormat()
|
.useUnusualXRepeatedCharacterHexFormat()
|
||||||
@@ -97,7 +79,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
* @return the legacy text. RGB colors are in BungeeCord format.
|
* @return the legacy text. RGB colors are in BungeeCord format.
|
||||||
*/
|
*/
|
||||||
public String getLegacyText() {
|
public String getLegacyText() {
|
||||||
return LEGACY_SERIALIZER_BUNGEE_FRIENDLY.serialize(getAdv());
|
return LEGACY_SERIALIZER_BUNGEE_FRIENDLY.serialize(get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,12 +87,12 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
* @return the plain text of this component.
|
* @return the plain text of this component.
|
||||||
*/
|
*/
|
||||||
public String getPlainText() {
|
public String getPlainText() {
|
||||||
return PlainTextComponentSerializer.plainText().serializeOr(getAdv(), "");
|
return PlainTextComponentSerializer.plainText().serializeOr(get(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull HoverEvent<Component> asHoverEvent(@NotNull UnaryOperator<Component> op) {
|
public @NotNull HoverEvent<Component> asHoverEvent(@NotNull UnaryOperator<Component> op) {
|
||||||
return HoverEvent.showText(op.apply(getAdv()));
|
return HoverEvent.showText(op.apply(get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,7 +101,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Component asComponent() {
|
public @NotNull Component asComponent() {
|
||||||
return getAdv();
|
return get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +109,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
* @return the {@link Component} built from this {@link Chat} component, with down-sampled colors.
|
* @return the {@link Component} built from this {@link Chat} component, with down-sampled colors.
|
||||||
*/
|
*/
|
||||||
public Component getAsDownSampledColorsComponent() {
|
public Component getAsDownSampledColorsComponent() {
|
||||||
String json = GsonComponentSerializer.colorDownsamplingGson().serialize(getAdv());
|
String json = GsonComponentSerializer.colorDownsamplingGson().serialize(get());
|
||||||
return GsonComponentSerializer.gson().deserialize(json);
|
return GsonComponentSerializer.gson().deserialize(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +126,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
* @return the MiniMessage representation if this {@link Chat} component.
|
* @return the MiniMessage representation if this {@link Chat} component.
|
||||||
*/
|
*/
|
||||||
public String getMiniMessage() {
|
public String getMiniMessage() {
|
||||||
return MiniMessage.miniMessage().serialize(getAdv());
|
return MiniMessage.miniMessage().serialize(get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -184,15 +166,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a BungeeCord {@link BaseComponent} to this component.
|
|
||||||
* @param comp the component to append.
|
|
||||||
* @return this.
|
|
||||||
*/
|
|
||||||
public Chat then(BaseComponent comp) {
|
|
||||||
return then(toAdventure(comp));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends a component to this component.
|
* Appends a component to this component.
|
||||||
* @param comp the component to append.
|
* @param comp the component to append.
|
||||||
@@ -207,15 +180,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
return then(comp.asComponent());
|
return then(comp.asComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a BungeeCord {@link BaseComponent} array to this component.
|
|
||||||
* @param comp the components to append.
|
|
||||||
* @return this.
|
|
||||||
*/
|
|
||||||
public Chat then(BaseComponent[] comp) {
|
|
||||||
return then(toAdventure(comp));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -495,17 +459,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
*/
|
*/
|
||||||
public Chat thenLeftText(ComponentLike leftText) { return then(leftText(leftText, console)); }
|
public Chat thenLeftText(ComponentLike leftText) { return then(leftText(leftText, console)); }
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a component filling a chat line with the configured decoration character and
|
|
||||||
* color and a left-aligned text.
|
|
||||||
* @param leftText the text aligned to the left.
|
|
||||||
* @return a new {@link FormatableChat} filling a chat line with the configured decoration character
|
|
||||||
* and color and a left-aligned text.
|
|
||||||
* @deprecated uses Bungeecord chat API.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public Chat thenLeftText(BaseComponent leftText) { return thenLeftText(chatComponent(leftText)); }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends a component filling a chat line with the configured decoration character and
|
* Appends a component filling a chat line with the configured decoration character and
|
||||||
* color and a right-aligned text.
|
* color and a right-aligned text.
|
||||||
@@ -515,17 +468,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
*/
|
*/
|
||||||
public Chat thenRightText(ComponentLike rightText) { return then(rightText(rightText, console)); }
|
public Chat thenRightText(ComponentLike rightText) { return then(rightText(rightText, console)); }
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a component filling a chat line with the configured decoration character and
|
|
||||||
* color and a right-aligned text.
|
|
||||||
* @param rightText the text aligned to the right.
|
|
||||||
* @return a new {@link FormatableChat} filling a chat line with the configured decoration character
|
|
||||||
* and color and a right-aligned text.
|
|
||||||
* @deprecated uses Bungeecord chat API.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public Chat thenRightText(BaseComponent rightText) { return thenRightText(chatComponent(rightText)); }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends a component filling a chat line with the configured decoration character and
|
* Appends a component filling a chat line with the configured decoration character and
|
||||||
* color and a centered text.
|
* color and a centered text.
|
||||||
@@ -537,19 +479,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
return then(centerText(centerText, console));
|
return then(centerText(centerText, console));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a component filling a chat line with the configured decoration character and
|
|
||||||
* color and a centered text.
|
|
||||||
* @param centerText the text aligned to the center.
|
|
||||||
* @return a new {@link FormatableChat} filling a chat line with the configured decoration character
|
|
||||||
* and color and a centered text.
|
|
||||||
* @deprecated uses Bungeecord chat API.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public Chat thenCenterText(BaseComponent centerText) {
|
|
||||||
return thenCenterText(chatComponent(centerText));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends a component filling a chat line with the configured decoration character and color.
|
* Appends a component filling a chat line with the configured decoration character and color.
|
||||||
* @return a new {@link FormatableChat} filling a chat line with a decoration character and color.
|
* @return a new {@link FormatableChat} filling a chat line with a decoration character and color.
|
||||||
@@ -629,12 +558,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
* @return this.
|
* @return this.
|
||||||
*/
|
*/
|
||||||
public FormatableChat color(TextColor c) { builder.color(c); return this; }
|
public FormatableChat color(TextColor c) { builder.color(c); return this; }
|
||||||
/**
|
|
||||||
* Sets the color of this component.
|
|
||||||
* @param c the color.
|
|
||||||
* @return this.
|
|
||||||
*/
|
|
||||||
public FormatableChat color(ChatColor c) { return color(c == null ? null : TextColor.color(c.getColor().getRGB())); }
|
|
||||||
/**
|
/**
|
||||||
* Sets the color of this component.
|
* Sets the color of this component.
|
||||||
* @param c the color.
|
* @param c the color.
|
||||||
@@ -646,7 +569,16 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
* @param c the color.
|
* @param c the color.
|
||||||
* @return this.
|
* @return this.
|
||||||
*/
|
*/
|
||||||
public FormatableChat color(String c) { return color(c == null ? null : ChatColor.of(c)); }
|
public FormatableChat color(String c) {
|
||||||
|
if (c == null)
|
||||||
|
return color((TextColor) null);
|
||||||
|
TextColor tc = c.startsWith("#")
|
||||||
|
? TextColor.fromCSSHexString(c)
|
||||||
|
: NamedTextColor.NAMES.value(c.toLowerCase(Locale.ROOT));
|
||||||
|
if (tc == null)
|
||||||
|
throw new IllegalArgumentException("Invalid color string '" + c + "'.");
|
||||||
|
return color(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -924,18 +856,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
* @return this.
|
* @return this.
|
||||||
*/
|
*/
|
||||||
public FormatableChat hover(ComponentLike v) { return hover(v.asComponent()); }
|
public FormatableChat hover(ComponentLike v) { return hover(v.asComponent()); }
|
||||||
/**
|
|
||||||
* Configure this component to show the provided component when hovered.
|
|
||||||
* @param v the component to show.
|
|
||||||
* @return this.
|
|
||||||
*/
|
|
||||||
public FormatableChat hover(BaseComponent v) { return hover(toAdventure(v)); }
|
|
||||||
/**
|
|
||||||
* Configure this component to show the provided component when hovered.
|
|
||||||
* @param v the component to show.
|
|
||||||
* @return this.
|
|
||||||
*/
|
|
||||||
public FormatableChat hover(BaseComponent[] v) { return hover(toAdventure(v)); }
|
|
||||||
/**
|
/**
|
||||||
* Configure this component to show the provided legacy text when hovered.
|
* Configure this component to show the provided legacy text when hovered.
|
||||||
* @param legacyText the legacy text to show.
|
* @param legacyText the legacy text to show.
|
||||||
@@ -963,7 +883,7 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getAdv().hashCode();
|
return get().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -976,8 +896,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
|
|
||||||
/* package */ static ComponentLike filterObjToComponentLike(Object v) {
|
/* package */ static ComponentLike filterObjToComponentLike(Object v) {
|
||||||
return switch (v) {
|
return switch (v) {
|
||||||
case BaseComponent[] baseComponents -> toAdventure(baseComponents);
|
|
||||||
case BaseComponent baseComponent -> toAdventure(baseComponent);
|
|
||||||
case ComponentLike componentLike -> componentLike;
|
case ComponentLike componentLike -> componentLike;
|
||||||
case null, default -> Component.text(Objects.toString(v));
|
case null, default -> Component.text(Objects.toString(v));
|
||||||
};
|
};
|
||||||
@@ -1011,40 +929,6 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the Bungee {@link BaseComponent} array into Adventure {@link Component}.
|
|
||||||
* @param components the Bungee {@link BaseComponent} array.
|
|
||||||
* @return a {@link Component}.
|
|
||||||
*/
|
|
||||||
public static Component toAdventure(BaseComponent[] components) {
|
|
||||||
return BungeeComponentSerializer.get().deserialize(components);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Converts the Bungee {@link BaseComponent} into Adventure {@link Component}.
|
|
||||||
* @param component the Bungee {@link BaseComponent}.
|
|
||||||
* @return a {@link Component}.
|
|
||||||
*/
|
|
||||||
public static Component toAdventure(BaseComponent component) {
|
|
||||||
return toAdventure(new BaseComponent[] { component });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the Adventure {@link Component} into Bungee {@link BaseComponent} array.
|
|
||||||
* @param component the Adventure {@link Component}.
|
|
||||||
* @return a {@link BaseComponent} array.
|
|
||||||
*/
|
|
||||||
public static BaseComponent[] toBungeeArray(Component component) {
|
|
||||||
return BungeeComponentSerializer.get().serialize(component);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Converts the Adventure {@link Component} into Bungee {@link BaseComponent}.
|
|
||||||
* @param component the Adventure {@link Component}.
|
|
||||||
* @return a {@link BaseComponent}.
|
|
||||||
*/
|
|
||||||
public static BaseComponent toBungee(Component component) {
|
|
||||||
BaseComponent[] arr = toBungeeArray(component);
|
|
||||||
return arr.length == 1 ? arr[0] : new net.md_5.bungee.api.chat.TextComponent(arr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force the italic formatting to be set to false if it is not explicitly set in the component.
|
* Force the italic formatting to be set to false if it is not explicitly set in the component.
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
package fr.pandacube.lib.chat;
|
package fr.pandacube.lib.chat;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import net.kyori.adventure.text.format.TextFormat;
|
||||||
|
import net.kyori.adventure.util.RGBLike;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import net.kyori.adventure.text.format.TextColor;
|
|
||||||
import net.kyori.adventure.util.RGBLike;
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides methods to manipulate legacy colors and {@link ChatColor} class.
|
* Provides methods to manipulate legacy colors.
|
||||||
*/
|
*/
|
||||||
public class ChatColorUtil {
|
public class ChatColorUtil {
|
||||||
|
|
||||||
@@ -38,12 +38,12 @@ public class ChatColorUtil {
|
|||||||
int length = legacyText.length();
|
int length = legacyText.length();
|
||||||
|
|
||||||
for (int index = length - 2; index >= 0; index--) {
|
for (int index = length - 2; index >= 0; index--) {
|
||||||
if (legacyText.charAt(index) == ChatColor.COLOR_CHAR) {
|
if (legacyText.charAt(index) == LegacyChatFormat.COLOR_CHAR) {
|
||||||
|
|
||||||
// detection of rgb color §x§0§1§2§3§4§5
|
// detection of rgb color §x§0§1§2§3§4§5
|
||||||
String rgb;
|
String rgb;
|
||||||
if (index > 11
|
if (index > 11
|
||||||
&& legacyText.charAt(index - 12) == ChatColor.COLOR_CHAR
|
&& legacyText.charAt(index - 12) == LegacyChatFormat.COLOR_CHAR
|
||||||
&& (legacyText.charAt(index - 11) == 'x'
|
&& (legacyText.charAt(index - 11) == 'x'
|
||||||
|| legacyText.charAt(index - 11) == 'X')
|
|| legacyText.charAt(index - 11) == 'X')
|
||||||
&& HEX_COLOR_PATTERN.matcher(rgb = legacyText.substring(index - 12, index + 2)).matches()) {
|
&& HEX_COLOR_PATTERN.matcher(rgb = legacyText.substring(index - 12, index + 2)).matches()) {
|
||||||
@@ -64,7 +64,7 @@ public class ChatColorUtil {
|
|||||||
|
|
||||||
// try detect non-rgb format
|
// try detect non-rgb format
|
||||||
char colorChar = legacyText.charAt(index + 1);
|
char colorChar = legacyText.charAt(index + 1);
|
||||||
ChatColor legacyColor = getChatColorByChar(colorChar);
|
LegacyChatFormat legacyColor = LegacyChatFormat.of(colorChar);
|
||||||
|
|
||||||
if (legacyColor != null) {
|
if (legacyColor != null) {
|
||||||
result.insert(0, legacyColor);
|
result.insert(0, legacyColor);
|
||||||
@@ -83,15 +83,6 @@ public class ChatColorUtil {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link ChatColor} associated with the provided char, case-insensitive.
|
|
||||||
* @param code the case-insensitive char code.
|
|
||||||
* @return the corresponding {@link ChatColor}.
|
|
||||||
*/
|
|
||||||
public static ChatColor getChatColorByChar(char code) {
|
|
||||||
return ChatColor.getByChar(Character.toLowerCase(code));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -99,7 +90,7 @@ public class ChatColorUtil {
|
|||||||
* Translate the color code of the provided string, that uses the alt color char, to the {@code §} color code
|
* Translate the color code of the provided string, that uses the alt color char, to the {@code §} color code
|
||||||
* format.
|
* format.
|
||||||
* <p>
|
* <p>
|
||||||
* This method is the improved version of {@link ChatColor#translateAlternateColorCodes(char, String)},
|
* This method is the improved version of Bukkit’s {@code ChatColor.translateAlternateColorCodes(char, String)},
|
||||||
* because it takes into account essentials RGB color code, and {@code altColorChar} escaping (by doubling it).
|
* because it takes into account essentials RGB color code, and {@code altColorChar} escaping (by doubling it).
|
||||||
* Essentials RGB color code are converted to Bungee chat RGB format, so the returned string can be converted
|
* Essentials RGB color code are converted to Bungee chat RGB format, so the returned string can be converted
|
||||||
* to component (see {@link Chat#legacyText(Object)}).
|
* to component (see {@link Chat#legacyText(Object)}).
|
||||||
@@ -112,7 +103,7 @@ public class ChatColorUtil {
|
|||||||
*/
|
*/
|
||||||
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
|
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
|
||||||
{
|
{
|
||||||
char colorChar = ChatColor.COLOR_CHAR;
|
char colorChar = LegacyChatFormat.COLOR_CHAR;
|
||||||
StringBuilder acc = new StringBuilder();
|
StringBuilder acc = new StringBuilder();
|
||||||
char[] b = textToTranslate.toCharArray();
|
char[] b = textToTranslate.toCharArray();
|
||||||
for ( int i = 0; i < b.length; i++ )
|
for ( int i = 0; i < b.length; i++ )
|
||||||
@@ -180,7 +171,7 @@ public class ChatColorUtil {
|
|||||||
* @return the text fully italic.
|
* @return the text fully italic.
|
||||||
*/
|
*/
|
||||||
public static String forceItalic(String legacyText) {
|
public static String forceItalic(String legacyText) {
|
||||||
return forceFormat(legacyText, ChatColor.ITALIC);
|
return forceFormat(legacyText, TextDecoration.ITALIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,7 +181,7 @@ public class ChatColorUtil {
|
|||||||
* @return the text fully bold.
|
* @return the text fully bold.
|
||||||
*/
|
*/
|
||||||
public static String forceBold(String legacyText) {
|
public static String forceBold(String legacyText) {
|
||||||
return forceFormat(legacyText, ChatColor.BOLD);
|
return forceFormat(legacyText, TextDecoration.BOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,7 +191,7 @@ public class ChatColorUtil {
|
|||||||
* @return the text fully underlined.
|
* @return the text fully underlined.
|
||||||
*/
|
*/
|
||||||
public static String forceUnderline(String legacyText) {
|
public static String forceUnderline(String legacyText) {
|
||||||
return forceFormat(legacyText, ChatColor.UNDERLINE);
|
return forceFormat(legacyText, TextDecoration.UNDERLINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,7 +201,7 @@ public class ChatColorUtil {
|
|||||||
* @return the text fully stroked through.
|
* @return the text fully stroked through.
|
||||||
*/
|
*/
|
||||||
public static String forceStrikethrough(String legacyText) {
|
public static String forceStrikethrough(String legacyText) {
|
||||||
return forceFormat(legacyText, ChatColor.STRIKETHROUGH);
|
return forceFormat(legacyText, TextDecoration.STRIKETHROUGH);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,15 +211,16 @@ public class ChatColorUtil {
|
|||||||
* @return the text fully obfuscated.
|
* @return the text fully obfuscated.
|
||||||
*/
|
*/
|
||||||
public static String forceObfuscated(String legacyText) {
|
public static String forceObfuscated(String legacyText) {
|
||||||
return forceFormat(legacyText, ChatColor.MAGIC);
|
return forceFormat(legacyText, TextDecoration.OBFUSCATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static String forceFormat(String legacyText, ChatColor format) {
|
private static String forceFormat(String legacyText, TextFormat format) {
|
||||||
|
String formatStr = LegacyChatFormat.of(format).toString();
|
||||||
return format + legacyText
|
return format + legacyText
|
||||||
.replace(format.toString(), "") // remove previous tag to make the result cleaner
|
.replace(formatStr, "") // remove previous tag to make the result cleaner
|
||||||
.replaceAll("§([a-frA-FR\\d])", "§$1" + format);
|
.replaceAll("§([a-frA-FR\\d])", "§$1" + formatStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -243,40 +235,12 @@ public class ChatColorUtil {
|
|||||||
* @return the resulting text.
|
* @return the resulting text.
|
||||||
*/
|
*/
|
||||||
public static String resetToColor(String legacyText, String color) {
|
public static String resetToColor(String legacyText, String color) {
|
||||||
return legacyText.replace(ChatColor.RESET.toString(), color);
|
return legacyText.replace(LegacyChatFormat.RESET.toString(), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the provided {@link ChatColor} to its Adventure counterpart.
|
|
||||||
* @param bungee a BungeeCord {@link ChatColor} instance.
|
|
||||||
* @return the {@link TextColor} equivalent to the provided {@link ChatColor}.
|
|
||||||
*/
|
|
||||||
public static TextColor toAdventure(ChatColor bungee) {
|
|
||||||
if (bungee == null)
|
|
||||||
return null;
|
|
||||||
if (bungee.getColor() == null)
|
|
||||||
throw new IllegalArgumentException("The provided Bungee ChatColor must be an actual color (not format nor reset).");
|
|
||||||
return TextColor.color(bungee.getColor().getRGB());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the provided {@link TextColor} to its BungeeCord counterpart.
|
|
||||||
* @param col a Adventure {@link TextColor} instance.
|
|
||||||
* @return the {@link ChatColor} equivalent to the provided {@link TextColor}.
|
|
||||||
*/
|
|
||||||
public static ChatColor toBungee(TextColor col) {
|
|
||||||
if (col == null)
|
|
||||||
return null;
|
|
||||||
if (col instanceof NamedTextColor) {
|
|
||||||
return ChatColor.of(((NamedTextColor) col).toString());
|
|
||||||
}
|
|
||||||
return ChatColor.of(col.asHexString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a color, interpolating between 2 colors.
|
* Create a color, interpolating between 2 colors.
|
||||||
* @param v0 the value corresponding to color {@code cc0}.
|
* @param v0 the value corresponding to color {@code cc0}.
|
||||||
|
@@ -87,7 +87,7 @@ public class ChatConfig {
|
|||||||
*/
|
*/
|
||||||
public static int getPrefixWidth(boolean console) {
|
public static int getPrefixWidth(boolean console) {
|
||||||
Chat c;
|
Chat c;
|
||||||
return prefix == null ? 0 : (c = prefix.get()) == null ? 0 : ChatUtil.componentWidth(c.getAdv(), console);
|
return prefix == null ? 0 : (c = prefix.get()) == null ? 0 : ChatUtil.componentWidth(c.get(), console);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package fr.pandacube.lib.chat;
|
package fr.pandacube.lib.chat;
|
||||||
|
|
||||||
import java.util.Objects;
|
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||||
|
|
||||||
import net.kyori.adventure.text.BlockNBTComponent;
|
import net.kyori.adventure.text.BlockNBTComponent;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.ComponentBuilder;
|
import net.kyori.adventure.text.ComponentBuilder;
|
||||||
@@ -18,9 +17,8 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
|||||||
import net.kyori.adventure.text.format.TextColor;
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class holding the publicly accessible methods to create an instance of {@link Chat} component.
|
* Abstract class holding the publicly accessible methods to create an instance of {@link Chat} component.
|
||||||
@@ -33,15 +31,6 @@ public abstract class ChatStatic {
|
|||||||
return new FormatableChat(componentToBuilder(c));
|
return new FormatableChat(componentToBuilder(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link FormatableChat} from the provided Bungee {@link BaseComponent}.
|
|
||||||
* @param c the {@link BaseComponent}.
|
|
||||||
* @return a new {@link FormatableChat}.
|
|
||||||
*/
|
|
||||||
public static FormatableChat chatComponent(BaseComponent c) {
|
|
||||||
return new FormatableChat(componentToBuilder(Chat.toAdventure(c)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link FormatableChat} from the provided {@link ComponentLike}.
|
* Creates a {@link FormatableChat} from the provided {@link ComponentLike}.
|
||||||
* If the provided component is an instance of {@link Chat}, its content will be duplicated, and the provided one
|
* If the provided component is an instance of {@link Chat}, its content will be duplicated, and the provided one
|
||||||
@@ -61,15 +50,6 @@ public abstract class ChatStatic {
|
|||||||
return new FormatableChat(Component.text());
|
return new FormatableChat(Component.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link FormatableChat} from the provided Bungee {@link BaseComponent BaseComponent[]}.
|
|
||||||
* @param c the array of {@link BaseComponent}.
|
|
||||||
* @return a new {@link FormatableChat}.
|
|
||||||
*/
|
|
||||||
public static FormatableChat chatComponent(BaseComponent[] c) {
|
|
||||||
return chatComponent(Chat.toAdventure(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,5 +1,17 @@
|
|||||||
package fr.pandacube.lib.chat;
|
package fr.pandacube.lib.chat;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.ComponentLike;
|
||||||
|
import net.kyori.adventure.text.TextComponent;
|
||||||
|
import net.kyori.adventure.text.TranslatableComponent;
|
||||||
|
import net.kyori.adventure.text.TranslationArgument;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration.State;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -10,19 +22,6 @@ import java.util.Set;
|
|||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.ComponentLike;
|
|
||||||
import net.kyori.adventure.text.TextComponent;
|
|
||||||
import net.kyori.adventure.text.TranslatableComponent;
|
|
||||||
import net.kyori.adventure.text.TranslationArgument;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import net.kyori.adventure.text.format.TextColor;
|
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
|
||||||
import net.kyori.adventure.text.format.TextDecoration.State;
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
|
||||||
|
|
||||||
import static fr.pandacube.lib.chat.ChatStatic.chat;
|
import static fr.pandacube.lib.chat.ChatStatic.chat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -351,7 +350,7 @@ public class ChatUtil {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
char c = legacyText.charAt(index);
|
char c = legacyText.charAt(index);
|
||||||
if (c == ChatColor.COLOR_CHAR && index < legacyText.length() - 1) {
|
if (c == LegacyComponentSerializer.SECTION_CHAR && index < legacyText.length() - 1) {
|
||||||
currentWord.append(c);
|
currentWord.append(c);
|
||||||
c = legacyText.charAt(++index);
|
c = legacyText.charAt(++index);
|
||||||
currentWord.append(c);
|
currentWord.append(c);
|
||||||
@@ -482,7 +481,7 @@ public class ChatUtil {
|
|||||||
}
|
}
|
||||||
if (!row.isEmpty())
|
if (!row.isEmpty())
|
||||||
spacedRow.then(row.getLast());
|
spacedRow.then(row.getLast());
|
||||||
spacedRows.add(spacedRow.getAdv());
|
spacedRows.add(spacedRow.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
return spacedRows;
|
return spacedRows;
|
||||||
@@ -504,14 +503,14 @@ public class ChatUtil {
|
|||||||
*/
|
*/
|
||||||
public static Component customWidthSpace(int width, boolean console) {
|
public static Component customWidthSpace(int width, boolean console) {
|
||||||
if (console)
|
if (console)
|
||||||
return Chat.text(" ".repeat(width)).getAdv();
|
return Chat.text(" ".repeat(width)).get();
|
||||||
return switch (width) {
|
return switch (width) {
|
||||||
case 0, 1 -> Component.empty();
|
case 0, 1 -> Component.empty();
|
||||||
case 2 -> Chat.text(".").black().getAdv();
|
case 2 -> Chat.text(".").black().get();
|
||||||
case 3 -> Chat.text("`").black().getAdv();
|
case 3 -> Chat.text("`").black().get();
|
||||||
case 6 -> Chat.text(". ").black().getAdv();
|
case 6 -> Chat.text(". ").black().get();
|
||||||
case 7 -> Chat.text("` ").black().getAdv();
|
case 7 -> Chat.text("` ").black().get();
|
||||||
case 11 -> Chat.text("` ").black().getAdv();
|
case 11 -> Chat.text("` ").black().get();
|
||||||
default -> {
|
default -> {
|
||||||
int nbSpace = width / 4;
|
int nbSpace = width / 4;
|
||||||
int nbBold = width % 4;
|
int nbBold = width % 4;
|
||||||
@@ -520,13 +519,13 @@ public class ChatUtil {
|
|||||||
if (nbBold > 0) {
|
if (nbBold > 0) {
|
||||||
yield Chat.text(" ".repeat(nbNotBold)).bold(false)
|
yield Chat.text(" ".repeat(nbNotBold)).bold(false)
|
||||||
.then(Chat.text(" ".repeat(nbBold)).bold(true))
|
.then(Chat.text(" ".repeat(nbBold)).bold(true))
|
||||||
.getAdv();
|
.get();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
yield Chat.text(" ".repeat(nbNotBold)).bold(false).getAdv();
|
yield Chat.text(" ".repeat(nbNotBold)).bold(false).get();
|
||||||
}
|
}
|
||||||
else if (nbBold > 0) {
|
else if (nbBold > 0) {
|
||||||
yield Chat.text(" ".repeat(nbBold)).bold(true).getAdv();
|
yield Chat.text(" ".repeat(nbBold)).bold(true).get();
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("Should not be here (width=" + width + "; nbSpace=" + nbSpace + "; nbBold=" + nbBold + "; nbNotBold=" + nbNotBold + ")");
|
throw new IllegalStateException("Should not be here (width=" + width + "; nbSpace=" + nbSpace + "; nbBold=" + nbBold + "; nbNotBold=" + nbNotBold + ")");
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,230 @@
|
|||||||
|
package fr.pandacube.lib.chat;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import net.kyori.adventure.text.format.TextFormat;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyFormat;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenient enum to uses legacy format while keeping compatibility with modern chat format and API (Adventure, ...)
|
||||||
|
*/
|
||||||
|
public enum LegacyChatFormat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Black (0) color format code.
|
||||||
|
*/
|
||||||
|
BLACK('0'),
|
||||||
|
/**
|
||||||
|
* Dark blue (1) color format code.
|
||||||
|
*/
|
||||||
|
DARK_BLUE('1'),
|
||||||
|
/**
|
||||||
|
* Dark green (2) color format code.
|
||||||
|
*/
|
||||||
|
DARK_GREEN('2'),
|
||||||
|
/**
|
||||||
|
* Dark aqua (3) color format code.
|
||||||
|
*/
|
||||||
|
DARK_AQUA('3'),
|
||||||
|
/**
|
||||||
|
* Dark red (4) color format code.
|
||||||
|
*/
|
||||||
|
DARK_RED('4'),
|
||||||
|
/**
|
||||||
|
* Dark purple (5) color format code.
|
||||||
|
*/
|
||||||
|
DARK_PURPLE('5'),
|
||||||
|
/**
|
||||||
|
* Gold (6) color format code.
|
||||||
|
*/
|
||||||
|
GOLD('6'),
|
||||||
|
/**
|
||||||
|
* Gray (7) color format code.
|
||||||
|
*/
|
||||||
|
GRAY('7'),
|
||||||
|
/**
|
||||||
|
* Dark gray (8) color format code.
|
||||||
|
*/
|
||||||
|
DARK_GRAY('8'),
|
||||||
|
/**
|
||||||
|
* Blue (9) color format code.
|
||||||
|
*/
|
||||||
|
BLUE('9'),
|
||||||
|
/**
|
||||||
|
* Green (A) color format code.
|
||||||
|
*/
|
||||||
|
GREEN('a'),
|
||||||
|
/**
|
||||||
|
* Aqua (B) color format code.
|
||||||
|
*/
|
||||||
|
AQUA('b'),
|
||||||
|
/**
|
||||||
|
* Red (C) color format code.
|
||||||
|
*/
|
||||||
|
RED('c'),
|
||||||
|
/**
|
||||||
|
* Light purple (D) color format code.
|
||||||
|
*/
|
||||||
|
LIGHT_PURPLE('d'),
|
||||||
|
/**
|
||||||
|
* Yellow (E) color format code.
|
||||||
|
*/
|
||||||
|
YELLOW('e'),
|
||||||
|
/**
|
||||||
|
* White (F) color format code.
|
||||||
|
*/
|
||||||
|
WHITE('f'),
|
||||||
|
/**
|
||||||
|
* Obfuscated (K) decoration format code.
|
||||||
|
*/
|
||||||
|
OBFUSCATED('k'),
|
||||||
|
/**
|
||||||
|
* Bold (L) decoration format code.
|
||||||
|
*/
|
||||||
|
BOLD('l'),
|
||||||
|
/**
|
||||||
|
* Strikethrough (M) decoration format code.
|
||||||
|
*/
|
||||||
|
STRIKETHROUGH('m'),
|
||||||
|
/**
|
||||||
|
* Underlined (N) decoration format code.
|
||||||
|
*/
|
||||||
|
UNDERLINED('n'),
|
||||||
|
/**
|
||||||
|
* Italic (O) decoration format code.
|
||||||
|
*/
|
||||||
|
ITALIC('o'),
|
||||||
|
/**
|
||||||
|
* Reset (R) format code.
|
||||||
|
*/
|
||||||
|
RESET('r');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The character used by Minecraft for legacy chat format.
|
||||||
|
*/
|
||||||
|
public static final char COLOR_CHAR = LegacyComponentSerializer.SECTION_CHAR;
|
||||||
|
|
||||||
|
/** {@link #COLOR_CHAR} but as a String! */
|
||||||
|
public static final String COLOR_STR_PREFIX = Character.toString(COLOR_CHAR);
|
||||||
|
|
||||||
|
private static final Map<Character, LegacyChatFormat> BY_CHAR;
|
||||||
|
private static final Map<TextFormat, LegacyChatFormat> BY_FORMAT;
|
||||||
|
private static final Map<LegacyFormat, LegacyChatFormat> BY_LEGACY;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link LegacyChatFormat} from the provided chat color code.
|
||||||
|
* @param code the character code from [0-9A-Fa-fK-Ok-oRr].
|
||||||
|
* @return the {@link LegacyChatFormat} related to the provided code.
|
||||||
|
*/
|
||||||
|
public static LegacyChatFormat of(char code) {
|
||||||
|
return BY_CHAR.get(Character.toLowerCase(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link LegacyChatFormat} from the provided {@link TextFormat} instance.
|
||||||
|
* @param format the {@link TextFormat} instance.
|
||||||
|
* @return the {@link LegacyChatFormat} related to the provided format.
|
||||||
|
*/
|
||||||
|
public static LegacyChatFormat of(TextFormat format) {
|
||||||
|
LegacyChatFormat colorOrDecoration = BY_FORMAT.get(format);
|
||||||
|
if (colorOrDecoration != null)
|
||||||
|
return colorOrDecoration;
|
||||||
|
if (format.getClass().getSimpleName().equals("Reset")) // an internal class of legacy serializer library
|
||||||
|
return RESET;
|
||||||
|
throw new IllegalArgumentException("Unsupported format of type " + format.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link LegacyChatFormat} from the provided {@link LegacyFormat} instance.
|
||||||
|
* @param advLegacy the {@link LegacyFormat} instance.
|
||||||
|
* @return the {@link LegacyChatFormat} related to the provided format.
|
||||||
|
*/
|
||||||
|
public static LegacyChatFormat of(LegacyFormat advLegacy) {
|
||||||
|
return BY_LEGACY.get(advLegacy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The format code of this chat format.
|
||||||
|
*/
|
||||||
|
public final char code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Adventure legacy format instance related to this chat format.
|
||||||
|
*/
|
||||||
|
public final LegacyFormat advLegacyFormat;
|
||||||
|
|
||||||
|
LegacyChatFormat(char code) {
|
||||||
|
this.code = code;
|
||||||
|
advLegacyFormat = LegacyComponentSerializer.parseChar(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the related {@link TextColor}, or null if it's not a color.
|
||||||
|
* @return the related {@link TextColor}, or null if it's not a color.
|
||||||
|
*/
|
||||||
|
public TextColor getTextColor() {
|
||||||
|
return advLegacyFormat.color();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this format is a color.
|
||||||
|
* @return true if this format is a color, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isColor() {
|
||||||
|
return getTextColor() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the related {@link TextDecoration}, or null if it's not a decoration.
|
||||||
|
* @return the related {@link TextDecoration}, or null if it's not a decoration.
|
||||||
|
*/
|
||||||
|
public TextDecoration getTextDecoration() {
|
||||||
|
return advLegacyFormat.decoration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this format is a decoration (bold, italic, ...).
|
||||||
|
* @return true if this format is a decoration, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isDecoration() {
|
||||||
|
return getTextDecoration() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this format is the reset.
|
||||||
|
* @return true if this format is the reset, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isReset() {
|
||||||
|
return this == RESET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return COLOR_STR_PREFIX + code;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static {
|
||||||
|
BY_CHAR = Arrays.stream(values()).sequential()
|
||||||
|
.collect(Collectors.toMap(e -> e.code, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||||
|
BY_FORMAT = Arrays.stream(values()).sequential()
|
||||||
|
.filter(e -> e.isColor() || e.isDecoration())
|
||||||
|
.collect(Collectors.toMap(e -> {
|
||||||
|
if (e.isColor())
|
||||||
|
return e.getTextColor();
|
||||||
|
return e.getTextDecoration();
|
||||||
|
}, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||||
|
BY_LEGACY = Arrays.stream(values()).sequential()
|
||||||
|
.collect(Collectors.toMap(e -> e.advLegacyFormat, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||||
|
}
|
||||||
|
}
|
@@ -15,42 +15,36 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
|
||||||
<id>minecraft-libraries</id>
|
|
||||||
<name>Minecraft Libraries</name>
|
|
||||||
<url>https://libraries.minecraft.net</url>
|
|
||||||
</repository>
|
|
||||||
<repository>
|
<repository>
|
||||||
<id>bungeecord-repo</id>
|
<id>bungeecord-repo</id>
|
||||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
</repository>
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
|
||||||
<artifactId>pandalib-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
|
||||||
<artifactId>pandalib-reflect</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
|
||||||
<artifactId>pandalib-commands</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.md-5</groupId>
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
<artifactId>bungeecord-log</artifactId>
|
<artifactId>pandalib-core</artifactId>
|
||||||
<version>${bungeecord.version}</version>
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-reflect</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-commands</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-config</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.md-5</groupId>
|
<groupId>net.md-5</groupId>
|
||||||
<artifactId>bungeecord-config</artifactId>
|
<artifactId>bungeecord-log</artifactId>
|
||||||
<version>${bungeecord.version}</version>
|
<version>${bungeecord.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ import fr.pandacube.lib.chat.Chat.FormatableChat;
|
|||||||
import fr.pandacube.lib.chat.ChatTreeNode;
|
import fr.pandacube.lib.chat.ChatTreeNode;
|
||||||
import fr.pandacube.lib.cli.CLIApplication;
|
import fr.pandacube.lib.cli.CLIApplication;
|
||||||
import fr.pandacube.lib.util.log.Log;
|
import fr.pandacube.lib.util.log.Log;
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -192,7 +192,7 @@ public class CommandAdmin extends CLIBrigadierCommand {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private BaseComponent displayCurrentNode(CommandNode<CLICommandSender> node, boolean redirectTarget, CLICommandSender sender) {
|
private Component displayCurrentNode(CommandNode<CLICommandSender> node, boolean redirectTarget, CLICommandSender sender) {
|
||||||
if (node == null)
|
if (node == null)
|
||||||
throw new IllegalArgumentException("node must not be null");
|
throw new IllegalArgumentException("node must not be null");
|
||||||
FormatableChat d;
|
FormatableChat d;
|
||||||
|
@@ -11,27 +11,31 @@ import java.util.logging.Logger;
|
|||||||
*/
|
*/
|
||||||
public class BadCommandUsage extends RuntimeException {
|
public class BadCommandUsage extends RuntimeException {
|
||||||
|
|
||||||
/** Constructs a new runtime exception with no message or cause.
|
/**
|
||||||
|
* Constructs a new runtime exception with no message or cause.
|
||||||
*/
|
*/
|
||||||
public BadCommandUsage() {
|
public BadCommandUsage() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs a new runtime exception with the specified cause.
|
/**
|
||||||
|
* Constructs a new runtime exception with the specified cause.
|
||||||
* @param cause the cause.
|
* @param cause the cause.
|
||||||
*/
|
*/
|
||||||
public BadCommandUsage(Throwable cause) {
|
public BadCommandUsage(Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs a new runtime exception with the specified message.
|
/**
|
||||||
|
* Constructs a new runtime exception with the specified message.
|
||||||
* @param message the message.
|
* @param message the message.
|
||||||
*/
|
*/
|
||||||
public BadCommandUsage(String message) {
|
public BadCommandUsage(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructs a new runtime exception with the specified message and cause.
|
/**
|
||||||
|
* Constructs a new runtime exception with the specified message and cause.
|
||||||
* @param message the message.
|
* @param message the message.
|
||||||
* @param cause the cause.
|
* @param cause the cause.
|
||||||
*/
|
*/
|
||||||
|
33
pandalib-config/pom.xml
Normal file
33
pandalib-config/pom.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>pandalib-parent</artifactId>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>pandalib-config</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>bungeecord-repo</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-config</artifactId>
|
||||||
|
<version>${bungeecord.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@@ -1,7 +1,4 @@
|
|||||||
package fr.pandacube.lib.core.config;
|
package fr.pandacube.lib.config;
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.ChatColorUtil;
|
|
||||||
import fr.pandacube.lib.util.log.Log;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -56,7 +53,7 @@ public abstract class AbstractConfig {
|
|||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
String trimmedLine = line.trim();
|
String trimmedLine = line.trim();
|
||||||
|
|
||||||
if (ignoreEmpty && trimmedLine.equals(""))
|
if (ignoreEmpty && trimmedLine.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (ignoreHashtagComment && trimmedLine.startsWith("#"))
|
if (ignoreHashtagComment && trimmedLine.startsWith("#"))
|
||||||
@@ -114,25 +111,6 @@ public abstract class AbstractConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method to that translate the {@code '&'} formatted string to the legacy format.
|
|
||||||
* @param string the string to convert.
|
|
||||||
* @return a legacy formatted string (using {@code '§'}).
|
|
||||||
*/
|
|
||||||
public static String getTranslatedColorCode(String string) {
|
|
||||||
return ChatColorUtil.translateAlternateColorCodes('&', string);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the message as a warning into console, prefixed with the context of this config.
|
|
||||||
* @param message the message to log.
|
|
||||||
*/
|
|
||||||
protected void warning(String message) {
|
|
||||||
Log.warning("Error in configuration '"+configFile.getName()+"': " + message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of config.
|
* The type of config.
|
||||||
*/
|
*/
|
@@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.lib.core.config;
|
package fr.pandacube.lib.config;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
@@ -37,6 +37,12 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>${guava.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Cron expression interpreter -->
|
<!-- Cron expression interpreter -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.eitchnet</groupId>
|
<groupId>ch.eitchnet</groupId>
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package fr.pandacube.lib.core.backup;
|
package fr.pandacube.lib.core.backup;
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
import fr.pandacube.lib.chat.Chat;
|
||||||
|
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||||
import fr.pandacube.lib.util.log.Log;
|
import fr.pandacube.lib.util.log.Log;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -107,14 +107,14 @@ public abstract class BackupCleaner implements UnaryOperator<TreeSet<LocalDateTi
|
|||||||
if (files == null)
|
if (files == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Log.info("[Backup] Cleaning up backup directory " + ChatColor.GRAY + compressDisplayName + ChatColor.RESET + "...");
|
Log.info("[Backup] Cleaning up backup directory " + LegacyChatFormat.GRAY + compressDisplayName + LegacyChatFormat.RESET + "...");
|
||||||
|
|
||||||
TreeMap<LocalDateTime, File> datedFiles = new TreeMap<>();
|
TreeMap<LocalDateTime, File> datedFiles = new TreeMap<>();
|
||||||
|
|
||||||
for (String filename : files) {
|
for (String filename : files) {
|
||||||
File file = new File(archiveDir, filename);
|
File file = new File(archiveDir, filename);
|
||||||
if (!filename.matches("\\d{8}-\\d{6}\\.zip")) {
|
if (!filename.matches("\\d{8}-\\d{6}\\.zip")) {
|
||||||
Log.warning("[Backup] " + ChatColor.GRAY + compressDisplayName + ChatColor.RESET + " Invalid file in backup directory: " + filename);
|
Log.warning("[Backup] " + LegacyChatFormat.GRAY + compressDisplayName + LegacyChatFormat.RESET + " Invalid file in backup directory: " + filename);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ public abstract class BackupCleaner implements UnaryOperator<TreeSet<LocalDateTi
|
|||||||
try {
|
try {
|
||||||
ldt = LocalDateTime.parse(dateTimeStr, BackupProcess.dateFileNameFormatter);
|
ldt = LocalDateTime.parse(dateTimeStr, BackupProcess.dateFileNameFormatter);
|
||||||
} catch (DateTimeParseException e) {
|
} catch (DateTimeParseException e) {
|
||||||
Log.warning("[Backup] " + ChatColor.GRAY + compressDisplayName + ChatColor.RESET + " Unable to parse file name to a date-time: " + filename, e);
|
Log.warning("[Backup] " + LegacyChatFormat.GRAY + compressDisplayName + LegacyChatFormat.RESET + " Unable to parse file name to a date-time: " + filename, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ public abstract class BackupCleaner implements UnaryOperator<TreeSet<LocalDateTi
|
|||||||
if (testOnly || oneDeleted)
|
if (testOnly || oneDeleted)
|
||||||
Log.warning(c.getLegacyText());
|
Log.warning(c.getLegacyText());
|
||||||
|
|
||||||
Log.info("[Backup] Backup directory " + ChatColor.GRAY + compressDisplayName + ChatColor.RESET + " cleaned.");
|
Log.info("[Backup] Backup directory " + LegacyChatFormat.GRAY + compressDisplayName + LegacyChatFormat.RESET + " cleaned.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
package fr.pandacube.lib.core.backup;
|
package fr.pandacube.lib.core.backup;
|
||||||
|
|
||||||
import fc.cron.CronExpression;
|
import fc.cron.CronExpression;
|
||||||
|
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||||
import fr.pandacube.lib.core.cron.CronScheduler;
|
import fr.pandacube.lib.core.cron.CronScheduler;
|
||||||
import fr.pandacube.lib.util.FileUtils;
|
import fr.pandacube.lib.util.FileUtils;
|
||||||
import fr.pandacube.lib.util.log.Log;
|
import fr.pandacube.lib.util.log.Log;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
@@ -209,7 +209,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
|||||||
File sourceDir = getSourceDir();
|
File sourceDir = getSourceDir();
|
||||||
|
|
||||||
if (!sourceDir.exists()) {
|
if (!sourceDir.exists()) {
|
||||||
Log.warning("[Backup] Unable to compress " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + ": source directory " + sourceDir + " doesn't exist");
|
Log.warning("[Backup] Unable to compress " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + ": source directory " + sourceDir + " doesn't exist");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
|||||||
onBackupStart();
|
onBackupStart();
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
Log.info("[Backup] Starting for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " ...");
|
Log.info("[Backup] Starting for " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " ...");
|
||||||
|
|
||||||
compressor = new ZipCompressor(sourceDir, target, 9, filter);
|
compressor = new ZipCompressor(sourceDir, target, 9, filter);
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
|||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
Log.info("[Backup] Finished for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET);
|
Log.info("[Backup] Finished for " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BackupCleaner cleaner = getBackupCleaner();
|
BackupCleaner cleaner = getBackupCleaner();
|
||||||
@@ -267,7 +267,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
|||||||
* Logs the scheduling status of this backup process.
|
* Logs the scheduling status of this backup process.
|
||||||
*/
|
*/
|
||||||
public void displayNextSchedule() {
|
public void displayNextSchedule() {
|
||||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " next backup on "
|
Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " next backup on "
|
||||||
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext())));
|
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +297,7 @@ public abstract class BackupProcess implements Comparable<BackupProcess>, Runnab
|
|||||||
public void logProgress() {
|
public void logProgress() {
|
||||||
if (compressor == null)
|
if (compressor == null)
|
||||||
return;
|
return;
|
||||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + ": " + compressor.getState().getLegacyText());
|
Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + ": " + compressor.getState().getLegacyText());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package fr.pandacube.lib.core.backup;
|
package fr.pandacube.lib.core.backup;
|
||||||
|
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
|
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||||
import fr.pandacube.lib.util.log.Log;
|
import fr.pandacube.lib.util.log.Log;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -53,7 +53,7 @@ public class RotatedLogsBackupProcess extends BackupProcess {
|
|||||||
if (!getSourceDir().isDirectory())
|
if (!getSourceDir().isDirectory())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Log.info("[Backup] Starting for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " ...");
|
Log.info("[Backup] Starting for " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " ...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// wait a little after the log message above, in case the log file rotation has to be performed.
|
// wait a little after the log message above, in case the log file rotation has to be performed.
|
||||||
@@ -82,9 +82,9 @@ public class RotatedLogsBackupProcess extends BackupProcess {
|
|||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
Log.info("[Backup] Finished for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET);
|
Log.info("[Backup] Finished for " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.severe("[Backup] Failed for : " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET, e);
|
Log.severe("[Backup] Failed for : " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET, e);
|
||||||
} finally {
|
} finally {
|
||||||
onBackupEnd(success);
|
onBackupEnd(success);
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import java.io.File;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -117,7 +118,11 @@ public class ZipCompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (Entry entry : entriesToCompress) {
|
for (Entry entry : entriesToCompress) {
|
||||||
entry.zip();
|
try {
|
||||||
|
entry.zip();
|
||||||
|
} catch (NoSuchFileException ignored) {
|
||||||
|
// file has been deleted since
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (stateLock) {
|
synchronized (stateLock) {
|
||||||
|
@@ -67,7 +67,11 @@
|
|||||||
"1.20.4": 765,
|
"1.20.4": 765,
|
||||||
"1.20.5": 766,
|
"1.20.5": 766,
|
||||||
"1.20.6": 766,
|
"1.20.6": 766,
|
||||||
"1.21": 767
|
"1.21": 767,
|
||||||
|
"1.21.1": 767,
|
||||||
|
"1.21.2": 768,
|
||||||
|
"1.21.3": 768,
|
||||||
|
"1.21.4": 769
|
||||||
},
|
},
|
||||||
"versionsOfProtocol": {
|
"versionsOfProtocol": {
|
||||||
"4": [
|
"4": [
|
||||||
@@ -220,7 +224,15 @@
|
|||||||
"1.20.6"
|
"1.20.6"
|
||||||
],
|
],
|
||||||
"767": [
|
"767": [
|
||||||
"1.21"
|
"1.21",
|
||||||
|
"1.21.1"
|
||||||
|
],
|
||||||
|
"768": [
|
||||||
|
"1.21.2",
|
||||||
|
"1.21.3"
|
||||||
|
],
|
||||||
|
"769": [
|
||||||
|
"1.21.4"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -23,7 +23,8 @@ public class DBConnection {
|
|||||||
public DBConnection(String host, int port, String dbname, String login, String password) {
|
public DBConnection(String host, int port, String dbname, String login, String password) {
|
||||||
this("jdbc:mysql://" + host + ":" + port + "/" + dbname
|
this("jdbc:mysql://" + host + ":" + port + "/" + dbname
|
||||||
+ "?useUnicode=true"
|
+ "?useUnicode=true"
|
||||||
+ "&useSSL=false"
|
+ "&sslMode=DISABLED"
|
||||||
|
+ "&allowPublicKeyRetrieval=true"
|
||||||
+ "&characterEncoding=utf8"
|
+ "&characterEncoding=utf8"
|
||||||
+ "&characterSetResults=utf8"
|
+ "&characterSetResults=utf8"
|
||||||
+ "&character_set_server=utf8mb4"
|
+ "&character_set_server=utf8mb4"
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>papermc</id>
|
<id>papermc</id>
|
||||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
|
||||||
<!-- WorldEdit -->
|
<!-- WorldEdit -->
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>papermc</id>
|
<id>papermc</id>
|
||||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
</repository>
|
</repository>
|
||||||
<repository>
|
<repository>
|
||||||
<id>fabricmc</id>
|
<id>fabricmc</id>
|
||||||
@@ -71,6 +71,12 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-bungee-chat</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
<artifactId>pandalib-paper-permissions</artifactId>
|
<artifactId>pandalib-paper-permissions</artifactId>
|
||||||
@@ -84,11 +90,6 @@
|
|||||||
<artifactId>paper-api</artifactId>
|
<artifactId>paper-api</artifactId>
|
||||||
<version>${paper.version}-SNAPSHOT</version>
|
<version>${paper.version}-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>io.papermc.paper</groupId>
|
|
||||||
<artifactId>paper-mojangapi</artifactId>
|
|
||||||
<version>${paper.version}-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@@ -5,27 +5,47 @@ import fr.pandacube.lib.paper.json.PaperJson;
|
|||||||
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main class for pandalib-paper.
|
||||||
|
*/
|
||||||
public class PandaLibPaper {
|
public class PandaLibPaper {
|
||||||
|
|
||||||
private static Plugin plugin;
|
private static Plugin plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to call in plugin's {@link Plugin#onLoad()} method.
|
||||||
|
* @param plugin the plugin instance.
|
||||||
|
*/
|
||||||
public static void onLoad(Plugin plugin) {
|
public static void onLoad(Plugin plugin) {
|
||||||
PandaLibPaper.plugin = plugin;
|
PandaLibPaper.plugin = plugin;
|
||||||
PaperJson.init();
|
PaperJson.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to call in plugin's {@link Plugin#onEnable()} method.
|
||||||
|
*/
|
||||||
public static void onEnable() {
|
public static void onEnable() {
|
||||||
PerformanceAnalysisManager.getInstance(); // initialize
|
PerformanceAnalysisManager.getInstance(); // initialize
|
||||||
ServerStopEvent.init();
|
ServerStopEvent.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to call in plugin's {@link Plugin#onDisable()} method.
|
||||||
|
*/
|
||||||
public static void disable() {
|
public static void disable() {
|
||||||
PerformanceAnalysisManager.getInstance().cancelInternalBossBar();
|
PerformanceAnalysisManager.getInstance().deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the plugin instance.
|
||||||
|
* @return the plugin instance provided with {@link #onLoad(Plugin)}.
|
||||||
|
*/
|
||||||
public static Plugin getPlugin() {
|
public static Plugin getPlugin() {
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PandaLibPaper() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,14 +6,65 @@ import java.io.File;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic class holding configuration for {@link PaperBackupManager}.
|
||||||
|
*/
|
||||||
@SuppressWarnings("CanBeFinal")
|
@SuppressWarnings("CanBeFinal")
|
||||||
public class PaperBackupConfig {
|
public class PaperBackupConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Paper backup config.
|
||||||
|
*/
|
||||||
|
public PaperBackupConfig() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true to enable worlds backup.
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
public boolean worldBackupEnabled = true;
|
public boolean worldBackupEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true to enable the backup of the working directory.
|
||||||
|
* The workdir backup will already ignore the logs directory and any world folder (folder with a level.dat file in it).
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
public boolean workdirBackupEnabled = true;
|
public boolean workdirBackupEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true to enable the backup of logs.
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
public boolean logsBackupEnabled = true;
|
public boolean logsBackupEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cron-formatted scheduling of the worlds and workdir backups.
|
||||||
|
* The default value is {@code "0 2 * * *"}, that is every day at 2am.
|
||||||
|
*/
|
||||||
public String scheduling = "0 2 * * *"; // cron format, here is every day at 2am
|
public String scheduling = "0 2 * * *"; // cron format, here is every day at 2am
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The backup target directory.
|
||||||
|
* Must be set (defaults to null).
|
||||||
|
*/
|
||||||
public File backupDirectory = null;
|
public File backupDirectory = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The backup cleaner for the worlds backup.
|
||||||
|
* Defaults to keep 1 backup every 3 month + the last 5 backups.
|
||||||
|
*/
|
||||||
public BackupCleaner worldBackupCleaner = BackupCleaner.KEEPING_1_EVERY_N_MONTH(3).merge(BackupCleaner.KEEPING_N_LAST(5));
|
public BackupCleaner worldBackupCleaner = BackupCleaner.KEEPING_1_EVERY_N_MONTH(3).merge(BackupCleaner.KEEPING_N_LAST(5));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The backup cleaner for the workdir backup.
|
||||||
|
* Defaults to keep 1 backup every 3 month + the last 5 backups.
|
||||||
|
*/
|
||||||
public BackupCleaner workdirBackupCleaner = BackupCleaner.KEEPING_1_EVERY_N_MONTH(3).merge(BackupCleaner.KEEPING_N_LAST(5));
|
public BackupCleaner workdirBackupCleaner = BackupCleaner.KEEPING_1_EVERY_N_MONTH(3).merge(BackupCleaner.KEEPING_N_LAST(5));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of files or directory to ignore.
|
||||||
|
* Defaults to none.
|
||||||
|
* The workdir backup will already ignore the logs directory and any world folder (folder with a level.dat file in it).
|
||||||
|
*/
|
||||||
public List<String> workdirIgnoreList = new ArrayList<>();
|
public List<String> workdirIgnoreList = new ArrayList<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,12 +23,19 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The backup manager for Paper servers.
|
||||||
|
*/
|
||||||
public class PaperBackupManager extends BackupManager implements Listener {
|
public class PaperBackupManager extends BackupManager implements Listener {
|
||||||
|
|
||||||
private final Map<String, PaperWorldProcess> compressWorlds = new HashMap<>();
|
private final Map<String, PaperWorldProcess> compressWorlds = new HashMap<>();
|
||||||
|
|
||||||
PaperBackupConfig config;
|
PaperBackupConfig config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new backup manager.
|
||||||
|
* @param config the configuration of the backups.
|
||||||
|
*/
|
||||||
public PaperBackupManager(PaperBackupConfig config) {
|
public PaperBackupManager(PaperBackupConfig config) {
|
||||||
super(config.backupDirectory);
|
super(config.backupDirectory);
|
||||||
setConfig(config);
|
setConfig(config);
|
||||||
@@ -49,13 +56,17 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
|||||||
super.addProcess(process);
|
super.addProcess(process);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the backups config
|
||||||
|
* @param config the new config.
|
||||||
|
*/
|
||||||
public void setConfig(PaperBackupConfig config) {
|
public void setConfig(PaperBackupConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
backupQueue.forEach(this::updateProcessConfig);
|
backupQueue.forEach(this::updateProcessConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void updateProcessConfig(BackupProcess process) {
|
private void updateProcessConfig(BackupProcess process) {
|
||||||
if (process instanceof PaperWorkdirProcess) {
|
if (process instanceof PaperWorkdirProcess) {
|
||||||
process.setEnabled(config.workdirBackupEnabled);
|
process.setEnabled(config.workdirBackupEnabled);
|
||||||
process.setBackupCleaner(config.workdirBackupCleaner);
|
process.setBackupCleaner(config.workdirBackupCleaner);
|
||||||
@@ -119,12 +130,12 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
|||||||
private final Set<String> dirtyForSave = new HashSet<>();
|
private final Set<String> dirtyForSave = new HashSet<>();
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onWorldLoad(WorldLoadEvent event) {
|
void onWorldLoad(WorldLoadEvent event) {
|
||||||
initWorldProcess(event.getWorld().getName());
|
initWorldProcess(event.getWorld().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onWorldSave(WorldSaveEvent event) {
|
void onWorldSave(WorldSaveEvent event) {
|
||||||
if (event.getWorld().getLoadedChunks().length > 0
|
if (event.getWorld().getLoadedChunks().length > 0
|
||||||
|| dirtyForSave.contains(event.getWorld().getName())) {
|
|| dirtyForSave.contains(event.getWorld().getName())) {
|
||||||
compressWorlds.get(event.getWorld().getName()).setDirtyAfterSave();
|
compressWorlds.get(event.getWorld().getName()).setDirtyAfterSave();
|
||||||
@@ -137,18 +148,18 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) {
|
void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) {
|
||||||
dirtyForSave.add(event.getFrom().getName());
|
dirtyForSave.add(event.getFrom().getName());
|
||||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,11 +10,19 @@ import net.kyori.adventure.bossbar.BossBar.Color;
|
|||||||
import net.kyori.adventure.bossbar.BossBar.Overlay;
|
import net.kyori.adventure.bossbar.BossBar.Overlay;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A backup process with specific logic around Paper server.
|
||||||
|
*/
|
||||||
public abstract class PaperBackupProcess extends BackupProcess {
|
public abstract class PaperBackupProcess extends BackupProcess {
|
||||||
|
|
||||||
|
|
||||||
private BossBar bossBar;
|
private BossBar bossBar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new backup process.
|
||||||
|
* @param bm the associated backup manager.
|
||||||
|
* @param id the process identifier.
|
||||||
|
*/
|
||||||
protected PaperBackupProcess(PaperBackupManager bm, String id) {
|
protected PaperBackupProcess(PaperBackupManager bm, String id) {
|
||||||
super(bm, id);
|
super(bm, id);
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,15 @@ package fr.pandacube.lib.paper.backup;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A backup process with specific logic around Paper server working directory.
|
||||||
|
*/
|
||||||
public class PaperWorkdirProcess extends PaperBackupProcess {
|
public class PaperWorkdirProcess extends PaperBackupProcess {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new backup process for the paper server working directory.
|
||||||
|
* @param bm the associated backup manager.
|
||||||
|
*/
|
||||||
protected PaperWorkdirProcess(PaperBackupManager bm) {
|
protected PaperWorkdirProcess(PaperBackupManager bm) {
|
||||||
super(bm, "workdir");
|
super(bm, "workdir");
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package fr.pandacube.lib.paper.backup;
|
package fr.pandacube.lib.paper.backup;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||||
import fr.pandacube.lib.paper.scheduler.SchedulerUtil;
|
import fr.pandacube.lib.paper.scheduler.SchedulerUtil;
|
||||||
import fr.pandacube.lib.paper.world.WorldUtil;
|
import fr.pandacube.lib.paper.world.WorldUtil;
|
||||||
import fr.pandacube.lib.util.log.Log;
|
import fr.pandacube.lib.util.log.Log;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
|
||||||
@@ -11,14 +11,22 @@ import java.io.File;
|
|||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A backup process with specific logic around Paper server world.
|
||||||
|
*/
|
||||||
public class PaperWorldProcess extends PaperBackupProcess {
|
public class PaperWorldProcess extends PaperBackupProcess {
|
||||||
private final String worldName;
|
private final String worldName;
|
||||||
|
|
||||||
private boolean autoSave = true;
|
private boolean autoSave = true;
|
||||||
|
|
||||||
protected PaperWorldProcess(PaperBackupManager bm, final String n) {
|
/**
|
||||||
super(bm, "worlds/" + n);
|
* Instantiates a new backup process for a world.
|
||||||
worldName = n;
|
* @param bm the associated backup manager.
|
||||||
|
* @param worldName the name of the world.
|
||||||
|
*/
|
||||||
|
protected PaperWorldProcess(PaperBackupManager bm, final String worldName) {
|
||||||
|
super(bm, "worlds/" + worldName);
|
||||||
|
this.worldName = worldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private World getWorld() {
|
private World getWorld() {
|
||||||
@@ -62,11 +70,11 @@ public class PaperWorldProcess extends PaperBackupProcess {
|
|||||||
|
|
||||||
public void displayNextSchedule() {
|
public void displayNextSchedule() {
|
||||||
if (hasNextScheduled()) {
|
if (hasNextScheduled()) {
|
||||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is dirty. Next backup on "
|
Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " is dirty. Next backup on "
|
||||||
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext())));
|
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext())));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is clean. Next backup not scheduled.");
|
Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " is clean. Next backup not scheduled.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +88,7 @@ public class PaperWorldProcess extends PaperBackupProcess {
|
|||||||
public void setDirtyAfterSave() {
|
public void setDirtyAfterSave() {
|
||||||
if (!isDirty()) { // don't set dirty if it is already
|
if (!isDirty()) { // don't set dirty if it is already
|
||||||
setDirtySinceNow();
|
setDirtySinceNow();
|
||||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " was saved and is now dirty. Next backup on "
|
Log.info("[Backup] " + LegacyChatFormat.GRAY + getDisplayName() + LegacyChatFormat.RESET + " was saved and is now dirty. Next backup on "
|
||||||
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG)
|
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG)
|
||||||
.format(new Date(getNext()))
|
.format(new Date(getNext()))
|
||||||
);
|
);
|
||||||
|
@@ -23,6 +23,8 @@ import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.PluginCommandNode;
|
|||||||
import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
|
import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
|
||||||
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
|
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
|
||||||
import fr.pandacube.lib.players.standalone.AbstractPlayerManager;
|
import fr.pandacube.lib.players.standalone.AbstractPlayerManager;
|
||||||
|
import fr.pandacube.lib.reflect.Reflect;
|
||||||
|
import fr.pandacube.lib.reflect.ReflectClass;
|
||||||
import fr.pandacube.lib.util.log.Log;
|
import fr.pandacube.lib.util.log.Log;
|
||||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||||
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
||||||
@@ -37,6 +39,7 @@ import org.bukkit.event.Listener;
|
|||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -54,10 +57,21 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
|||||||
|
|
||||||
private static CommandDispatcher<CommandSourceStack> vanillaPaperDispatcher = null;
|
private static CommandDispatcher<CommandSourceStack> vanillaPaperDispatcher = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Brigadier dispatcher provided by paper API during {@link LifecycleEvents#COMMANDS}.
|
||||||
|
* <p>
|
||||||
|
* This Dispatcher is not the vanilla one. Instead, Paper implementation wraps the vanilla one to handle proper registration
|
||||||
|
* of commands from plugins.
|
||||||
|
* @return the Brigadier dispatcher.
|
||||||
|
*/
|
||||||
public static CommandDispatcher<CommandSourceStack> getVanillaPaperDispatcher() {
|
public static CommandDispatcher<CommandSourceStack> getVanillaPaperDispatcher() {
|
||||||
return vanillaPaperDispatcher;
|
return vanillaPaperDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the root node of the dispatcher from {@link #getVanillaPaperDispatcher()}.
|
||||||
|
* @return the root node, or null if {@link #getVanillaPaperDispatcher()} is also null.
|
||||||
|
*/
|
||||||
public static RootCommandNode<CommandSourceStack> getRootNode() {
|
public static RootCommandNode<CommandSourceStack> getRootNode() {
|
||||||
return vanillaPaperDispatcher == null ? null : vanillaPaperDispatcher.getRoot();
|
return vanillaPaperDispatcher == null ? null : vanillaPaperDispatcher.getRoot();
|
||||||
}
|
}
|
||||||
@@ -65,6 +79,19 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
|||||||
private static void updateVanillaPaperDispatcher(CommandDispatcher<CommandSourceStack> newDispatcher) {
|
private static void updateVanillaPaperDispatcher(CommandDispatcher<CommandSourceStack> newDispatcher) {
|
||||||
if (vanillaPaperDispatcher == null || newDispatcher != vanillaPaperDispatcher) {
|
if (vanillaPaperDispatcher == null || newDispatcher != vanillaPaperDispatcher) {
|
||||||
vanillaPaperDispatcher = newDispatcher;
|
vanillaPaperDispatcher = newDispatcher;
|
||||||
|
|
||||||
|
// vanillaPaperDispatcher.getRoot() is not the real root but a wrapped root. Trying to map the fake root with the real one to trick the Paper/Brigadier (un)wrapper
|
||||||
|
RootCommandNode<CommandSourceStack> wrappedRoot = vanillaPaperDispatcher.getRoot();
|
||||||
|
ReflectClass<?> apiMirrorRootNodeClass = Reflect.ofClassOfInstance(wrappedRoot);
|
||||||
|
try {
|
||||||
|
RootCommandNode<?> unwrappedRoot = ((CommandDispatcher<?>) apiMirrorRootNodeClass.method("getDispatcher").invoke(wrappedRoot)).getRoot();
|
||||||
|
|
||||||
|
Reflect.ofClass(CommandNode.class).field("unwrappedCached").setValue(wrappedRoot, unwrappedRoot);
|
||||||
|
Reflect.ofClass(CommandNode.class).field("wrappedCached").setValue(unwrappedRoot, wrappedRoot);
|
||||||
|
|
||||||
|
} catch (InvocationTargetException|IllegalAccessException|NoSuchMethodException|NoSuchFieldException e) {
|
||||||
|
Log.severe("Unable to trick the Paper/Brigadier unwrapper to properly handle commands redirecting to root command node.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,12 +140,15 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
|||||||
/**
|
/**
|
||||||
* The command node of this command.
|
* The command node of this command.
|
||||||
*/
|
*/
|
||||||
protected final LiteralCommandNode<CommandSourceStack> commandNode;
|
protected LiteralCommandNode<CommandSourceStack> commandNode;
|
||||||
/**
|
/**
|
||||||
* The command requested aliases.
|
* The command requested aliases.
|
||||||
*/
|
*/
|
||||||
protected final String[] aliases;
|
protected final String[] aliases;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command description.
|
||||||
|
*/
|
||||||
protected final String description;
|
protected final String description;
|
||||||
|
|
||||||
private final RegistrationPolicy registrationPolicy;
|
private final RegistrationPolicy registrationPolicy;
|
||||||
@@ -134,11 +164,9 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
|||||||
public PaperBrigadierCommand(Plugin pl, RegistrationPolicy regPolicy) {
|
public PaperBrigadierCommand(Plugin pl, RegistrationPolicy regPolicy) {
|
||||||
plugin = pl;
|
plugin = pl;
|
||||||
registrationPolicy = regPolicy;
|
registrationPolicy = regPolicy;
|
||||||
commandNode = buildCommand().build();
|
|
||||||
String[] aliasesTmp = getAliases();
|
String[] aliasesTmp = getAliases();
|
||||||
aliases = aliasesTmp == null ? new String[0] : aliasesTmp;
|
aliases = aliasesTmp == null ? new String[0] : aliasesTmp;
|
||||||
description = getDescription();
|
description = getDescription();
|
||||||
postBuildCommand(commandNode);
|
|
||||||
register();
|
register();
|
||||||
//try {
|
//try {
|
||||||
// PandalibPaperPermissions.addPermissionMapping("minecraft.command." + commandNode.getLiteral().toLowerCase(), getTargetPermission().toLowerCase());
|
// PandalibPaperPermissions.addPermissionMapping("minecraft.command." + commandNode.getLiteral().toLowerCase(), getTargetPermission().toLowerCase());
|
||||||
@@ -160,12 +188,16 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
|||||||
plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
|
plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
|
||||||
updateVanillaPaperDispatcher(event.registrar().getDispatcher());
|
updateVanillaPaperDispatcher(event.registrar().getDispatcher());
|
||||||
|
|
||||||
|
commandNode = buildCommand().build();
|
||||||
|
postBuildCommand(commandNode);
|
||||||
|
|
||||||
if (vanillaPaperDispatcher.getRoot().getChild(commandNode.getName()) != null) {
|
if (vanillaPaperDispatcher.getRoot().getChild(commandNode.getName()) != null) {
|
||||||
Log.info("Command /" + commandNode.getName() + " found in the vanilla dispatcher during initial command registration. Replacing it by force.");
|
Log.info("Command /" + commandNode.getName() + " found in the vanilla dispatcher during initial command registration. Replacing it by force.");
|
||||||
vanillaPaperDispatcher.getRoot().getChildren().removeIf(c -> c.getName().equals(commandNode.getName()));
|
vanillaPaperDispatcher.getRoot().getChildren().removeIf(c -> c.getName().equals(commandNode.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
registeredAliases = new HashSet<>(event.registrar().register(commandNode, description, List.of(aliases)));
|
registeredAliases = new HashSet<>(event.registrar().register(commandNode, description, List.of(aliases)));
|
||||||
|
doPostRegistrationFixes();
|
||||||
|
|
||||||
if (registrationPolicy == RegistrationPolicy.ALL) {
|
if (registrationPolicy == RegistrationPolicy.ALL) {
|
||||||
// enforce registration of aliases
|
// enforce registration of aliases
|
||||||
@@ -177,40 +209,81 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
Bukkit.getServer().getScheduler().runTask(plugin, () -> {
|
Bukkit.getServer().getScheduler().runTask(plugin, () -> {
|
||||||
if (vanillaPaperDispatcher == null)
|
if (vanillaPaperDispatcher == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Set<String> forceRegistrationAgain = new HashSet<>();
|
Set<String> forceRegistrationAgain = new HashSet<>();
|
||||||
forceRegistrationAgain.add(commandNode.getName());
|
forceRegistrationAgain.add(commandNode.getName());
|
||||||
if (registrationPolicy == RegistrationPolicy.ALL)
|
if (registrationPolicy == RegistrationPolicy.ALL)
|
||||||
forceRegistrationAgain.addAll(List.of(aliases));
|
forceRegistrationAgain.addAll(List.of(aliases));
|
||||||
|
|
||||||
for (String aliasToForce : forceRegistrationAgain) {
|
for (String aliasToForce : forceRegistrationAgain) {
|
||||||
CommandNode<CommandSourceStack> actualNode = vanillaPaperDispatcher.getRoot().getChild(aliasToForce);
|
CommandNode<CommandSourceStack> actualNode = vanillaPaperDispatcher.getRoot().getChild(aliasToForce);
|
||||||
if (actualNode != null) {
|
if (actualNode != null) {
|
||||||
//Log.info("Forcing registration of alias /" + aliasToForce + " for command /" + commandNode.getName() + ": replacing " + getCommandIdentity(actualNode) + "?");
|
//Log.info("Forcing registration of alias /" + aliasToForce + " for command /" + commandNode.getName() + ": replacing " + getCommandIdentity(actualNode) + "?");
|
||||||
if (PluginCommandNode.REFLECT.get().isInstance(actualNode)) {
|
if (PluginCommandNode.REFLECT.get().isInstance(actualNode)) {
|
||||||
PluginCommandNode pcn = wrap(actualNode, PluginCommandNode.class);
|
PluginCommandNode pcn = wrap(actualNode, PluginCommandNode.class);
|
||||||
if (pcn.getPlugin().equals(plugin))
|
if (pcn.getPlugin().equals(plugin))
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
else if (BukkitCommandNode.REFLECT.get().isInstance(actualNode)) {
|
||||||
|
BukkitCommandNode bcn = wrap(actualNode, BukkitCommandNode.class);
|
||||||
|
if (bcn.getBukkitCommand() instanceof PluginCommand pc && pc.getPlugin().equals(plugin))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vanillaPaperDispatcher.getRoot().getChildren().removeIf(c -> c.getName().equals(aliasToForce));
|
||||||
}
|
}
|
||||||
else if (BukkitCommandNode.REFLECT.get().isInstance(actualNode)) {
|
|
||||||
BukkitCommandNode bcn = wrap(actualNode, BukkitCommandNode.class);
|
|
||||||
if (bcn.getBukkitCommand() instanceof PluginCommand pc && pc.getPlugin().equals(plugin))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
vanillaPaperDispatcher.getRoot().getChildren().removeIf(c -> c.getName().equals(aliasToForce));
|
|
||||||
}
|
|
||||||
/*else {
|
/*else {
|
||||||
Log.info("Forcing registration of alias /" + aliasToForce + " for command /" + commandNode.getName() + ": no command found for alias. Adding alias.");
|
Log.info("Forcing registration of alias /" + aliasToForce + " for command /" + commandNode.getName() + ": no command found for alias. Adding alias.");
|
||||||
}*/
|
}*/
|
||||||
LiteralCommandNode<CommandSourceStack> newPCN = unwrap(new PluginCommandNode(aliasToForce, plugin.getPluginMeta(), commandNode, description));
|
LiteralCommandNode<CommandSourceStack> newPCN = unwrap(new PluginCommandNode(aliasToForce, plugin.getPluginMeta(), commandNode, description));
|
||||||
vanillaPaperDispatcher.getRoot().addChild(newPCN);
|
vanillaPaperDispatcher.getRoot().addChild(newPCN);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void doPostRegistrationFixes() {
|
||||||
|
postRegistrationFixNode(new HashSet<>(), commandNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postRegistrationFixNode(Set<CommandNode<CommandSourceStack>> fixedNodes, CommandNode<CommandSourceStack> originalNode) {
|
||||||
|
if (originalNode instanceof RootCommandNode)
|
||||||
|
return;
|
||||||
|
if (fixedNodes.contains(originalNode))
|
||||||
|
return;
|
||||||
|
fixedNodes.add(originalNode);
|
||||||
|
if (originalNode.getRedirect() != null) {
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
ReflectClass<CommandNode> cmdNodeClass = Reflect.ofClass(CommandNode.class);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
CommandNode<CommandSourceStack> unwrappedNode = (CommandNode<CommandSourceStack>) cmdNodeClass.field("unwrappedCached").getValue(originalNode);
|
||||||
|
if (unwrappedNode != null) {
|
||||||
|
cmdNodeClass.field("modifier").setValue(unwrappedNode, cmdNodeClass.field("modifier").getValue(originalNode));
|
||||||
|
cmdNodeClass.field("forks").setValue(unwrappedNode, cmdNodeClass.field("forks").getValue(originalNode));
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
postRegistrationFixNode(fixedNodes, originalNode.getRedirect());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
for (CommandNode<CommandSourceStack> child : originalNode.getChildren())
|
||||||
|
postRegistrationFixNode(fixedNodes, child);
|
||||||
|
} catch (UnsupportedOperationException ignored) {
|
||||||
|
// in case getChildren is not possible (vanilla commands are wrapped by Paper API)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -276,6 +349,10 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the aliases that are actually registered in the server.
|
||||||
|
* @return the actually registered aliases.
|
||||||
|
*/
|
||||||
protected Set<String> getRegisteredAliases() {
|
protected Set<String> getRegisteredAliases() {
|
||||||
return Set.copyOf(registeredAliases);
|
return Set.copyOf(registeredAliases);
|
||||||
}
|
}
|
||||||
@@ -322,12 +399,15 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSour
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isConsole(CommandSourceStack wrapper) {
|
public boolean isConsole(CommandSourceStack wrapper) {
|
||||||
return isConsole(getCommandSender(wrapper));
|
return isConsole(getCommandSender(wrapper));
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
public boolean isPlayer(CommandSourceStack wrapper) {
|
public boolean isPlayer(CommandSourceStack wrapper) {
|
||||||
return isPlayer(getCommandSender(wrapper));
|
return isPlayer(getCommandSender(wrapper));
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
public Predicate<CommandSourceStack> hasPermission(String permission) {
|
public Predicate<CommandSourceStack> hasPermission(String permission) {
|
||||||
return wrapper -> getCommandSender(wrapper).hasPermission(permission);
|
return wrapper -> getCommandSender(wrapper).hasPermission(permission);
|
||||||
}
|
}
|
||||||
|
@@ -10,28 +10,38 @@ import org.bukkit.event.server.PluginDisableEvent;
|
|||||||
import org.bukkit.event.server.ServerEvent;
|
import org.bukkit.event.server.ServerEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired at the beginning of the server stop process.
|
||||||
|
* More specifically, this event is called when the first plugin is disabling ({@link PluginDisableEvent}) while
|
||||||
|
* {@link Bukkit#isStopping()} returns true.
|
||||||
|
* <p>
|
||||||
|
* This event can be useful when a plugin want to execute stuff on server stop as soon as possible in the process,
|
||||||
|
* but not when the plugin itself is disabling (because some part of the Bukkit API is not usable at that moment).
|
||||||
|
*/
|
||||||
public class ServerStopEvent extends ServerEvent {
|
public class ServerStopEvent extends ServerEvent {
|
||||||
|
|
||||||
|
|
||||||
private static final HandlerList handlers = new HandlerList();
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
@NotNull
|
/**
|
||||||
@Override
|
* Gets the handler list of the event.
|
||||||
public HandlerList getHandlers() {
|
* @return the handler list of the event.
|
||||||
return handlers;
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static HandlerList getHandlerList() {
|
public static HandlerList getHandlerList() {
|
||||||
return handlers;
|
return handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static boolean hasTriggered = false;
|
private static boolean hasTriggered = false;
|
||||||
|
private static boolean isInit = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the event used to detect the server stop.
|
||||||
|
*/
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
if (isInit)
|
||||||
|
return;
|
||||||
|
|
||||||
BukkitEvent.register(new Listener() {
|
BukkitEvent.register(new Listener() {
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
@@ -45,7 +55,25 @@ public class ServerStopEvent extends ServerEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
isInit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ServerStopEvent() {}
|
||||||
|
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -63,6 +63,7 @@ public class DirectionalVector {
|
|||||||
* contained in the provided {@link Location}.
|
* contained in the provided {@link Location}.
|
||||||
* {@link Location#getYaw()} and {@link Location#getPitch()} values are automatically
|
* {@link Location#getYaw()} and {@link Location#getPitch()} values are automatically
|
||||||
* converted to conform {@link #yaw} and {@link #pitch} specification.
|
* converted to conform {@link #yaw} and {@link #pitch} specification.
|
||||||
|
* @param l the location.
|
||||||
*/
|
*/
|
||||||
public DirectionalVector(Location l) {
|
public DirectionalVector(Location l) {
|
||||||
this(
|
this(
|
||||||
@@ -79,6 +80,7 @@ public class DirectionalVector {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Creates a new {@link DirectionalVector} from a simple {@link Vector}.
|
||||||
* @param v the vector representing the direction. If v.getX() and v.getZ() are 0,
|
* @param v the vector representing the direction. If v.getX() and v.getZ() are 0,
|
||||||
* the yaw will be 0. This may have inconsistency if the vector is calculated
|
* the yaw will be 0. This may have inconsistency if the vector is calculated
|
||||||
* from a {@link Location}'s yaw and pitch. In this case, prefer using
|
* from a {@link Location}'s yaw and pitch. In this case, prefer using
|
||||||
@@ -126,7 +128,10 @@ public class DirectionalVector {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a Vector using the internal X, Y and Z values, that is a simple directional 3D vector.
|
||||||
|
* @return this vector as a simple 3D {@link Vector}.
|
||||||
|
*/
|
||||||
public Vector toVector() {
|
public Vector toVector() {
|
||||||
return new Vector(x, y, z);
|
return new Vector(x, y, z);
|
||||||
}
|
}
|
||||||
@@ -135,7 +140,8 @@ public class DirectionalVector {
|
|||||||
/**
|
/**
|
||||||
* Set the yaw and the pitch of the provided {@link Location}
|
* Set the yaw and the pitch of the provided {@link Location}
|
||||||
* with the values inside the current {@link DirectionalVector}
|
* with the values inside the current {@link DirectionalVector}
|
||||||
* after conversion of these values
|
* after conversion of these values.
|
||||||
|
* @param l the location.
|
||||||
*/
|
*/
|
||||||
public void putIntoLocation(Location l) {
|
public void putIntoLocation(Location l) {
|
||||||
/* std : -PI/2 : <0 ? +2PI : MC
|
/* std : -PI/2 : <0 ? +2PI : MC
|
||||||
@@ -148,7 +154,10 @@ public class DirectionalVector {
|
|||||||
l.setPitch((float) Math.toDegrees(-pitch));
|
l.setPitch((float) Math.toDegrees(-pitch));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the vector pointing to the opposite direction.
|
||||||
|
* @return the opposite vector.
|
||||||
|
*/
|
||||||
public DirectionalVector getOpposite() {
|
public DirectionalVector getOpposite() {
|
||||||
return new DirectionalVector(
|
return new DirectionalVector(
|
||||||
-x,
|
-x,
|
||||||
@@ -163,6 +172,7 @@ public class DirectionalVector {
|
|||||||
* If the current direction is the player face direction,
|
* If the current direction is the player face direction,
|
||||||
* this method return the direction of the back of the head.
|
* this method return the direction of the back of the head.
|
||||||
* This is an alias of {@link #getOpposite()}
|
* This is an alias of {@link #getOpposite()}
|
||||||
|
* @return the opposite of this vector.
|
||||||
*/
|
*/
|
||||||
public DirectionalVector getBackDirection() {
|
public DirectionalVector getBackDirection() {
|
||||||
return getOpposite();
|
return getOpposite();
|
||||||
@@ -171,6 +181,7 @@ public class DirectionalVector {
|
|||||||
/**
|
/**
|
||||||
* If the current direction is the player face direction,
|
* If the current direction is the player face direction,
|
||||||
* this method return the direction of the bottom of the head.
|
* this method return the direction of the bottom of the head.
|
||||||
|
* @return the vector pointing on the bottom, as if this vector was the front orientation of a player head.
|
||||||
*/
|
*/
|
||||||
public DirectionalVector getBottomDirection() {
|
public DirectionalVector getBottomDirection() {
|
||||||
return new DirectionalVector(
|
return new DirectionalVector(
|
||||||
@@ -182,6 +193,7 @@ public class DirectionalVector {
|
|||||||
/**
|
/**
|
||||||
* If the current direction is the player face direction,
|
* If the current direction is the player face direction,
|
||||||
* this method return the direction of the top of the head.
|
* this method return the direction of the top of the head.
|
||||||
|
* @return the vector pointing on the top, as if this vector was the front orientation of a player head.
|
||||||
*/
|
*/
|
||||||
public DirectionalVector getTopDirection() {
|
public DirectionalVector getTopDirection() {
|
||||||
return new DirectionalVector(
|
return new DirectionalVector(
|
||||||
@@ -194,6 +206,7 @@ public class DirectionalVector {
|
|||||||
/**
|
/**
|
||||||
* If the current direction is the player face direction,
|
* If the current direction is the player face direction,
|
||||||
* this method return the direction of the left of the head.
|
* this method return the direction of the left of the head.
|
||||||
|
* @return the vector pointing on the left, as if this vector was the front orientation of a player head.
|
||||||
*/
|
*/
|
||||||
public DirectionalVector getLeftDirection() {
|
public DirectionalVector getLeftDirection() {
|
||||||
return new DirectionalVector(
|
return new DirectionalVector(
|
||||||
@@ -206,6 +219,7 @@ public class DirectionalVector {
|
|||||||
/**
|
/**
|
||||||
* If the current direction is the player face direction,
|
* If the current direction is the player face direction,
|
||||||
* this method return the direction of the right of the head.
|
* this method return the direction of the right of the head.
|
||||||
|
* @return the vector pointing on the right, as if this vector was the front orientation of a player head.
|
||||||
*/
|
*/
|
||||||
public DirectionalVector getRightDirection() {
|
public DirectionalVector getRightDirection() {
|
||||||
return new DirectionalVector(
|
return new DirectionalVector(
|
||||||
|
@@ -2,24 +2,29 @@ package fr.pandacube.lib.paper.geometry;
|
|||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
|
import org.bukkit.util.RayTraceResult;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class related to geometry and Minecraft.
|
||||||
|
*/
|
||||||
public class GeometryUtil {
|
public class GeometryUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value equal to <code>{@link Math#PI}</code>.
|
* Value equal to <code>{@link Math#PI}</code>.
|
||||||
*/
|
*/
|
||||||
public static final double PI = Math.PI;
|
static final double PI = Math.PI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value equal to <code>{@link Math#PI} / 2</code>.
|
* Value equal to <code>{@link Math#PI} / 2</code>.
|
||||||
*/
|
*/
|
||||||
public static final double PId2 = PI/2;
|
static final double PId2 = PI/2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value equal to <code>{@link Math#PI} * 2</code>.
|
* Value equal to <code>{@link Math#PI} * 2</code>.
|
||||||
*/
|
*/
|
||||||
public static final double PIx2 = PI*2;
|
static final double PIx2 = PI*2;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -55,9 +60,21 @@ public class GeometryUtil {
|
|||||||
* Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight
|
* Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight
|
||||||
*/
|
*/
|
||||||
public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.27;
|
public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.27;
|
||||||
|
/**
|
||||||
|
* The size of a skin pixel.
|
||||||
|
*/
|
||||||
public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32;
|
public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32;
|
||||||
|
/**
|
||||||
|
* The height of the center of rotation of the head, that is the distance between neck and the player's foot.
|
||||||
|
*/
|
||||||
public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24;
|
public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24;
|
||||||
|
/**
|
||||||
|
* The height of the center of rotation of the head, that is the distance between neck and the player's foot, but when the player is sneaking.
|
||||||
|
*/
|
||||||
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_ROTATION_HEIGHT_SNEAK = PLAYER_HEAD_ROTATION_HEIGHT - (PLAYER_SKIN_HEIGHT - PLAYER_SKIN_HEIGHT_SNEAK);
|
||||||
|
/**
|
||||||
|
* The size of the first layer of the players head.
|
||||||
|
*/
|
||||||
public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8;
|
public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8;
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +84,6 @@ public class GeometryUtil {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -76,7 +92,7 @@ public class GeometryUtil {
|
|||||||
/**
|
/**
|
||||||
* Get the {@link Location}s of the 8 vertex of the player head<br/>
|
* Get the {@link Location}s of the 8 vertex of the player head<br/>
|
||||||
* This method only work if the player is standing up
|
* This method only work if the player is standing up
|
||||||
* (not dead, not gliding, not sleeping).
|
* (not dead, not gliding, not sleeping, not swimming).
|
||||||
* @param playerLocation the location of the player, generally provided by {@link Player#getLocation()}
|
* @param playerLocation the location of the player, generally provided by {@link Player#getLocation()}
|
||||||
* @param isSneaking if the player is sneaking. Generally {@link Player#isSneaking()}
|
* @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).
|
* @return an array of 8 {@link Location}s with x, y, and z values filled (yaw and pitch are ignored).
|
||||||
@@ -129,27 +145,22 @@ public class GeometryUtil {
|
|||||||
/**
|
/**
|
||||||
* Check if the path from <i>start</i> location to <i>end</i> pass through
|
* 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>.
|
* the axis aligned bounding box defined by <i>min</i> and <i>max</i>.
|
||||||
|
* @param start the start of the path.
|
||||||
|
* @param end the end of the path.
|
||||||
|
* @param min the min of the bounding box.
|
||||||
|
* @param max the max of the bounding box.
|
||||||
|
* @return true if the path intersects the bounding box.
|
||||||
|
* @deprecated use {@link BoundingBox#rayTrace(Vector, Vector, double)} instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static boolean hasIntersection(Vector start, Vector end, Vector min, Vector max) {
|
public static boolean hasIntersection(Vector start, Vector end, Vector min, Vector max) {
|
||||||
final double epsilon = 0.0001f;
|
RayTraceResult res = BoundingBox.of(min, max).rayTrace(start, end.clone().subtract(start), end.distance(start));
|
||||||
|
return res != null;
|
||||||
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()));
|
|
||||||
|
|
||||||
return !(
|
|
||||||
Math.abs(c.getX()) > e.getX() + ad.getX()
|
|
||||||
|| Math.abs(c.getY()) > e.getY() + ad.getY()
|
|
||||||
|| Math.abs(c.getZ()) > e.getX() + ad.getZ()
|
|
||||||
|| Math.abs(d.getY() * c.getZ() - d.getZ() * c.getY()) > e.getY() * ad.getZ() + e.getZ() * ad.getY() + epsilon
|
|
||||||
|| Math.abs(d.getZ() * c.getX() - d.getX() * c.getZ()) > e.getZ() * ad.getX() + e.getX() * ad.getZ() + epsilon
|
|
||||||
|| Math.abs(d.getX() * c.getY() - d.getY() * c.getX()) > e.getX() * ad.getY() + e.getY() * ad.getX() + epsilon
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private GeometryUtil() {}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,11 +2,10 @@ package fr.pandacube.lib.paper.geometry.blocks;
|
|||||||
|
|
||||||
import fr.pandacube.lib.util.RandomUtil;
|
import fr.pandacube.lib.util.RandomUtil;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.block.Block;
|
|
||||||
import org.bukkit.util.BlockVector;
|
import org.bukkit.util.BlockVector;
|
||||||
import org.bukkit.util.BoundingBox;
|
import org.bukkit.util.BoundingBox;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
@@ -30,10 +29,19 @@ public class AABBBlock implements BlockSet, Cloneable {
|
|||||||
volume = original.volume;
|
volume = original.volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link AABBBlock} based on the two provided Bukkit's {@link Vector}.
|
||||||
|
* @param p1 the first vector.
|
||||||
|
* @param p2 the second vector.
|
||||||
|
*/
|
||||||
public AABBBlock(Vector p1, Vector p2) {
|
public AABBBlock(Vector p1, Vector p2) {
|
||||||
this(p1.getBlockX(), p1.getBlockY(), p1.getBlockZ(), p2.getBlockX(), p2.getBlockY(), p2.getBlockZ());
|
this(p1.getBlockX(), p1.getBlockY(), p1.getBlockZ(), p2.getBlockX(), p2.getBlockY(), p2.getBlockZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link AABBBlock} based on the provided Bukkit's {@link BoundingBox}.
|
||||||
|
* @param bb the bounding box.
|
||||||
|
*/
|
||||||
public AABBBlock(BoundingBox bb) {
|
public AABBBlock(BoundingBox bb) {
|
||||||
pos1 = bb.getMin();
|
pos1 = bb.getMin();
|
||||||
pos2 = bb.getMax();
|
pos2 = bb.getMax();
|
||||||
@@ -41,15 +49,35 @@ public class AABBBlock implements BlockSet, Cloneable {
|
|||||||
volume = (int) bb.getVolume();
|
volume = (int) bb.getVolume();
|
||||||
bukkitBoundingBox = bb;
|
bukkitBoundingBox = bb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link AABBBlock} based on the two provided Bukkit's {@link Location}.
|
||||||
|
* The worlds defined in the provided locations are ignored.
|
||||||
|
* @param l1 the first location.
|
||||||
|
* @param l2 the second location.
|
||||||
|
*/
|
||||||
public AABBBlock(Location l1, Location l2) {
|
public AABBBlock(Location l1, Location l2) {
|
||||||
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
|
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link AABBBlock} based on the two provided Bukkit's {@link BlockVector}.
|
||||||
|
* @param l1 the first block vector.
|
||||||
|
* @param l2 the second block vector.
|
||||||
|
*/
|
||||||
public AABBBlock(BlockVector l1, BlockVector l2) {
|
public AABBBlock(BlockVector l1, BlockVector l2) {
|
||||||
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
|
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link AABBBlock} based on the individual coordinates of the 2 vectors.
|
||||||
|
* @param p1x the x value of the first vector.
|
||||||
|
* @param p1y the y value of the first vector.
|
||||||
|
* @param p1z the z value of the first vector.
|
||||||
|
* @param p2x the x value of the second vector.
|
||||||
|
* @param p2y the y value of the second vector.
|
||||||
|
* @param p2z the z value of the second vector.
|
||||||
|
*/
|
||||||
public AABBBlock(int p1x, int p1y, int p1z, int p2x, int p2y, int p2z) {
|
public AABBBlock(int p1x, int p1y, int p1z, int p2x, int p2y, int p2z) {
|
||||||
/*
|
/*
|
||||||
* Prends les points extérieurs permettant de former un bounding box englobant
|
* Prends les points extérieurs permettant de former un bounding box englobant
|
||||||
@@ -74,22 +102,45 @@ public class AABBBlock implements BlockSet, Cloneable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the "minimum" {@link Vector}, that is the vector with the lowest coordinates that is inside this bounding box.
|
||||||
|
* @return the minimum vector.
|
||||||
|
*/
|
||||||
public Vector getMin() {
|
public Vector getMin() {
|
||||||
return pos1.clone();
|
return pos1.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the "maximum" {@link Vector}, that is the vector with the highest coordinates that is inside this bounding box.
|
||||||
|
* @return the maximum vector.
|
||||||
|
*/
|
||||||
public Vector getMax() {
|
public Vector getMax() {
|
||||||
return pos2.clone();
|
return pos2.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link BlockVector} with the lowest coordinates in this bounding box.
|
||||||
|
* @return the minimum block vector.
|
||||||
|
*/
|
||||||
public BlockVector getMinBlock() {
|
public BlockVector getMinBlock() {
|
||||||
return pos1.toBlockVector();
|
return pos1.toBlockVector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link BlockVector} with the highest coordinates in this bounding box.
|
||||||
|
* @return the maximum block vector.
|
||||||
|
*/
|
||||||
public BlockVector getMaxBlock() {
|
public BlockVector getMaxBlock() {
|
||||||
return pos2.clone().add(new Vector(-1, -1, -1)).toBlockVector();
|
return pos2.clone().add(new Vector(-1, -1, -1)).toBlockVector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a new {@link AABBBlock} with its coordinates shifted by the provided amount.
|
||||||
|
* @param x the x shift.
|
||||||
|
* @param y the y shift.
|
||||||
|
* @param z the z shift.
|
||||||
|
* @return a shifted bounding box.
|
||||||
|
*/
|
||||||
public AABBBlock shift(int x, int y, int z) {
|
public AABBBlock shift(int x, int y, int z) {
|
||||||
return new AABBBlock(this, x, y, z);
|
return new AABBBlock(this, x, y, z);
|
||||||
}
|
}
|
||||||
@@ -101,7 +152,6 @@ public class AABBBlock implements BlockSet, Cloneable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public boolean overlaps(BoundingBox bb) {
|
public boolean overlaps(BoundingBox bb) {
|
||||||
return asBukkitBoundingBox().overlaps(bb);
|
return asBukkitBoundingBox().overlaps(bb);
|
||||||
}
|
}
|
||||||
@@ -109,15 +159,23 @@ public class AABBBlock implements BlockSet, Cloneable {
|
|||||||
public boolean isInside(Vector v) {
|
public boolean isInside(Vector v) {
|
||||||
return asBukkitBoundingBox().contains(v);
|
return asBukkitBoundingBox().contains(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the coordinate of the center of this bounding box.
|
||||||
|
* @return the center of this bounding box.
|
||||||
|
*/
|
||||||
public Vector getCenter() {
|
public Vector getCenter() {
|
||||||
return center.clone();
|
return center.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getVolume() {
|
public long getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Bukkit equivalent of this bounding box.
|
||||||
|
* @return a {@link BoundingBox} corresponding to this {@link AABBBlock}.
|
||||||
|
*/
|
||||||
public BoundingBox asBukkitBoundingBox() {
|
public BoundingBox asBukkitBoundingBox() {
|
||||||
if (bukkitBoundingBox == null) {
|
if (bukkitBoundingBox == null) {
|
||||||
bukkitBoundingBox = new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(),
|
bukkitBoundingBox = new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(),
|
||||||
@@ -134,7 +192,7 @@ public class AABBBlock implements BlockSet, Cloneable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<BlockVector> iterator() {
|
public @NotNull Iterator<BlockVector> iterator() {
|
||||||
return new Iterator<>() {
|
return new Iterator<>() {
|
||||||
private int x = pos1.getBlockX(),
|
private int x = pos1.getBlockX(),
|
||||||
y = pos1.getBlockY(),
|
y = pos1.getBlockY(),
|
||||||
@@ -162,22 +220,6 @@ public class AABBBlock implements BlockSet, Cloneable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -12,19 +12,33 @@ import java.util.Collection;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group of {@link AABBBlock}.
|
||||||
|
*/
|
||||||
public class AABBBlockGroup implements BlockSet {
|
public class AABBBlockGroup implements BlockSet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of {@link AABBBlock} contained in this group. This list is unmodifiable.
|
||||||
|
*/
|
||||||
public final List<AABBBlock> subAABB;
|
public final List<AABBBlock> subAABB;
|
||||||
|
|
||||||
private final AABBBlock englobingAABB;
|
private final AABBBlock englobingAABB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link AABBBlockGroup}.
|
||||||
|
* @param in the list of {@link AABBBlock} that will be contained in this group.
|
||||||
|
*/
|
||||||
public AABBBlockGroup(Collection<AABBBlock> in) {
|
public AABBBlockGroup(Collection<AABBBlock> in) {
|
||||||
if (in.isEmpty())
|
if (in.isEmpty())
|
||||||
throw new IllegalArgumentException("Provided collection must not be empty.");
|
throw new IllegalArgumentException("Provided collection must not be empty.");
|
||||||
subAABB = List.copyOf(in);
|
subAABB = List.copyOf(in);
|
||||||
englobingAABB = initEnglobingAABB();
|
englobingAABB = initEnglobingAABB();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link AABBBlockGroup}.
|
||||||
|
* @param in an array of {@link AABBBlock} that will be contained in this group.
|
||||||
|
*/
|
||||||
public AABBBlockGroup(AABBBlock... in) {
|
public AABBBlockGroup(AABBBlock... in) {
|
||||||
this(Arrays.asList(in));
|
this(Arrays.asList(in));
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,58 @@
|
|||||||
package fr.pandacube.lib.paper.geometry.blocks;
|
package fr.pandacube.lib.paper.geometry.blocks;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.util.BlockVector;
|
import org.bukkit.util.BlockVector;
|
||||||
import org.bukkit.util.BoundingBox;
|
import org.bukkit.util.BoundingBox;
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a set of blocks in a world.
|
||||||
|
*/
|
||||||
public interface BlockSet extends Iterable<BlockVector> {
|
public interface BlockSet extends Iterable<BlockVector> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a random coordinate that is inside this block set.
|
||||||
|
* @return a random coordinate inside this block set.
|
||||||
|
*/
|
||||||
Vector getRandomPosition();
|
Vector getRandomPosition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the volume, in blocks (or cubic meters), of this block set.
|
||||||
|
* @return the volume of this block set.
|
||||||
|
*/
|
||||||
long getVolume();
|
long getVolume();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the englobing bounding box if this block set.
|
||||||
|
* @return the englobing bounding box if this block set.
|
||||||
|
*/
|
||||||
AABBBlock getEnglobingAABB();
|
AABBBlock getEnglobingAABB();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this block set overlaps the provided bounding box.
|
||||||
|
* @param bb the provided bounding box
|
||||||
|
* @return true if its overlaps, false otherwise.
|
||||||
|
*/
|
||||||
boolean overlaps(BoundingBox bb);
|
boolean overlaps(BoundingBox bb);
|
||||||
|
/**
|
||||||
|
* Tells if this block set overlaps the bounding box of the provided entity.
|
||||||
|
* @param e the provided entity.
|
||||||
|
* @return true if its overlaps, false otherwise.
|
||||||
|
*/
|
||||||
default boolean overlaps(Entity e) {
|
default boolean overlaps(Entity e) {
|
||||||
return overlaps(e.getBoundingBox());
|
return overlaps(e.getBoundingBox());
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Tells if this block set overlaps the provided one. that is there is at least one block in common.
|
||||||
|
* @param bs the provided block set.
|
||||||
|
* @return true if its overlaps, false otherwise.
|
||||||
|
*/
|
||||||
default boolean overlaps(BlockSet bs) {
|
default boolean overlaps(BlockSet bs) {
|
||||||
if (this instanceof AABBBlock b1) {
|
if (this instanceof AABBBlock b1) {
|
||||||
if (bs instanceof AABBBlock b2)
|
if (bs instanceof AABBBlock b2)
|
||||||
@@ -37,20 +72,78 @@ public interface BlockSet extends Iterable<BlockVector> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided vector is inside this bounding box.
|
||||||
|
* @param v the vector.
|
||||||
|
* @return true if its inside, false otherwise.
|
||||||
|
*/
|
||||||
boolean isInside(Vector v);
|
boolean isInside(Vector v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided location is inside this bounding box.
|
||||||
|
* The world of the location is ignored.
|
||||||
|
* @param l the location.
|
||||||
|
* @return true if its inside, false otherwise.
|
||||||
|
*/
|
||||||
default boolean isInside(Location l) {
|
default boolean isInside(Location l) {
|
||||||
return isInside(l.toVector());
|
return isInside(l.toVector());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided block is inside this bounding box.
|
||||||
|
* The world of the block is ignored.
|
||||||
|
* @param b the block.
|
||||||
|
* @return true if its inside, false otherwise.
|
||||||
|
*/
|
||||||
default boolean isInside(Block b) {
|
default boolean isInside(Block b) {
|
||||||
return isInside(b.getLocation().add(.5, .5, .5));
|
return isInside(b.getLocation().add(.5, .5, .5));
|
||||||
}
|
}
|
||||||
default boolean isInside(Entity p) {
|
|
||||||
return isInside(p.getLocation());
|
/**
|
||||||
|
* Tells if the provided entity is inside this bounding box.
|
||||||
|
* It calls {@link #isInside(Location)} using the returned value of {@link Entity#getLocation()}.
|
||||||
|
* The world of the entity is ignored.
|
||||||
|
* @param e the entity.
|
||||||
|
* @return true if its inside, false otherwise.
|
||||||
|
*/
|
||||||
|
default boolean isInside(Entity e) {
|
||||||
|
return isInside(e.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an iterator iterating through all the blocks of this block set.
|
||||||
|
* @param w the world of the blocks.
|
||||||
|
* @return a new iterator.
|
||||||
|
*/
|
||||||
|
default Iterable<Block> asBlockIterable(World w) {
|
||||||
|
return () -> new Iterator<>() {
|
||||||
|
final Iterator<BlockVector> nested = BlockSet.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());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the two block set overlap each other.
|
||||||
|
* This method works on any implementation of this interface, but they should override the
|
||||||
|
* {@link #overlaps(BlockSet)} method to provide a more optimized code.
|
||||||
|
* @param bs1 the first block set.
|
||||||
|
* @param bs2 the second block set.
|
||||||
|
* @return true if the two block set overlap, false otherwise.
|
||||||
|
*/
|
||||||
static boolean overlap(BlockSet bs1, BlockSet bs2) {
|
static boolean overlap(BlockSet bs1, BlockSet bs2) {
|
||||||
if (!bs1.getEnglobingAABB().overlaps(bs2.getEnglobingAABB()))
|
if (!bs1.getEnglobingAABB().overlaps(bs2.getEnglobingAABB()))
|
||||||
return false;
|
return false;
|
||||||
|
@@ -37,13 +37,21 @@ public class GUIHotBar implements Listener {
|
|||||||
private final int defaultSlot;
|
private final int defaultSlot;
|
||||||
|
|
||||||
private final List<Player> currentPlayers = new ArrayList<>();
|
private final List<Player> currentPlayers = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup a new gui hot bar. You should not instantiate more than one hot bar.
|
||||||
|
* @param defaultSlot the default slot (currently held item) when the player joins the hot bar.
|
||||||
|
*/
|
||||||
public GUIHotBar(int defaultSlot) {
|
public GUIHotBar(int defaultSlot) {
|
||||||
this.defaultSlot = Math.max(0, Math.min(8, defaultSlot));
|
this.defaultSlot = Math.max(0, Math.min(8, defaultSlot));
|
||||||
|
|
||||||
BukkitEvent.register(this);
|
BukkitEvent.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables this hot bar.
|
||||||
|
* @param clearPlayerMenuItems if the items of this hot bar should be removed from the players inventories.
|
||||||
|
*/
|
||||||
public void disable(boolean clearPlayerMenuItems) {
|
public void disable(boolean clearPlayerMenuItems) {
|
||||||
removeAllPlayers(clearPlayerMenuItems);
|
removeAllPlayers(clearPlayerMenuItems);
|
||||||
|
|
||||||
@@ -53,9 +61,10 @@ public class GUIHotBar implements Listener {
|
|||||||
/**
|
/**
|
||||||
* Add the item to this hot bar menu. if there is already players hooked to this hot bar, the item will be directly added to
|
* Add the item to this hot bar menu. if there is already players hooked to this hot bar, the item will be directly added to
|
||||||
* their inventories.
|
* their inventories.
|
||||||
* @param i the item stack
|
* @param i the item stack.
|
||||||
* @param setter code executed to put the item in the inventory. Additionally, check for permission before doing the addition.
|
* @param setter code executed to put the item in the inventory. Additionally, check for permission before doing the addition.
|
||||||
* @param run the Runnable to run when the user right-click on the item in the hot bar.
|
* @param run the Runnable to run when the user right-click on the item in the hot bar.
|
||||||
|
* @return itself for daisy-chaining.
|
||||||
*/
|
*/
|
||||||
public GUIHotBar addItem(ItemStack i, BiConsumer<PlayerInventory, ItemStack> setter, Consumer<Player> run) {
|
public GUIHotBar addItem(ItemStack i, BiConsumer<PlayerInventory, ItemStack> setter, Consumer<Player> run) {
|
||||||
itemsAndSetters.put(i, setter);
|
itemsAndSetters.put(i, setter);
|
||||||
@@ -69,8 +78,9 @@ public class GUIHotBar implements Listener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the hot bar elements to this player, or update them if applicable.
|
* Add the hot bar elements to this player, or update them if applicable.
|
||||||
*
|
* <br>
|
||||||
* The player is automatically removed when they quit. You can remove it before by calling {@link #removePlayer(Player, boolean)}.
|
* The player is automatically removed when they quit. You can remove it before by calling {@link #removePlayer(Player, boolean)}.
|
||||||
|
* @param p the player to add.
|
||||||
*/
|
*/
|
||||||
public void addPlayer(Player p) {
|
public void addPlayer(Player p) {
|
||||||
if (!currentPlayers.contains(p))
|
if (!currentPlayers.contains(p))
|
||||||
@@ -85,6 +95,7 @@ public class GUIHotBar implements Listener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach this player from this hot bar manager and removes the managed items from the players inventory.
|
* Detach this player from this hot bar manager and removes the managed items from the players inventory.
|
||||||
|
* @param p the player to remove.
|
||||||
*/
|
*/
|
||||||
public void removePlayer(Player p) {
|
public void removePlayer(Player p) {
|
||||||
removePlayer(p, true);
|
removePlayer(p, true);
|
||||||
@@ -92,6 +103,8 @@ public class GUIHotBar implements Listener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach this player from this hot bar manager and optionally removes the managed items from the players inventory.
|
* Detach this player from this hot bar manager and optionally removes the managed items from the players inventory.
|
||||||
|
* @param p the player to remove.
|
||||||
|
* @param clearMenuItems if the items from this hot bar should be removed from the player inventory.
|
||||||
*/
|
*/
|
||||||
public void removePlayer(Player p, boolean clearMenuItems) {
|
public void removePlayer(Player p, boolean clearMenuItems) {
|
||||||
if (!currentPlayers.contains(p))
|
if (!currentPlayers.contains(p))
|
||||||
@@ -106,18 +119,28 @@ public class GUIHotBar implements Listener {
|
|||||||
currentPlayers.remove(p);
|
currentPlayers.remove(p);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided player is attached to this hot bar.
|
||||||
|
* @param p the player to check.
|
||||||
|
* @return true if the player is attached, false otherwise.
|
||||||
|
*/
|
||||||
public boolean containsPlayer(Player p) {
|
public boolean containsPlayer(Player p) {
|
||||||
return currentPlayers.contains(p);
|
return currentPlayers.contains(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach all players from this hot bar.
|
||||||
|
*/
|
||||||
public void removeAllPlayers() {
|
public void removeAllPlayers() {
|
||||||
removeAllPlayers(true);
|
removeAllPlayers(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach all players from this hot bar.
|
||||||
|
* @param clearMenuItems if the items from this hot bar should be removed from the player inventory.
|
||||||
|
*/
|
||||||
public void removeAllPlayers(boolean clearMenuItems) {
|
public void removeAllPlayers(boolean clearMenuItems) {
|
||||||
for (Player p : new ArrayList<>(currentPlayers))
|
for (Player p : new ArrayList<>(currentPlayers))
|
||||||
removePlayer(p, clearMenuItems);
|
removePlayer(p, clearMenuItems);
|
||||||
@@ -127,7 +150,7 @@ public class GUIHotBar implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void addItemToPlayer(Player p, ItemStack is) {
|
private void addItemToPlayer(Player p, ItemStack is) {
|
||||||
if (!itemsAndSetters.containsKey(is))
|
if (!itemsAndSetters.containsKey(is))
|
||||||
throw new IllegalArgumentException("The provided ItemStack is not registered in this GUIHotBar");
|
throw new IllegalArgumentException("The provided ItemStack is not registered in this GUIHotBar");
|
||||||
if (!currentPlayers.contains(p))
|
if (!currentPlayers.contains(p))
|
||||||
@@ -135,7 +158,7 @@ public class GUIHotBar implements Listener {
|
|||||||
itemsAndSetters.get(is).accept(p.getInventory(), is.clone());
|
itemsAndSetters.get(is).accept(p.getInventory(), is.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeItemFromPlayer(Player p, ItemStack is) {
|
private void removeItemFromPlayer(Player p, ItemStack is) {
|
||||||
p.getInventory().remove(is);
|
p.getInventory().remove(is);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +167,7 @@ public class GUIHotBar implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerDropItem(PlayerDropItemEvent event) {
|
void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||||
if (!currentPlayers.contains(event.getPlayer()))
|
if (!currentPlayers.contains(event.getPlayer()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -159,7 +182,7 @@ public class GUIHotBar implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
void onPlayerInteract(PlayerInteractEvent event) {
|
||||||
if (!currentPlayers.contains(event.getPlayer()))
|
if (!currentPlayers.contains(event.getPlayer()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -188,7 +211,7 @@ public class GUIHotBar implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onInventoryClick(InventoryClickEvent event) {
|
void onInventoryClick(InventoryClickEvent event) {
|
||||||
if (event.getClickedInventory() == null || !(event.getClickedInventory() instanceof PlayerInventory inv))
|
if (event.getClickedInventory() == null || !(event.getClickedInventory() instanceof PlayerInventory inv))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -213,20 +236,20 @@ public class GUIHotBar implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
removePlayer(event.getPlayer());
|
removePlayer(event.getPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
void onPlayerDeath(PlayerDeathEvent event) {
|
||||||
if (!currentPlayers.contains(event.getEntity()))
|
if (!currentPlayers.contains(event.getEntity()))
|
||||||
return;
|
return;
|
||||||
event.getDrops().removeAll(itemsAndSetters.keySet());
|
event.getDrops().removeAll(itemsAndSetters.keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||||
if (!currentPlayers.contains(event.getPlayer()))
|
if (!currentPlayers.contains(event.getPlayer()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ public class GUIInventory implements Listener {
|
|||||||
if (title == null)
|
if (title == null)
|
||||||
inv = Bukkit.createInventory(null, nbLines * 9);
|
inv = Bukkit.createInventory(null, nbLines * 9);
|
||||||
else
|
else
|
||||||
inv = Bukkit.createInventory(null, nbLines * 9, title.getAdv());
|
inv = Bukkit.createInventory(null, nbLines * 9, title.get());
|
||||||
|
|
||||||
setCloseEvent(closeEventAction);
|
setCloseEvent(closeEventAction);
|
||||||
|
|
||||||
@@ -49,7 +49,11 @@ public class GUIInventory implements Listener {
|
|||||||
Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin());
|
Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the action when the player closes the inventory window.
|
||||||
|
* @param closeEventAction the action to run.
|
||||||
|
*/
|
||||||
protected void setCloseEvent(Consumer<InventoryCloseEvent> closeEventAction) {
|
protected void setCloseEvent(Consumer<InventoryCloseEvent> closeEventAction) {
|
||||||
onCloseEvent = closeEventAction;
|
onCloseEvent = closeEventAction;
|
||||||
}
|
}
|
||||||
@@ -188,13 +192,13 @@ public class GUIInventory implements Listener {
|
|||||||
onCloseEvent.accept(event);
|
onCloseEvent.accept(event);
|
||||||
isOpened = false;
|
isOpened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the number of inventory line needed to show the provided number of slot.
|
||||||
|
* @param nb the number of slot.
|
||||||
|
* @return the number of line.
|
||||||
|
*/
|
||||||
public static int nbLineForNbElements(int nb) {
|
public static int nbLineForNbElements(int nb) {
|
||||||
return nb / 9 + ((nb % 9 == 0) ? 0 : 1);
|
return nb / 9 + ((nb % 9 == 0) ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,191 @@
|
|||||||
|
package fr.pandacube.lib.paper.inventory;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.HumanEntity;
|
||||||
|
import org.bukkit.inventory.EquipmentSlot;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.PlayerInventory;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy implementation of a player inventory.
|
||||||
|
*/
|
||||||
|
public class DummyPlayerInventory extends InventoryWrapper implements PlayerInventory {
|
||||||
|
|
||||||
|
private int heldItemSlot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dummy player inventory.
|
||||||
|
* @param base the inventory itself.
|
||||||
|
* @param heldItemSlot the currently held item slot, from 0 to 8.
|
||||||
|
*/
|
||||||
|
public DummyPlayerInventory(Inventory base, int heldItemSlot) {
|
||||||
|
super(base);
|
||||||
|
if (base.getSize() < 41)
|
||||||
|
throw new IllegalArgumentException("base inventory should have a size of 41 (" + base.getSize() + " given).");
|
||||||
|
if (heldItemSlot < 0 || heldItemSlot > 8)
|
||||||
|
throw new IllegalArgumentException("heldItemSlot should be between 0 and 8 inclusive.");
|
||||||
|
this.heldItemSlot = heldItemSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ItemStack @NotNull [] getStorageContents() {
|
||||||
|
return Arrays.copyOfRange(getContents(), 0, 36);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ItemStack @NotNull [] getArmorContents() {
|
||||||
|
return Arrays.copyOfRange(getContents(), 36, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setArmorContents(@Nullable ItemStack[] items) {
|
||||||
|
this.setSlots(items, 36, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ItemStack @NotNull [] getExtraContents() {
|
||||||
|
return Arrays.copyOfRange(getContents(), 40, getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExtraContents(@Nullable ItemStack[] items) {
|
||||||
|
this.setSlots(items, 40, getSize() - 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSlots(ItemStack[] items, int baseSlot, int length) {
|
||||||
|
if (items == null) {
|
||||||
|
items = new ItemStack[length];
|
||||||
|
}
|
||||||
|
Preconditions.checkArgument(items.length <= length, "items.length must be < %s", length);
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (i >= items.length) {
|
||||||
|
this.setItem(baseSlot + i, null);
|
||||||
|
} else {
|
||||||
|
this.setItem(baseSlot + i, items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack getHelmet() {
|
||||||
|
return getItem(39);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHelmet(@Nullable ItemStack helmet) {
|
||||||
|
setItem(39, helmet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack getChestplate() {
|
||||||
|
return getItem(38);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChestplate(@Nullable ItemStack chestplate) {
|
||||||
|
setItem(38, chestplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack getLeggings() {
|
||||||
|
return getItem(37);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLeggings(@Nullable ItemStack leggings) {
|
||||||
|
setItem(37, leggings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack getBoots() {
|
||||||
|
return getItem(36);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBoots(@Nullable ItemStack boots) {
|
||||||
|
setItem(36, boots);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setItem(EquipmentSlot slot, ItemStack item) {
|
||||||
|
Preconditions.checkArgument(slot != null, "slot must not be null");
|
||||||
|
|
||||||
|
switch (slot) {
|
||||||
|
case HAND -> this.setItemInMainHand(item);
|
||||||
|
case OFF_HAND -> this.setItemInOffHand(item);
|
||||||
|
case FEET -> this.setBoots(item);
|
||||||
|
case LEGS -> this.setLeggings(item);
|
||||||
|
case CHEST -> this.setChestplate(item);
|
||||||
|
case HEAD -> this.setHelmet(item);
|
||||||
|
default -> throw new IllegalArgumentException("Not implemented. This is a bug");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ItemStack getItem(@NotNull EquipmentSlot slot) {
|
||||||
|
return switch (slot) {
|
||||||
|
case HAND -> this.getItemInMainHand();
|
||||||
|
case OFF_HAND -> this.getItemInOffHand();
|
||||||
|
case FEET -> Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(Material.AIR));
|
||||||
|
case LEGS -> Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(Material.AIR));
|
||||||
|
case CHEST -> Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(Material.AIR));
|
||||||
|
case HEAD -> Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(Material.AIR));
|
||||||
|
case BODY -> new ItemStack(Material.AIR); // for horses/wolves armor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ItemStack getItemInMainHand() {
|
||||||
|
return Objects.requireNonNullElse(getItem(heldItemSlot), new ItemStack(Material.AIR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setItemInMainHand(@Nullable ItemStack item) {
|
||||||
|
setItem(heldItemSlot, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ItemStack getItemInOffHand() {
|
||||||
|
return Objects.requireNonNullElse(getItem(40), new ItemStack(Material.AIR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setItemInOffHand(@Nullable ItemStack item) {
|
||||||
|
setItem(40, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ItemStack getItemInHand() {
|
||||||
|
return getItemInMainHand();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setItemInHand(@Nullable ItemStack stack) {
|
||||||
|
setItemInMainHand(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeldItemSlot() {
|
||||||
|
return heldItemSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeldItemSlot(int slot) {
|
||||||
|
if (slot < 0 || slot > 8)
|
||||||
|
throw new IllegalArgumentException("Slot is not between 0 and 8 inclusive");
|
||||||
|
heldItemSlot = slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable HumanEntity getHolder() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
package fr.pandacube.lib.paper.inventory;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
@@ -13,12 +13,22 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for an {@link Inventory}.
|
||||||
|
* Can be overridden to add specific behaviour to some methods.
|
||||||
|
*/
|
||||||
public class InventoryWrapper implements Inventory {
|
public class InventoryWrapper implements Inventory {
|
||||||
private final Inventory base;
|
private final Inventory base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a wrapper for the provided inventory.
|
||||||
|
* @param base the wrapped inventory. Cannot be null.
|
||||||
|
* @throws NullPointerException if the base inventory is null.
|
||||||
|
*/
|
||||||
public InventoryWrapper(Inventory base) {
|
public InventoryWrapper(Inventory base) {
|
||||||
this.base = base;
|
this.base = Objects.requireNonNull(base, "base inventory cannot be null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
package fr.pandacube.lib.paper.inventory;
|
||||||
|
|
||||||
import com.google.common.collect.Streams;
|
import com.google.common.collect.Streams;
|
||||||
import fr.pandacube.lib.chat.Chat;
|
import fr.pandacube.lib.chat.Chat;
|
||||||
@@ -18,6 +18,9 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import static fr.pandacube.lib.chat.ChatStatic.chatComponent;
|
import static fr.pandacube.lib.chat.ChatStatic.chatComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link ItemStack}.
|
||||||
|
*/
|
||||||
public class ItemStackBuilder {
|
public class ItemStackBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,10 +80,22 @@ public class ItemStackBuilder {
|
|||||||
return (cachedMeta != null) ? cachedMeta : (cachedMeta = stack.getItemMeta());
|
return (cachedMeta != null) ? cachedMeta : (cachedMeta = stack.getItemMeta());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the provided updater on the {@link ItemMeta} instance of the built stack.
|
||||||
|
* @param metaUpdater the updater that will modify the meta.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder meta(Consumer<ItemMeta> metaUpdater) {
|
public ItemStackBuilder meta(Consumer<ItemMeta> metaUpdater) {
|
||||||
return meta(metaUpdater, ItemMeta.class);
|
return meta(metaUpdater, ItemMeta.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the provided updater on the {@link ItemMeta} instance of the built stack.
|
||||||
|
* @param metaUpdater the updater that will modify the meta.
|
||||||
|
* @param metaType the type of the meta instance.
|
||||||
|
* @param <T> the type of item meta.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public <T extends ItemMeta> ItemStackBuilder meta(Consumer<T> metaUpdater, Class<T> metaType) {
|
public <T extends ItemMeta> ItemStackBuilder meta(Consumer<T> metaUpdater, Class<T> metaType) {
|
||||||
stack.editMeta(metaType, m -> {
|
stack.editMeta(metaType, m -> {
|
||||||
metaUpdater.accept(m);
|
metaUpdater.accept(m);
|
||||||
@@ -88,38 +103,67 @@ public class ItemStackBuilder {
|
|||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the amount of the built stack.
|
||||||
|
* @param a the new amount.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder amount(int a) {
|
public ItemStackBuilder amount(int a) {
|
||||||
stack.setAmount(a);
|
stack.setAmount(a);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the display name of the item, directly passing to {@link ItemMeta#displayName(Component)}.
|
||||||
|
* @param displayName the new display name. Can be null to unset.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder rawDisplayName(Component displayName) {
|
public ItemStackBuilder rawDisplayName(Component displayName) {
|
||||||
return meta(m -> m.displayName(displayName));
|
return meta(m -> m.displayName(displayName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the display name of the item, filtering to make default italic to false.
|
||||||
|
* @param displayName the new display name. Can be null to unset.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder displayName(ComponentLike displayName) {
|
public ItemStackBuilder displayName(ComponentLike displayName) {
|
||||||
if (displayName != null) {
|
return rawDisplayName(displayName != null
|
||||||
return rawDisplayName(Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent());
|
? Chat.italicFalseIfNotSet(chatComponent(displayName)).asComponent()
|
||||||
}
|
: null);
|
||||||
else
|
|
||||||
return rawDisplayName(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the lore of the item, directly passing to {@link ItemMeta#lore(List)}.
|
||||||
|
* @param lore the new lore. Can be null to unset.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder rawLore(List<Component> lore) {
|
public ItemStackBuilder rawLore(List<Component> lore) {
|
||||||
return meta(m -> m.lore(lore));
|
return meta(m -> m.lore(lore));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the lore of the item, filtering to make default italic to false.
|
||||||
|
* @param lore the new lore. Can be null to unset.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder lore(List<? extends ComponentLike> lore) {
|
public ItemStackBuilder lore(List<? extends ComponentLike> lore) {
|
||||||
if (lore != null) {
|
if (lore != null) {
|
||||||
return rawLore(lore.stream()
|
return rawLore(lore.stream()
|
||||||
.map(line -> Chat.italicFalseIfNotSet(chatComponent(line)).getAdv())
|
.map(line -> Chat.italicFalseIfNotSet(chatComponent(line)).get())
|
||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return rawLore(Collections.emptyList());
|
return rawLore(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds new lore lines to the existing lore of the item.
|
||||||
|
* @param lores the added lore lines.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder addLoreAfter(List<? extends ComponentLike> lores) {
|
public ItemStackBuilder addLoreAfter(List<? extends ComponentLike> lores) {
|
||||||
if (lores != null) {
|
if (lores != null) {
|
||||||
List<Component> baseLore = getOrInitMeta().lore();
|
List<Component> baseLore = getOrInitMeta().lore();
|
||||||
@@ -128,14 +172,19 @@ public class ItemStackBuilder {
|
|||||||
Streams.concat(
|
Streams.concat(
|
||||||
baseLore.stream(),
|
baseLore.stream(),
|
||||||
lores.stream()
|
lores.stream()
|
||||||
.map(line -> Chat.italicFalseIfNotSet(chatComponent(line)).getAdv())
|
.map(line -> Chat.italicFalseIfNotSet(chatComponent(line)).get())
|
||||||
)
|
)
|
||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds new lore lines to the existing lore of the item.
|
||||||
|
* @param lores the added lore lines.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder addLoreAfter(ComponentLike... lores) {
|
public ItemStackBuilder addLoreAfter(ComponentLike... lores) {
|
||||||
if (lores == null || lores.length == 0)
|
if (lores == null || lores.length == 0)
|
||||||
return this;
|
return this;
|
||||||
@@ -143,69 +192,120 @@ public class ItemStackBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Enchant the item.
|
||||||
* Supports unsafe enchants.
|
* Supports unsafe enchants.
|
||||||
|
* @param enchantment the enchantment.
|
||||||
|
* @param level the enchant level.
|
||||||
|
* @return itself.
|
||||||
*/
|
*/
|
||||||
public ItemStackBuilder enchant(Enchantment enchantment, int level) {
|
public ItemStackBuilder enchant(Enchantment enchantment, int level) {
|
||||||
return meta(m -> m.addEnchant(enchantment, level, true));
|
return meta(m -> m.addEnchant(enchantment, level, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided flags to the item.
|
||||||
|
* @param flags he flags to add.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder flags(ItemFlag... flags) {
|
public ItemStackBuilder flags(ItemFlag... flags) {
|
||||||
return flags(true, flags);
|
return flags(true, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or removes the provided flags to the item.
|
||||||
|
* @param add true to add, false to remove.
|
||||||
|
* @param flags he flags to add.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder flags(boolean add, ItemFlag... flags) {
|
public ItemStackBuilder flags(boolean add, ItemFlag... flags) {
|
||||||
return add ? meta(m -> m.addItemFlags(flags))
|
return add ? meta(m -> m.addItemFlags(flags))
|
||||||
: meta(m -> m.removeItemFlags(flags));
|
: meta(m -> m.removeItemFlags(flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the enchants from the tooltip of the item.
|
||||||
|
* Will set the {@link ItemFlag#HIDE_ENCHANTS} flag of the item.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder hideEnchants() {
|
public ItemStackBuilder hideEnchants() {
|
||||||
return hideEnchants(true);
|
return hideEnchants(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets or unsets the {@link ItemFlag#HIDE_ENCHANTS} flag of the item.
|
||||||
|
* @param hide true to hide, false to show.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder hideEnchants(boolean hide) {
|
public ItemStackBuilder hideEnchants(boolean hide) {
|
||||||
return flags(hide, ItemFlag.HIDE_ENCHANTS);
|
return flags(hide, ItemFlag.HIDE_ENCHANTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the attributes from the tooltip of the item.
|
||||||
|
* Will set the {@link ItemFlag#HIDE_ATTRIBUTES} flag of the item.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder hideAttributes() {
|
public ItemStackBuilder hideAttributes() {
|
||||||
return hideAttributes(true);
|
return hideAttributes(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets or unsets the {@link ItemFlag#HIDE_ATTRIBUTES} flag of the item.
|
||||||
|
* @param hide true to hide, false to show.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder hideAttributes(boolean hide) {
|
public ItemStackBuilder hideAttributes(boolean hide) {
|
||||||
return flags(hide, ItemFlag.HIDE_ATTRIBUTES);
|
return flags(hide, ItemFlag.HIDE_ATTRIBUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the enchantment glint to the item, event if it's not enchant.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder fakeEnchant() {
|
public ItemStackBuilder fakeEnchant() {
|
||||||
return fakeEnchant(true);
|
return fakeEnchant(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the enchantment glint override to the item.
|
||||||
|
* @param apply true to enforce the enchantment glint, false to set to default.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder fakeEnchant(boolean apply) {
|
public ItemStackBuilder fakeEnchant(boolean apply) {
|
||||||
if (apply) {
|
return meta(m -> m.setEnchantmentGlintOverride(apply ? true : null));
|
||||||
enchant(Enchantment.UNBREAKING, 1);
|
|
||||||
return hideEnchants();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets this item as unbreakable.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder unbreakable() {
|
public ItemStackBuilder unbreakable() {
|
||||||
return unbreakable(true);
|
return unbreakable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the unbreakable status of this item.
|
||||||
|
* @param unbreakable the unbreakable status.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder unbreakable(boolean unbreakable) {
|
public ItemStackBuilder unbreakable(boolean unbreakable) {
|
||||||
return meta(m -> m.setUnbreakable(unbreakable));
|
return meta(m -> m.setUnbreakable(unbreakable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the damage value of this item.
|
||||||
|
* @param d the new damage value.
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
public ItemStackBuilder damage(int d) {
|
public ItemStackBuilder damage(int d) {
|
||||||
return meta(m -> m.setDamage(d), Damageable.class);
|
return meta(m -> m.setDamage(d), Damageable.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the {@link ItemStack}.
|
||||||
|
* @return the build item stack.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public ItemStack build() {
|
public ItemStack build() {
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
@@ -14,4 +14,7 @@ public class PaperJson {
|
|||||||
Json.registerTypeAdapterFactory(ItemStackAdapter.FACTORY);
|
Json.registerTypeAdapterFactory(ItemStackAdapter.FACTORY);
|
||||||
Json.registerTypeAdapterFactory(ConfigurationSerializableAdapter.FACTORY);
|
Json.registerTypeAdapterFactory(ConfigurationSerializableAdapter.FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private PaperJson() {}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import com.destroystokyo.paper.event.server.ServerTickEndEvent;
|
|||||||
import com.destroystokyo.paper.event.server.ServerTickStartEvent;
|
import com.destroystokyo.paper.event.server.ServerTickStartEvent;
|
||||||
import fr.pandacube.lib.chat.Chat;
|
import fr.pandacube.lib.chat.Chat;
|
||||||
import fr.pandacube.lib.chat.ChatColorGradient;
|
import fr.pandacube.lib.chat.ChatColorGradient;
|
||||||
import fr.pandacube.lib.chat.ChatColorUtil;
|
|
||||||
import fr.pandacube.lib.chat.ChatConfig.PandaTheme;
|
import fr.pandacube.lib.chat.ChatConfig.PandaTheme;
|
||||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||||
import fr.pandacube.lib.paper.players.PaperOffPlayer;
|
import fr.pandacube.lib.paper.players.PaperOffPlayer;
|
||||||
@@ -13,16 +12,15 @@ import fr.pandacube.lib.paper.scheduler.SchedulerUtil;
|
|||||||
import fr.pandacube.lib.paper.util.AutoUpdatedBossBar;
|
import fr.pandacube.lib.paper.util.AutoUpdatedBossBar;
|
||||||
import fr.pandacube.lib.paper.util.AutoUpdatedBossBar.BarUpdater;
|
import fr.pandacube.lib.paper.util.AutoUpdatedBossBar.BarUpdater;
|
||||||
import fr.pandacube.lib.players.standalone.AbstractPlayerManager;
|
import fr.pandacube.lib.players.standalone.AbstractPlayerManager;
|
||||||
import fr.pandacube.lib.util.log.Log;
|
|
||||||
import fr.pandacube.lib.util.MemoryUtil;
|
import fr.pandacube.lib.util.MemoryUtil;
|
||||||
import fr.pandacube.lib.util.MemoryUtil.MemoryUnit;
|
import fr.pandacube.lib.util.MemoryUtil.MemoryUnit;
|
||||||
import fr.pandacube.lib.util.TimeUtil;
|
import fr.pandacube.lib.util.TimeUtil;
|
||||||
|
import fr.pandacube.lib.util.log.Log;
|
||||||
import net.kyori.adventure.bossbar.BossBar;
|
import net.kyori.adventure.bossbar.BossBar;
|
||||||
import net.kyori.adventure.bossbar.BossBar.Color;
|
import net.kyori.adventure.bossbar.BossBar.Color;
|
||||||
import net.kyori.adventure.bossbar.BossBar.Overlay;
|
import net.kyori.adventure.bossbar.BossBar.Overlay;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.format.TextColor;
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.command.ConsoleCommandSender;
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
@@ -45,10 +43,17 @@ import static fr.pandacube.lib.chat.ChatStatic.infoText;
|
|||||||
import static fr.pandacube.lib.chat.ChatStatic.successText;
|
import static fr.pandacube.lib.chat.ChatStatic.successText;
|
||||||
import static fr.pandacube.lib.chat.ChatStatic.text;
|
import static fr.pandacube.lib.chat.ChatStatic.text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various tools to supervise the JVM RAM and the CPU usage of the main server thread.
|
||||||
|
*/
|
||||||
public class PerformanceAnalysisManager implements Listener {
|
public class PerformanceAnalysisManager implements Listener {
|
||||||
|
|
||||||
private static PerformanceAnalysisManager instance;
|
private static PerformanceAnalysisManager instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the instance of {@link PerformanceAnalysisManager}.
|
||||||
|
* @return the instance of {@link PerformanceAnalysisManager}.
|
||||||
|
*/
|
||||||
public static synchronized PerformanceAnalysisManager getInstance() {
|
public static synchronized PerformanceAnalysisManager getInstance() {
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
instance = new PerformanceAnalysisManager();
|
instance = new PerformanceAnalysisManager();
|
||||||
@@ -77,14 +82,22 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
private final LinkedList<Long> interTPSDurations = new LinkedList<>();
|
private final LinkedList<Long> interTPSDurations = new LinkedList<>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The boss bar that shows in real time the CPU performance of the main server thread.
|
||||||
|
*/
|
||||||
public final AutoUpdatedBossBar tpsBar;
|
public final AutoUpdatedBossBar tpsBar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The boss bar that shows in real time the JVM RAM usage.
|
||||||
|
*/
|
||||||
public final AutoUpdatedBossBar memoryBar;
|
public final AutoUpdatedBossBar memoryBar;
|
||||||
private final List<Player> barPlayers = new ArrayList<>();
|
private final List<Player> barPlayers = new ArrayList<>();
|
||||||
private final List<BossBar> relatedBossBars = new ArrayList<>();
|
private final List<BossBar> relatedBossBars = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The gradient of color covering the common range of TPS values.
|
||||||
|
*/
|
||||||
public final ChatColorGradient tps1sGradient = new ChatColorGradient()
|
public final ChatColorGradient tps1sGradient = new ChatColorGradient()
|
||||||
.add(0, NamedTextColor.BLACK)
|
.add(0, NamedTextColor.BLACK)
|
||||||
.add(1, NamedTextColor.DARK_RED)
|
.add(1, NamedTextColor.DARK_RED)
|
||||||
@@ -95,23 +108,7 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
.add(21, PandaTheme.CHAT_GREEN_1_NORMAL)
|
.add(21, PandaTheme.CHAT_GREEN_1_NORMAL)
|
||||||
.add(26, NamedTextColor.BLUE);
|
.add(26, NamedTextColor.BLUE);
|
||||||
|
|
||||||
|
private final ChatColorGradient memoryUsageGradient = new ChatColorGradient()
|
||||||
public final ChatColorGradient tps10sGradient = new ChatColorGradient()
|
|
||||||
.add(0, NamedTextColor.DARK_RED)
|
|
||||||
.add(5, NamedTextColor.RED)
|
|
||||||
.add(10, NamedTextColor.GOLD)
|
|
||||||
.add(14, NamedTextColor.YELLOW)
|
|
||||||
.add(19, PandaTheme.CHAT_GREEN_1_NORMAL);
|
|
||||||
|
|
||||||
|
|
||||||
public final ChatColorGradient tps1mGradient = new ChatColorGradient()
|
|
||||||
.add(0, NamedTextColor.DARK_RED)
|
|
||||||
.add(8, NamedTextColor.RED)
|
|
||||||
.add(14, NamedTextColor.GOLD)
|
|
||||||
.add(17, NamedTextColor.YELLOW)
|
|
||||||
.add(19, PandaTheme.CHAT_GREEN_1_NORMAL);
|
|
||||||
|
|
||||||
public final ChatColorGradient memoryUsageGradient = new ChatColorGradient()
|
|
||||||
.add(.60f, PandaTheme.CHAT_GREEN_1_NORMAL)
|
.add(.60f, PandaTheme.CHAT_GREEN_1_NORMAL)
|
||||||
.add(.70f, NamedTextColor.YELLOW)
|
.add(.70f, NamedTextColor.YELLOW)
|
||||||
.add(.80f, NamedTextColor.GOLD)
|
.add(.80f, NamedTextColor.GOLD)
|
||||||
@@ -133,10 +130,19 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided players is seeing the performance boss bars.
|
||||||
|
* @param p the player to verify.
|
||||||
|
* @return true if the provided players is seeing the performance boss bars, false otherwise.
|
||||||
|
*/
|
||||||
public boolean barsContainsPlayer(Player p) {
|
public boolean barsContainsPlayer(Player p) {
|
||||||
return barPlayers.contains(p);
|
return barPlayers.contains(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the performance boss bars to the provided player.
|
||||||
|
* @param p the player.
|
||||||
|
*/
|
||||||
public synchronized void addPlayerToBars(Player p) {
|
public synchronized void addPlayerToBars(Player p) {
|
||||||
barPlayers.add(p);
|
barPlayers.add(p);
|
||||||
p.showBossBar(tpsBar.bar);
|
p.showBossBar(tpsBar.bar);
|
||||||
@@ -144,7 +150,11 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
for (BossBar bar : relatedBossBars)
|
for (BossBar bar : relatedBossBars)
|
||||||
p.showBossBar(bar);
|
p.showBossBar(bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the performance boss bars from the provided player.
|
||||||
|
* @param p the player.
|
||||||
|
*/
|
||||||
public synchronized void removePlayerToBars(Player p) {
|
public synchronized void removePlayerToBars(Player p) {
|
||||||
p.hideBossBar(tpsBar.bar);
|
p.hideBossBar(tpsBar.bar);
|
||||||
p.hideBossBar(memoryBar.bar);
|
p.hideBossBar(memoryBar.bar);
|
||||||
@@ -152,7 +162,11 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
p.hideBossBar(bar);
|
p.hideBossBar(bar);
|
||||||
barPlayers.remove(p);
|
barPlayers.remove(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an additional boss bar to the players currently seeing the performance ones.
|
||||||
|
* @param bar the new bar to show.
|
||||||
|
*/
|
||||||
public synchronized void addBossBar(BossBar bar) {
|
public synchronized void addBossBar(BossBar bar) {
|
||||||
if (relatedBossBars.contains(bar))
|
if (relatedBossBars.contains(bar))
|
||||||
return;
|
return;
|
||||||
@@ -160,7 +174,11 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
for (Player p : barPlayers)
|
for (Player p : barPlayers)
|
||||||
p.showBossBar(bar);
|
p.showBossBar(bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides an additional boss bar from the players currently seeing the performance ones.
|
||||||
|
* @param bar the additional bar to hide.
|
||||||
|
*/
|
||||||
public synchronized void removeBossBar(BossBar bar) {
|
public synchronized void removeBossBar(BossBar bar) {
|
||||||
if (!relatedBossBars.contains(bar))
|
if (!relatedBossBars.contains(bar))
|
||||||
return;
|
return;
|
||||||
@@ -168,8 +186,11 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
for (Player p : barPlayers)
|
for (Player p : barPlayers)
|
||||||
p.hideBossBar(bar);
|
p.hideBossBar(bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void cancelInternalBossBar() {
|
/**
|
||||||
|
* De-initialize the performance analyzer.
|
||||||
|
*/
|
||||||
|
public synchronized void deinit() {
|
||||||
tpsBar.cancel();
|
tpsBar.cancel();
|
||||||
memoryBar.cancel();
|
memoryBar.cancel();
|
||||||
}
|
}
|
||||||
@@ -179,7 +200,7 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public synchronized void onTickStart(ServerTickStartEvent event) {
|
synchronized void onTickStart(ServerTickStartEvent event) {
|
||||||
tickStartNanoTime = System.nanoTime();
|
tickStartNanoTime = System.nanoTime();
|
||||||
tickStartCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0;
|
tickStartCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0;
|
||||||
|
|
||||||
@@ -187,7 +208,7 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public synchronized void onTickEnd(ServerTickEndEvent event) {
|
synchronized void onTickEnd(ServerTickEndEvent event) {
|
||||||
tickEndNanoTime = System.nanoTime();
|
tickEndNanoTime = System.nanoTime();
|
||||||
long tickEndCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0;
|
long tickEndCPUTime = threadMXBean.isThreadCpuTimeSupported() ? threadMXBean.getCurrentThreadCpuTime() : 0;
|
||||||
|
|
||||||
@@ -214,7 +235,7 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer> playerManager = (AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer>) AbstractPlayerManager.getInstance();
|
AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer> playerManager = (AbstractPlayerManager<PaperOnlinePlayer, PaperOffPlayer>) AbstractPlayerManager.getInstance();
|
||||||
@@ -234,7 +255,7 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
removePlayerToBars(event.getPlayer());
|
removePlayerToBars(event.getPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,15 +320,17 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
|
|
||||||
// keep the legacy text when generating the bar to save space when converting to component
|
// keep the legacy text when generating the bar to save space when converting to component
|
||||||
StringBuilder s = new StringBuilder();
|
StringBuilder s = new StringBuilder();
|
||||||
ChatColor prevC = ChatColor.RESET;
|
TextColor prevC = null;
|
||||||
for (int i = 58; i >= 0; i--) {
|
for (int i = 58; i >= 0; i--) {
|
||||||
int t = tpsHistory[i];
|
int t = tpsHistory[i];
|
||||||
ChatColor newC = ChatColorUtil.toBungee(tps1sGradient.pickColorAt(t));
|
TextColor newC = tps1sGradient.pickColorAt(t);
|
||||||
if (!newC.equals(prevC)) {
|
if (!newC.equals(prevC)) {
|
||||||
s.append(newC);
|
s.append(text("|").color(newC).getLegacyText());
|
||||||
prevC = newC;
|
prevC = newC;
|
||||||
}
|
}
|
||||||
s.append("|");
|
else {
|
||||||
|
s.append("|");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -374,28 +397,34 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Chat alteredTPSTitle = null;
|
private Chat alteredTPSTitle = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary change the title of the TPS boss bar.
|
||||||
|
* @param title the title override. null to restore to the normal TPS title.
|
||||||
|
*/
|
||||||
public synchronized void setAlteredTPSTitle(Chat title) {
|
public synchronized void setAlteredTPSTitle(Chat title) {
|
||||||
alteredTPSTitle = title;
|
alteredTPSTitle = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// special case where the getTPS method always returns a whole number when retrieving the TPS for 1 sec
|
/**
|
||||||
|
* Gets the number of tick in the last second.
|
||||||
|
* @return the number of tick in the last second.
|
||||||
|
*/
|
||||||
public int getTPS1s() {
|
public int getTPS1s() {
|
||||||
return (int) getTPS(1_000);
|
return (int) getTPS(1_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param nbTicks number of ticks when the avg value is computed from history
|
* @param nbTicks number of ticks when the avg value is computed from history
|
||||||
* @return the avg number of TPS in the interval
|
* @return the avg number of TPS in the interval
|
||||||
*/
|
*/
|
||||||
public synchronized float getAvgNano(List<Long> data, int nbTicks) {
|
private synchronized float getAvgNano(List<Long> data, int nbTicks) {
|
||||||
if (data.isEmpty())
|
if (data.isEmpty())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -409,7 +438,7 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Gets the average number of tick per second in the n last milliseconds.
|
||||||
* @param nbMillis number of milliseconds when the avg TPS is computed from history
|
* @param nbMillis number of milliseconds when the avg TPS is computed from history
|
||||||
* @return the avg number of TPS in the interval
|
* @return the avg number of TPS in the interval
|
||||||
*/
|
*/
|
||||||
@@ -428,8 +457,12 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
|
|
||||||
return count * (1000 / (float) nbMillis);
|
return count * (1000 / (float) nbMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the history of TPS performance.
|
||||||
|
* @return an array of TPS values from the last minute. The value at 0 is in the last second (current second on the clock - 1), the value at index 1 is now - 2, ...
|
||||||
|
*/
|
||||||
public synchronized int[] getTPSHistory() {
|
public synchronized int[] getTPSHistory() {
|
||||||
int[] history = new int[60];
|
int[] history = new int[60];
|
||||||
|
|
||||||
@@ -447,15 +480,22 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current server's target tick rate.
|
||||||
|
* Usually 20 but the server can be configured to tick at a different rate.
|
||||||
|
* @return the current server's target tick rate.
|
||||||
|
*/
|
||||||
public static int getTargetTickRate() {
|
public static int getTargetTickRate() {
|
||||||
return Math.round(Bukkit.getServerTickManager().getTickRate());
|
return Math.round(Bukkit.getServerTickManager().getTickRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the garbage collector on the server.
|
||||||
|
* Depending on the server load and the used memory, this can freeze the server for a second.
|
||||||
|
* @param sender the command sender that triggers the garbase collector. Can be null (the report will be sent to the
|
||||||
|
* console)
|
||||||
|
*/
|
||||||
public static void gc(CommandSender sender) {
|
public static void gc(CommandSender sender) {
|
||||||
long t1 = System.currentTimeMillis();
|
long t1 = System.currentTimeMillis();
|
||||||
long alloc1 = Runtime.getRuntime().totalMemory();
|
long alloc1 = Runtime.getRuntime().totalMemory();
|
||||||
@@ -477,7 +517,7 @@ public class PerformanceAnalysisManager implements Listener {
|
|||||||
Log.info(finalMessage.getLegacyText());
|
Log.info(finalMessage.getLegacyText());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String displayRound10(double val) {
|
private static String displayRound10(double val) {
|
||||||
long v = (long) Math.ceil(val * 10);
|
long v = (long) Math.ceil(val * 10);
|
||||||
return "" + (v / 10f);
|
return "" + (v / 10f);
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
package fr.pandacube.lib.paper.players;
|
package fr.pandacube.lib.paper.players;
|
||||||
|
|
||||||
import fr.pandacube.lib.paper.reflect.util.PrimaryWorlds;
|
import fr.pandacube.lib.paper.world.PrimaryWorlds;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer;
|
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.NbtIo;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.NbtIo;
|
||||||
import fr.pandacube.lib.paper.util.PlayerDataWrapper;
|
import fr.pandacube.lib.paper.players.PlayerDataWrapper.PlayerDataLoadException;
|
||||||
import fr.pandacube.lib.paper.util.PlayerDataWrapper.PlayerDataLoadException;
|
|
||||||
import fr.pandacube.lib.paper.world.WorldUtil;
|
import fr.pandacube.lib.paper.world.WorldUtil;
|
||||||
import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
|
import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
|
||||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||||
@@ -116,26 +115,31 @@ public interface PaperOffPlayer extends AbstractOffPlayer {
|
|||||||
* Player config
|
* Player config
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||||
@Override
|
@Override
|
||||||
default String getConfig(String key) throws Exception {
|
default String getConfig(String key) throws Exception {
|
||||||
return PaperPlayerConfigStorage.get(getUniqueId(), key);
|
return PaperPlayerConfigStorage.get(getUniqueId(), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||||
@Override
|
@Override
|
||||||
default String getConfig(String key, String deflt) throws Exception {
|
default String getConfig(String key, String deflt) throws Exception {
|
||||||
return PaperPlayerConfigStorage.get(getUniqueId(), key, deflt);
|
return PaperPlayerConfigStorage.get(getUniqueId(), key, deflt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||||
@Override
|
@Override
|
||||||
default void setConfig(String key, String value) throws Exception {
|
default void setConfig(String key, String value) throws Exception {
|
||||||
PaperPlayerConfigStorage.set(getUniqueId(), key, value);
|
PaperPlayerConfigStorage.set(getUniqueId(), key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||||
@Override
|
@Override
|
||||||
default void updateConfig(String key, String deflt, UnaryOperator<String> updater) throws Exception {
|
default void updateConfig(String key, String deflt, UnaryOperator<String> updater) throws Exception {
|
||||||
PaperPlayerConfigStorage.update(getUniqueId(), key, deflt, updater);
|
PaperPlayerConfigStorage.update(getUniqueId(), key, deflt, updater);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows") // may be thrown by concrete implementation
|
||||||
@Override
|
@Override
|
||||||
default void unsetConfig(String key) throws Exception {
|
default void unsetConfig(String key) throws Exception {
|
||||||
PaperPlayerConfigStorage.unset(getUniqueId(), key);
|
PaperPlayerConfigStorage.unset(getUniqueId(), key);
|
||||||
|
@@ -3,7 +3,7 @@ package fr.pandacube.lib.paper.players;
|
|||||||
import com.destroystokyo.paper.ClientOption;
|
import com.destroystokyo.paper.ClientOption;
|
||||||
import com.destroystokyo.paper.ClientOption.ChatVisibility;
|
import com.destroystokyo.paper.ClientOption.ChatVisibility;
|
||||||
import com.destroystokyo.paper.SkinParts;
|
import com.destroystokyo.paper.SkinParts;
|
||||||
import fr.pandacube.lib.paper.players.PlayerNonPersistentConfig.Expiration;
|
import fr.pandacube.lib.paper.players.PlayerNonPersistentConfig.ExpirationPolicy;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftPlayer;
|
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftPlayer;
|
||||||
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
|
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
|
||||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||||
@@ -170,7 +170,7 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer
|
|||||||
@Override
|
@Override
|
||||||
public boolean isChatFullyVisible() {
|
public boolean isChatFullyVisible() {
|
||||||
ChatVisibility v = getChatVisibility();
|
ChatVisibility v = getChatVisibility();
|
||||||
return v == ChatVisibility.FULL || v == ChatVisibility.UNKNOWN;
|
return v == ChatVisibility.FULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -283,7 +283,7 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer
|
|||||||
* @param relZ the relative z coordinate.
|
* @param relZ the relative z coordinate.
|
||||||
*/
|
*/
|
||||||
default void teleportRelatively(float relX, float relY, float relZ) {
|
default void teleportRelatively(float relX, float relY, float relZ) {
|
||||||
getBukkitPlayer().teleport(getBukkitPlayer().getLocation().add(relX, relY, relZ), Relative.X, Relative.Y, Relative.Z, Relative.YAW, Relative.PITCH);
|
getBukkitPlayer().teleport(getBukkitPlayer().getLocation().add(relX, relY, relZ), Relative.VELOCITY_X, Relative.VELOCITY_Y, Relative.VELOCITY_Z, Relative.VELOCITY_ROTATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -291,7 +291,7 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer
|
|||||||
* @param destination the destination.
|
* @param destination the destination.
|
||||||
*/
|
*/
|
||||||
default void teleportRelatively(Location destination) {
|
default void teleportRelatively(Location destination) {
|
||||||
getBukkitPlayer().teleport(destination, Relative.X, Relative.Y, Relative.Z, Relative.YAW, Relative.PITCH);
|
getBukkitPlayer().teleport(destination, Relative.VELOCITY_X, Relative.VELOCITY_Y, Relative.VELOCITY_Z, Relative.VELOCITY_ROTATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -304,18 +304,39 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer
|
|||||||
* Player config
|
* Player config
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the non-persistent value of the provided configuration key of this player.
|
||||||
|
* @param key the configuration key.
|
||||||
|
* @return the value of the configuration, or null if the configuration is not set.
|
||||||
|
*/
|
||||||
default String getNonPersistentConfig(String key) {
|
default String getNonPersistentConfig(String key) {
|
||||||
return PlayerNonPersistentConfig.getData(getUniqueId(), key);
|
return PlayerNonPersistentConfig.getData(getUniqueId(), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the non-persistent value of the provided configuration key of this player.
|
||||||
|
* @param key the configuration key.
|
||||||
|
* @param deflt the default value if the configuration is not set.
|
||||||
|
* @return the value of the configuration, or {@code deflt} if the configuration is not set.
|
||||||
|
*/
|
||||||
default String getNonPersistentConfig(String key, String deflt) {
|
default String getNonPersistentConfig(String key, String deflt) {
|
||||||
return PlayerNonPersistentConfig.getData(getUniqueId(), key);
|
return PlayerNonPersistentConfig.getData(getUniqueId(), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
default void setNonPersistentConfig(String key, String value, Expiration expiration) {
|
/**
|
||||||
PlayerNonPersistentConfig.setData(getUniqueId(), key, value, expiration);
|
* Sets the non-persistent value of the provided configuration key for this player.
|
||||||
|
* @param key the configuration key to set.
|
||||||
|
* @param value the new value.
|
||||||
|
* @param expirationPolicy the expiration policy.
|
||||||
|
*/
|
||||||
|
default void setNonPersistentConfig(String key, String value, ExpirationPolicy expirationPolicy) {
|
||||||
|
PlayerNonPersistentConfig.setData(getUniqueId(), key, value, expirationPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsets the non-persistent value of the provided configuration key for this player.
|
||||||
|
* @param key the configuration key to update.
|
||||||
|
*/
|
||||||
default void unsetNonPersistentConfig(String key) {
|
default void unsetNonPersistentConfig(String key) {
|
||||||
PlayerNonPersistentConfig.unsetData(getUniqueId(), key);
|
PlayerNonPersistentConfig.unsetData(getUniqueId(), key);
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,10 @@ import java.util.UUID;
|
|||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides rudimentary player data storage using a file in the plugin configuration.
|
||||||
|
* The file is loaded on the first access, and is auto-saved if needed every 30 seconds.
|
||||||
|
*/
|
||||||
public class PaperPlayerConfigStorage {
|
public class PaperPlayerConfigStorage {
|
||||||
|
|
||||||
static final File storageFile = new File(PandaLibPaper.getPlugin().getDataFolder(), "playerdata.yml");
|
static final File storageFile = new File(PandaLibPaper.getPlugin().getDataFolder(), "playerdata.yml");
|
||||||
@@ -77,6 +81,8 @@ public class PaperPlayerConfigStorage {
|
|||||||
|
|
||||||
|
|
||||||
private static synchronized void save() {
|
private static synchronized void save() {
|
||||||
|
if (!changed)
|
||||||
|
return;
|
||||||
YamlConfiguration config = new YamlConfiguration();
|
YamlConfiguration config = new YamlConfiguration();
|
||||||
for (UUID pId : playerSortedData.keySet()) {
|
for (UUID pId : playerSortedData.keySet()) {
|
||||||
String pIdStr = pId.toString();
|
String pIdStr = pId.toString();
|
||||||
@@ -109,11 +115,17 @@ public class PaperPlayerConfigStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static synchronized void set(UUID player, String key, String newValue) {
|
/**
|
||||||
|
* Sets the value of the provided configuration key for the player.
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key to set.
|
||||||
|
* @param value the new value.
|
||||||
|
*/
|
||||||
|
public static synchronized void set(UUID player, String key, String value) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
ConfigKey cKey = new ConfigKey(player, key);
|
ConfigKey cKey = new ConfigKey(player, key);
|
||||||
ConfigEntry e = data.get(cKey);
|
ConfigEntry e = data.get(cKey);
|
||||||
if (e != null && newValue == null) { // delete
|
if (e != null && value == null) { // delete
|
||||||
data.remove(cKey);
|
data.remove(cKey);
|
||||||
if (playerSortedData.containsKey(player))
|
if (playerSortedData.containsKey(player))
|
||||||
playerSortedData.get(player).remove(e);
|
playerSortedData.get(player).remove(e);
|
||||||
@@ -121,50 +133,91 @@ public class PaperPlayerConfigStorage {
|
|||||||
keySortedData.get(key).remove(e);
|
keySortedData.get(key).remove(e);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
else if (e == null && newValue != null) { // create
|
else if (e == null && value != null) { // create
|
||||||
create(player, key, newValue);
|
create(player, key, value);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
else if (e != null && !newValue.equals(e.value)) { // update
|
else if (e != null && !value.equals(e.value)) { // update
|
||||||
e.value = newValue;
|
e.value = value;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the provided configuration key of the player.
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key.
|
||||||
|
* @return the value of the configuration, or null if the configuration is not set.
|
||||||
|
*/
|
||||||
public static synchronized String get(UUID player, String key) {
|
public static synchronized String get(UUID player, String key) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
ConfigEntry e = data.get(new ConfigKey(player, key));
|
ConfigEntry e = data.get(new ConfigKey(player, key));
|
||||||
return e != null ? e.value : null;
|
return e != null ? e.value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String get(UUID p, String k, String deflt) {
|
/**
|
||||||
String value = get(p, k);
|
* Gets the value of the provided configuration key of the player.
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key.
|
||||||
|
* @param deflt the default value if the configuration is not set.
|
||||||
|
* @return the value of the configuration, or {@code deflt} if the configuration is not set.
|
||||||
|
*/
|
||||||
|
public static String get(UUID player, String key, String deflt) {
|
||||||
|
String value = get(player, key);
|
||||||
return value == null ? deflt : value;
|
return value == null ? deflt : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void update(UUID p, String k, String deflt, UnaryOperator<String> updater) {
|
/**
|
||||||
String oldValue = get(p, k, deflt);
|
* Updates the value of the provided configuration key for the player, using the provided updater.
|
||||||
set(p, k, updater.apply(oldValue));
|
* @param player the player.
|
||||||
|
* @param key the configuration key to update.
|
||||||
|
* @param deflt the default value to use if the configuration is not already set.
|
||||||
|
* @param updater the unary operator to use to update th value. The old value is used as the parameter of the updater,
|
||||||
|
* and it returns the new value of the configuration.
|
||||||
|
*/
|
||||||
|
public static synchronized void update(UUID player, String key, String deflt, UnaryOperator<String> updater) {
|
||||||
|
String oldValue = get(player, key, deflt);
|
||||||
|
set(player, key, updater.apply(oldValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unset(UUID p, String k) {
|
/**
|
||||||
set(p, k, null);
|
* Unsets the value of the provided configuration key for the player.
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key to update.
|
||||||
|
*/
|
||||||
|
public static void unset(UUID player, String key) {
|
||||||
|
set(player, key, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
public static LinkedHashSet<ConfigEntry> getAllFromPlayer(UUID p) {
|
* Gets all the config key-value pairs of the provided player.
|
||||||
|
* @param player the player.
|
||||||
|
* @return all the config key-value pairs of the provided player.
|
||||||
|
*/
|
||||||
|
public static LinkedHashSet<ConfigEntry> getAllFromPlayer(UUID player) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
return new LinkedHashSet<>(playerSortedData.getOrDefault(p, new LinkedHashSet<>()));
|
return new LinkedHashSet<>(playerSortedData.getOrDefault(player, new LinkedHashSet<>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the config key-value pairs of all players that have the provided key.
|
||||||
|
* @param key the key.
|
||||||
|
* @return all the config key-value pairs of all players that have the provided key.
|
||||||
|
*/
|
||||||
public static LinkedHashSet<ConfigEntry> getAllWithKeys(String key) {
|
public static LinkedHashSet<ConfigEntry> getAllWithKeys(String key) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
return new LinkedHashSet<>(keySortedData.getOrDefault(key, new LinkedHashSet<>()));
|
return new LinkedHashSet<>(keySortedData.getOrDefault(key, new LinkedHashSet<>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LinkedHashSet<ConfigEntry> getAllWithKeyValue(String k, String v) {
|
/**
|
||||||
|
* Gets all the config key-value pairs of all players that have the provided key AND value.
|
||||||
|
* @param key the key.
|
||||||
|
* @param v the value.
|
||||||
|
* @return all the config key-value pairs of all players that have the provided key AND value.
|
||||||
|
*/
|
||||||
|
public static LinkedHashSet<ConfigEntry> getAllWithKeyValue(String key, String v) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
return getAllWithKeys(k).stream()
|
return getAllWithKeys(key).stream()
|
||||||
.filter(c -> c.value.equals(v))
|
.filter(c -> c.value.equals(v))
|
||||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
}
|
}
|
||||||
@@ -173,25 +226,46 @@ public class PaperPlayerConfigStorage {
|
|||||||
|
|
||||||
private record ConfigKey(UUID playerId, String key) { }
|
private record ConfigKey(UUID playerId, String key) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class holding the playerId-key-value triplet.
|
||||||
|
*/
|
||||||
public static class ConfigEntry {
|
public static class ConfigEntry {
|
||||||
private final UUID playerId;
|
private final UUID playerId;
|
||||||
private final String key;
|
private final String key;
|
||||||
private String value;
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ConfigEntry}.
|
||||||
|
* @param playerId the player id.
|
||||||
|
* @param key the key.
|
||||||
|
* @param value the value.
|
||||||
|
*/
|
||||||
private ConfigEntry(UUID playerId, String key, String value) {
|
private ConfigEntry(UUID playerId, String key, String value) {
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the player id.
|
||||||
|
* @return the player id.
|
||||||
|
*/
|
||||||
public UUID getPlayerId() {
|
public UUID getPlayerId() {
|
||||||
return playerId;
|
return playerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the config key.
|
||||||
|
* @return the config key.
|
||||||
|
*/
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the config value.
|
||||||
|
* @return the config value.
|
||||||
|
*/
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -208,4 +282,7 @@ public class PaperPlayerConfigStorage {
|
|||||||
&& Objects.equals(key, o.key);
|
&& Objects.equals(key, o.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private PaperPlayerConfigStorage() {}
|
||||||
}
|
}
|
||||||
|
@@ -1,39 +1,33 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
package fr.pandacube.lib.paper.players;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import fr.pandacube.lib.paper.inventory.DummyPlayerInventory;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftItemStack;
|
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftItemStack;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.ListTag;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.ListTag;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.Tag;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.Tag;
|
||||||
|
import fr.pandacube.lib.paper.util.ExperienceUtil;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.entity.HumanEntity;
|
|
||||||
import org.bukkit.event.inventory.InventoryType;
|
import org.bukkit.event.inventory.InventoryType;
|
||||||
import org.bukkit.inventory.EquipmentSlot;
|
|
||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.PlayerInventory;
|
import org.bukkit.inventory.PlayerInventory;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.IntUnaryOperator;
|
import java.util.function.IntUnaryOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper to easily manipulate vanilla player data.
|
* A wrapper to easily manipulate the player data file.
|
||||||
*
|
*
|
||||||
* @param data The data as they are stored in the player file.
|
* @param data The NBT data structure as it is stored in the player file.
|
||||||
*/
|
*/
|
||||||
public record PlayerDataWrapper(CompoundTag data) {
|
public record PlayerDataWrapper(CompoundTag data) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new wrapper for the provided player data.
|
* Creates a new wrapper for the provided player data.
|
||||||
* @param data the data to wrap.
|
* @param data the NBT data to wrap.
|
||||||
*/
|
*/
|
||||||
public PlayerDataWrapper(CompoundTag data) {
|
public PlayerDataWrapper(CompoundTag data) {
|
||||||
this.data = data == null ? new CompoundTag() : data;
|
this.data = data == null ? new CompoundTag() : data;
|
||||||
@@ -41,8 +35,9 @@ public record PlayerDataWrapper(CompoundTag data) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a snapshot of the inventory of this player.
|
* Gets a snapshot of the inventory of this player.
|
||||||
* If modified, call the {@link #setInventory(PlayerInventory)} to update the data.
|
* If the inventory is modified, the {@link #setInventory(PlayerInventory)} method should be called to update the
|
||||||
* @return the player inventory
|
* data in this wrapper.
|
||||||
|
* @return the player inventory.
|
||||||
*/
|
*/
|
||||||
public PlayerInventory getInventory() {
|
public PlayerInventory getInventory() {
|
||||||
return new DummyPlayerInventory(
|
return new DummyPlayerInventory(
|
||||||
@@ -67,6 +62,11 @@ public record PlayerDataWrapper(CompoundTag data) {
|
|||||||
throw new IllegalArgumentException("Unrecognized NBT player inventory slot " + nbtSlot);
|
throw new IllegalArgumentException("Unrecognized NBT player inventory slot " + nbtSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the player inventory to the content of the provided one.
|
||||||
|
* The internal data of this wrapper will be updated.
|
||||||
|
* @param inv the inventory to store in this player data in place of the old one.
|
||||||
|
*/
|
||||||
public void setInventory(PlayerInventory inv) {
|
public void setInventory(PlayerInventory inv) {
|
||||||
setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot);
|
setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot);
|
||||||
setHeldItemSlot(inv.getHeldItemSlot());
|
setHeldItemSlot(inv.getHeldItemSlot());
|
||||||
@@ -86,10 +86,21 @@ public record PlayerDataWrapper(CompoundTag data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a snapshot of the enderchest of this player.
|
||||||
|
* If the enderchest is modified, the {@link #setEnderChest(Inventory)} method should be called to update the
|
||||||
|
* data in this wrapper.
|
||||||
|
* @return the player enderchest.
|
||||||
|
*/
|
||||||
public Inventory getEnderChest() {
|
public Inventory getEnderChest() {
|
||||||
return getBukkitInventory("EnderItems", InventoryType.ENDER_CHEST, IntUnaryOperator.identity());
|
return getBukkitInventory("EnderItems", InventoryType.ENDER_CHEST, IntUnaryOperator.identity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the player enderchest to the content of the provided one.
|
||||||
|
* The internal data of this wrapper will be updated.
|
||||||
|
* @param inv the enderchest content to store in this player data in place of the old enderchest.
|
||||||
|
*/
|
||||||
public void setEnderChest(Inventory inv) {
|
public void setEnderChest(Inventory inv) {
|
||||||
setBukkitInventory("EnderItems", inv, IntUnaryOperator.identity());
|
setBukkitInventory("EnderItems", inv, IntUnaryOperator.identity());
|
||||||
}
|
}
|
||||||
@@ -171,6 +182,10 @@ public record PlayerDataWrapper(CompoundTag data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the score of the player, as stored in the data with the key {@code Score}.
|
||||||
|
* @return the value of Score.
|
||||||
|
*/
|
||||||
public int getScore() {
|
public int getScore() {
|
||||||
if (!data.contains("Score"))
|
if (!data.contains("Score"))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -178,17 +193,29 @@ public record PlayerDataWrapper(CompoundTag data) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the score of the player, as stored in the data with the key {@code Score}.
|
||||||
|
* @param score the value of Score to set.
|
||||||
|
*/
|
||||||
public void setScore(int score) {
|
public void setScore(int score) {
|
||||||
data.putInt("Score", score);
|
data.putInt("Score", score);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the total experience of the player, as stored in the data with the key {@code XpTotal}.
|
||||||
|
* @return the value of XpTotal.
|
||||||
|
*/
|
||||||
public int getTotalExperience() {
|
public int getTotalExperience() {
|
||||||
if (!data.contains("XpTotal"))
|
if (!data.contains("XpTotal"))
|
||||||
return 0;
|
return 0;
|
||||||
return data.getInt("XpTotal");
|
return data.getInt("XpTotal");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the total experience of the player, as stored in the data with the key {@code XpTotal}.
|
||||||
|
* @param xp the value of XpTotal to set.
|
||||||
|
*/
|
||||||
public void setTotalExperience(int xp) {
|
public void setTotalExperience(int xp) {
|
||||||
data.putInt("XpTotal", xp);
|
data.putInt("XpTotal", xp);
|
||||||
double levelAndExp = ExperienceUtil.getLevelFromExp(xp);
|
double levelAndExp = ExperienceUtil.getLevelFromExp(xp);
|
||||||
@@ -198,178 +225,11 @@ public record PlayerDataWrapper(CompoundTag data) {
|
|||||||
data.putFloat("XpP", (float) expProgress);
|
data.putFloat("XpP", (float) expProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
private static class DummyPlayerInventory extends InventoryWrapper implements PlayerInventory {
|
* Thrown to indicate that an error occurred while loading the data of the player from the file.
|
||||||
|
*/
|
||||||
private int heldItemSlot;
|
|
||||||
|
|
||||||
public DummyPlayerInventory(Inventory base, int heldItemSlot) {
|
|
||||||
super(base);
|
|
||||||
this.heldItemSlot = heldItemSlot;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable ItemStack @NotNull [] getStorageContents() {
|
|
||||||
return Arrays.copyOfRange(getContents(), 0, 36);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable ItemStack @NotNull [] getArmorContents() {
|
|
||||||
return Arrays.copyOfRange(getContents(), 36, 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setArmorContents(@Nullable ItemStack[] items) {
|
|
||||||
this.setSlots(items, 36, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable ItemStack @NotNull [] getExtraContents() {
|
|
||||||
return Arrays.copyOfRange(getContents(), 40, getSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setExtraContents(@Nullable ItemStack[] items) {
|
|
||||||
this.setSlots(items, 40, getSize() - 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSlots(ItemStack[] items, int baseSlot, int length) {
|
|
||||||
if (items == null) {
|
|
||||||
items = new ItemStack[length];
|
|
||||||
}
|
|
||||||
Preconditions.checkArgument(items.length <= length, "items.length must be < %s", length);
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
if (i >= items.length) {
|
|
||||||
this.setItem(baseSlot + i, null);
|
|
||||||
} else {
|
|
||||||
this.setItem(baseSlot + i, items[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemStack getHelmet() {
|
|
||||||
return getItem(39);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setHelmet(@Nullable ItemStack helmet) {
|
|
||||||
setItem(39, helmet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemStack getChestplate() {
|
|
||||||
return getItem(38);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setChestplate(@Nullable ItemStack chestplate) {
|
|
||||||
setItem(38, chestplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemStack getLeggings() {
|
|
||||||
return getItem(37);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setLeggings(@Nullable ItemStack leggings) {
|
|
||||||
setItem(37, leggings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemStack getBoots() {
|
|
||||||
return getItem(36);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBoots(@Nullable ItemStack boots) {
|
|
||||||
setItem(36, boots);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setItem(EquipmentSlot slot, ItemStack item) {
|
|
||||||
Preconditions.checkArgument(slot != null, "slot must not be null");
|
|
||||||
|
|
||||||
switch (slot) {
|
|
||||||
case HAND -> this.setItemInMainHand(item);
|
|
||||||
case OFF_HAND -> this.setItemInOffHand(item);
|
|
||||||
case FEET -> this.setBoots(item);
|
|
||||||
case LEGS -> this.setLeggings(item);
|
|
||||||
case CHEST -> this.setChestplate(item);
|
|
||||||
case HEAD -> this.setHelmet(item);
|
|
||||||
default -> throw new IllegalArgumentException("Not implemented. This is a bug");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ItemStack getItem(@NotNull EquipmentSlot slot) {
|
|
||||||
return switch (slot) {
|
|
||||||
case HAND -> this.getItemInMainHand();
|
|
||||||
case OFF_HAND -> this.getItemInOffHand();
|
|
||||||
case FEET -> Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(Material.AIR));
|
|
||||||
case LEGS -> Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(Material.AIR));
|
|
||||||
case CHEST -> Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(Material.AIR));
|
|
||||||
case HEAD -> Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(Material.AIR));
|
|
||||||
case BODY -> new ItemStack(Material.AIR); // for horses/wolves armor
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ItemStack getItemInMainHand() {
|
|
||||||
return Objects.requireNonNullElse(getItem(heldItemSlot), new ItemStack(Material.AIR));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setItemInMainHand(@Nullable ItemStack item) {
|
|
||||||
setItem(heldItemSlot, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ItemStack getItemInOffHand() {
|
|
||||||
return Objects.requireNonNullElse(getItem(40), new ItemStack(Material.AIR));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setItemInOffHand(@Nullable ItemStack item) {
|
|
||||||
setItem(40, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ItemStack getItemInHand() {
|
|
||||||
return getItemInMainHand();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setItemInHand(@Nullable ItemStack stack) {
|
|
||||||
setItemInMainHand(stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHeldItemSlot() {
|
|
||||||
return heldItemSlot;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setHeldItemSlot(int slot) {
|
|
||||||
if (slot < 0 || slot > 8)
|
|
||||||
throw new IllegalArgumentException("Slot is not between 0 and 8 inclusive");
|
|
||||||
heldItemSlot = slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable HumanEntity getHolder() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class PlayerDataLoadException extends RuntimeException {
|
public static class PlayerDataLoadException extends RuntimeException {
|
||||||
public PlayerDataLoadException(String playerName, UUID playerId, Throwable cause) {
|
/* package */ PlayerDataLoadException(String playerName, UUID playerId, Throwable cause) {
|
||||||
super("Unable to load data of player " + playerName + " (" + playerId + ")", cause);
|
super("Unable to load data of player " + playerName + " (" + playerId + ")", cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -12,6 +12,9 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the player related configuration that is not persisted to disk.
|
||||||
|
*/
|
||||||
public class PlayerNonPersistentConfig {
|
public class PlayerNonPersistentConfig {
|
||||||
private static final Map<UUID, Map<String, ConfigEntry>> data = new HashMap<>();
|
private static final Map<UUID, Map<String, ConfigEntry>> data = new HashMap<>();
|
||||||
|
|
||||||
@@ -22,34 +25,58 @@ public class PlayerNonPersistentConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void setData(UUID playerId, String key, String value, Expiration expiration) {
|
/**
|
||||||
data.computeIfAbsent(Objects.requireNonNull(playerId, "playerId"), pp -> new HashMap<>())
|
* Sets the value of the provided configuration key for the player.
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key to set.
|
||||||
|
* @param value the new value.
|
||||||
|
* @param expirationPolicy the expiration policy for this config. If the config key already exists for this player. the expiration will be overridden.
|
||||||
|
*/
|
||||||
|
public static void setData(UUID player, String key, String value, ExpirationPolicy expirationPolicy) {
|
||||||
|
data.computeIfAbsent(Objects.requireNonNull(player, "playerId"), pp -> new HashMap<>())
|
||||||
.put(Objects.requireNonNull(key, "key"),
|
.put(Objects.requireNonNull(key, "key"),
|
||||||
new ConfigEntry(Objects.requireNonNull(value, "value"),
|
new ConfigEntry(Objects.requireNonNull(value, "value"),
|
||||||
Objects.requireNonNull(expiration, "expiration")
|
Objects.requireNonNull(expirationPolicy, "expiration")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unsetData(UUID playerId, String key) {
|
/**
|
||||||
data.getOrDefault(Objects.requireNonNull(playerId, "playerId"), new HashMap<>())
|
* Unsets the value of the provided configuration key for the player.
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key to update.
|
||||||
|
*/
|
||||||
|
public static void unsetData(UUID player, String key) {
|
||||||
|
data.getOrDefault(Objects.requireNonNull(player, "playerId"), new HashMap<>())
|
||||||
.remove(Objects.requireNonNull(key, "key"));
|
.remove(Objects.requireNonNull(key, "key"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getData(UUID playerId, String key) {
|
/**
|
||||||
Map<String, ConfigEntry> playerData = data.getOrDefault(Objects.requireNonNull(playerId, "playerId"), new HashMap<>());
|
* Gets the value of the provided configuration key of the player.
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key.
|
||||||
|
* @return the value of the configuration, or {@code deflt} if the configuration is not set.
|
||||||
|
*/
|
||||||
|
public static String getData(UUID player, String key) {
|
||||||
|
Map<String, ConfigEntry> playerData = data.getOrDefault(Objects.requireNonNull(player, "playerId"), new HashMap<>());
|
||||||
ConfigEntry ce = playerData.get(Objects.requireNonNull(key, "key"));
|
ConfigEntry ce = playerData.get(Objects.requireNonNull(key, "key"));
|
||||||
if (ce == null)
|
if (ce == null)
|
||||||
return null;
|
return null;
|
||||||
if (!ce.expiration.valid(playerId, key)) {
|
if (!ce.expirationPolicy.valid(player, key)) {
|
||||||
playerData.remove(key);
|
playerData.remove(key);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return ce.value;
|
return ce.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isDataSet(UUID playerId, String key) {
|
/**
|
||||||
return getData(playerId, key) != null;
|
* Tells if the provided config key is set for the player.
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key.
|
||||||
|
* @return true if the value is set, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isDataSet(UUID player, String key) {
|
||||||
|
return getData(player, key) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -63,34 +90,76 @@ public class PlayerNonPersistentConfig {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private record ConfigEntry(String value, Expiration expiration) { }
|
private record ConfigEntry(String value, ExpirationPolicy expirationPolicy) { }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Super class for all expiration policies.
|
||||||
|
*/
|
||||||
|
public static abstract class ExpirationPolicy {
|
||||||
|
/**
|
||||||
|
* Creates an expiration policy.
|
||||||
|
*/
|
||||||
|
public ExpirationPolicy() {}
|
||||||
|
|
||||||
|
/**
|
||||||
public static abstract class Expiration {
|
* Tests if the associated configuration is still valid (not expired).
|
||||||
|
* @param player the player.
|
||||||
|
* @param key the configuration key.
|
||||||
|
* @return true if the associated configuration is still valid, false otherwise.
|
||||||
|
*/
|
||||||
abstract boolean valid(UUID player, String key);
|
abstract boolean valid(UUID player, String key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExpiresLogout extends Expiration {
|
/**
|
||||||
|
* Expiration policy for a config that expires when the player logs out.
|
||||||
|
*/
|
||||||
|
public static class ExpiresLogout extends ExpirationPolicy {
|
||||||
|
/**
|
||||||
|
* Creates a logout expiration policy.
|
||||||
|
*/
|
||||||
|
public ExpiresLogout() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected boolean valid(UUID player, String key) {
|
protected boolean valid(UUID player, String key) {
|
||||||
return Bukkit.getPlayer(player) != null; // should not be call if player reconnects because it is removed on player quit
|
return Bukkit.getPlayer(player) != null; // should not be call if player reconnects because it is removed on player quit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExpiresTick extends Expiration {
|
/**
|
||||||
|
* Expiration policy for a config that expires after a certain amount of game tick.
|
||||||
|
*/
|
||||||
|
public static class ExpiresTick extends ExpirationPolicy {
|
||||||
final long expirationTick;
|
final long expirationTick;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a delay expiration policy.
|
||||||
|
* @param expirationDelayTick the number of tick after which the config will expire. If 0, will expire immediately ; 1 to expire on the next tick.
|
||||||
|
*/
|
||||||
public ExpiresTick(long expirationDelayTick) {
|
public ExpiresTick(long expirationDelayTick) {
|
||||||
expirationTick = tick + expirationDelayTick;
|
expirationTick = tick + expirationDelayTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected boolean valid(UUID player, String key) {
|
protected boolean valid(UUID player, String key) {
|
||||||
return tick < expirationTick;
|
return tick < expirationTick;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExpiresServerStop extends Expiration {
|
/**
|
||||||
|
* Expiration policy for a config that expires when the server stops.
|
||||||
|
*/
|
||||||
|
public static class ExpiresServerStop extends ExpirationPolicy {
|
||||||
|
/**
|
||||||
|
* Creates a server stop expiration policy.
|
||||||
|
*/
|
||||||
|
public ExpiresServerStop() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected boolean valid(UUID player, String key) {
|
protected boolean valid(UUID player, String key) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -103,7 +172,7 @@ public class PlayerNonPersistentConfig {
|
|||||||
|
|
||||||
|
|
||||||
private static class ConfigListeners implements Listener {
|
private static class ConfigListeners implements Listener {
|
||||||
public ConfigListeners() {
|
private ConfigListeners() {
|
||||||
Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin());
|
Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +180,7 @@ public class PlayerNonPersistentConfig {
|
|||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
data.getOrDefault(event.getPlayer().getUniqueId(), new HashMap<>())
|
data.getOrDefault(event.getPlayer().getUniqueId(), new HashMap<>())
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.removeIf(e -> e.getValue().expiration instanceof ExpiresLogout);
|
.removeIf(e -> e.getValue().expirationPolicy instanceof ExpiresLogout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
@@ -119,4 +188,9 @@ public class PlayerNonPersistentConfig {
|
|||||||
tick++;
|
tick++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private PlayerNonPersistentConfig() {}
|
||||||
}
|
}
|
||||||
|
@@ -23,4 +23,6 @@ public class OBCReflect {
|
|||||||
return Reflect.ofClass(CRAFTBUKKIT_PACKAGE + "." + obcClass);
|
return Reflect.ofClass(CRAFTBUKKIT_PACKAGE + "." + obcClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OBCReflect() { }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -64,6 +64,7 @@ import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStack;
|
|||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Level;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Level;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.MapItemSavedData;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.MapItemSavedData;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.PlayerDataStorage;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.PlayerDataStorage;
|
||||||
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.RegionFileStorage;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.SavedData;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.SavedData;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Vec3;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Vec3;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.VoxelShape;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.VoxelShape;
|
||||||
@@ -72,13 +73,13 @@ import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.block.Block;
|
|||||||
import fr.pandacube.lib.paper.reflect.wrapper.netty.ByteBuf;
|
import fr.pandacube.lib.paper.reflect.wrapper.netty.ByteBuf;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.netty.Unpooled;
|
import fr.pandacube.lib.paper.reflect.wrapper.netty.Unpooled;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.PaperAdventure;
|
import fr.pandacube.lib.paper.reflect.wrapper.paper.PaperAdventure;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.QueuedChangesMapLong2Object;
|
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.BukkitCommandNode;
|
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.BukkitCommandNode;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.PaperBrigadier;
|
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.PaperBrigadier;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.PluginCommandNode;
|
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.PluginCommandNode;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.ShadowBrigNode;
|
import fr.pandacube.lib.paper.reflect.wrapper.paper.commands.ShadowBrigNode;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.configuration.FallbackValue_Int;
|
import fr.pandacube.lib.paper.reflect.wrapper.paper.configuration.FallbackValue_Int;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.paper.configuration.WorldConfiguration;
|
import fr.pandacube.lib.paper.reflect.wrapper.paper.configuration.WorldConfiguration;
|
||||||
|
import fr.pandacube.lib.paper.reflect.wrapper.spottedleaf.moonrise.ChunkSystemChunkStorage;
|
||||||
import fr.pandacube.lib.reflect.ReflectionWrapperBypass;
|
import fr.pandacube.lib.reflect.ReflectionWrapperBypass;
|
||||||
import fr.pandacube.lib.util.ThrowableAccumulator;
|
import fr.pandacube.lib.util.ThrowableAccumulator;
|
||||||
|
|
||||||
@@ -193,6 +194,7 @@ public class PandalibPaperReflect {
|
|||||||
thAcc.catchThrowable(() -> initWrapper(Level.class, Level.REFLECT.get()));
|
thAcc.catchThrowable(() -> initWrapper(Level.class, Level.REFLECT.get()));
|
||||||
thAcc.catchThrowable(() -> initWrapper(MapItemSavedData.class, MapItemSavedData.REFLECT.get()));
|
thAcc.catchThrowable(() -> initWrapper(MapItemSavedData.class, MapItemSavedData.REFLECT.get()));
|
||||||
thAcc.catchThrowable(() -> initWrapper(PlayerDataStorage.class, PlayerDataStorage.REFLECT.get()));
|
thAcc.catchThrowable(() -> initWrapper(PlayerDataStorage.class, PlayerDataStorage.REFLECT.get()));
|
||||||
|
thAcc.catchThrowable(() -> initWrapper(RegionFileStorage.class, RegionFileStorage.REFLECT.get()));
|
||||||
thAcc.catchThrowable(() -> initWrapper(SavedData.class, SavedData.REFLECT.get()));
|
thAcc.catchThrowable(() -> initWrapper(SavedData.class, SavedData.REFLECT.get()));
|
||||||
thAcc.catchThrowable(() -> initWrapper(Vec3.class, Vec3.REFLECT.get()));
|
thAcc.catchThrowable(() -> initWrapper(Vec3.class, Vec3.REFLECT.get()));
|
||||||
thAcc.catchThrowable(() -> initWrapper(VoxelShape.class, VoxelShape.REFLECT.get()));
|
thAcc.catchThrowable(() -> initWrapper(VoxelShape.class, VoxelShape.REFLECT.get()));
|
||||||
@@ -216,10 +218,14 @@ public class PandalibPaperReflect {
|
|||||||
thAcc.catchThrowable(() -> initWrapper(WorldConfiguration.Chunks.class, WorldConfiguration.Chunks.REFLECT.get()));
|
thAcc.catchThrowable(() -> initWrapper(WorldConfiguration.Chunks.class, WorldConfiguration.Chunks.REFLECT.get()));
|
||||||
// paper
|
// paper
|
||||||
thAcc.catchThrowable(() -> initWrapper(PaperAdventure.class, PaperAdventure.REFLECT.get()));
|
thAcc.catchThrowable(() -> initWrapper(PaperAdventure.class, PaperAdventure.REFLECT.get()));
|
||||||
thAcc.catchThrowable(() -> initWrapper(QueuedChangesMapLong2Object.class, QueuedChangesMapLong2Object.REFLECT.get()));
|
|
||||||
|
// spottedleaf
|
||||||
|
thAcc.catchThrowable(() -> initWrapper(ChunkSystemChunkStorage.class, ChunkSystemChunkStorage.REFLECT.get()));
|
||||||
|
|
||||||
|
|
||||||
thAcc.throwCaught();
|
thAcc.throwCaught();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PandalibPaperReflect() {}
|
||||||
}
|
}
|
||||||
|
@@ -12,11 +12,25 @@ import org.bukkit.event.Listener;
|
|||||||
import org.bukkit.event.block.BlockPlaceEvent;
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
import org.bukkit.util.BoundingBox;
|
import org.bukkit.util.BoundingBox;
|
||||||
|
|
||||||
// simplified version of https://github.com/Camotoy/BambooCollisionFix/tree/c7d7d5327791cbb416d106de0b9eb0bf2461acbd/src/main/java/net/camotoy/bamboocollisionfix
|
// simplified version of
|
||||||
// we remove the bamboo bounding box due to bedrock clients not having the same placement for bamboos
|
|
||||||
|
/**
|
||||||
|
* Disables the server-side bounding box of the bamboo stalk blocks.
|
||||||
|
* Bamboo stalks are the thin bamboo plants that are shifted differently, depending on the X and Z coordinate.
|
||||||
|
* This X/Z dependent shift is different between Java and Bedrock implementation.
|
||||||
|
* But since this block as a collision box and players cannot go through them, Bedrock players are often rolled back
|
||||||
|
* when they are walking around bamboo stalk due to the server being in Java Edition and thinking the player tries to
|
||||||
|
* move through the bamboo.
|
||||||
|
* To avoid this issue, we reduce to 0 the size of the bounding box on the server.
|
||||||
|
* <br>
|
||||||
|
* See <a href="https://github.com/Camotoy/BambooCollisionFix/tree/c7d7d5327791cbb416d106de0b9eb0bf2461acbd/src/main/java/net/camotoy/bamboocollisionfix">the original implementation</a>.
|
||||||
|
*/
|
||||||
public final class BedrockBambooCollisionFixer implements Listener {
|
public final class BedrockBambooCollisionFixer implements Listener {
|
||||||
private final BoundingBox originalBambooBoundingBox = new BoundingBox(6.5D / 16D, 0.0D, 6.5D / 16.0D, 9.5D / 16.0D, 1D, 9.5D / 16.0D);
|
private final BoundingBox originalBambooBoundingBox = new BoundingBox(6.5D / 16D, 0.0D, 6.5D / 16.0D, 9.5D / 16.0D, 1D, 9.5D / 16.0D);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link BedrockBambooCollisionFixer}. There is no need for multiple instances.
|
||||||
|
*/
|
||||||
public BedrockBambooCollisionFixer() {
|
public BedrockBambooCollisionFixer() {
|
||||||
// Make the bamboo block have zero collision.
|
// Make the bamboo block have zero collision.
|
||||||
try {
|
try {
|
||||||
@@ -34,7 +48,7 @@ public final class BedrockBambooCollisionFixer implements Listener {
|
|||||||
* our ability.
|
* our ability.
|
||||||
*/
|
*/
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onBlockPlace(BlockPlaceEvent event) {
|
void onBlockPlace(BlockPlaceEvent event) {
|
||||||
if (event.getBlockPlaced().getBlockData().getMaterial().equals(Material.BAMBOO)) {
|
if (event.getBlockPlaced().getBlockData().getMaterial().equals(Material.BAMBOO)) {
|
||||||
BoundingBox currentBambooBoundingBox = originalBambooBoundingBox.clone().shift(event.getBlockPlaced().getLocation());
|
BoundingBox currentBambooBoundingBox = originalBambooBoundingBox.clone().shift(event.getBlockPlaced().getLocation());
|
||||||
for (LivingEntity e : event.getBlock().getLocation().getNearbyLivingEntities(5)) {
|
for (LivingEntity e : event.getBlock().getLocation().getNearbyLivingEntities(5)) {
|
||||||
|
@@ -1,21 +1,24 @@
|
|||||||
package fr.pandacube.lib.paper.reflect.util;
|
package fr.pandacube.lib.paper.reflect.util;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.World;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
import fr.pandacube.lib.chat.Chat;
|
||||||
import fr.pandacube.lib.chat.ChatConfig.PandaTheme;
|
import fr.pandacube.lib.chat.ChatConfig.PandaTheme;
|
||||||
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftWorld;
|
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftWorld;
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ChunkMap;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerLevel;
|
||||||
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.util.ProgressListener;
|
||||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides static methods to save worlds presumably in a better way than Bukkit provides (better flushing, more released RAM).
|
||||||
|
*/
|
||||||
public class WorldSaveUtil {
|
public class WorldSaveUtil {
|
||||||
|
|
||||||
private static ChunkMap getChunkMap(World w) {
|
/**
|
||||||
return ReflectWrapper.wrapTyped(w, CraftWorld.class).getHandle().getChunkSource().chunkMap;
|
* Save the provided world using the NMS {@link ServerLevel#save(ProgressListener, boolean, boolean)} method.
|
||||||
}
|
* @param w the world to save.
|
||||||
|
*/
|
||||||
public static void nmsSaveFlush(World w) {
|
public static void nmsSaveFlush(World w) {
|
||||||
PerformanceAnalysisManager.getInstance().setAlteredTPSTitle(
|
PerformanceAnalysisManager.getInstance().setAlteredTPSTitle(
|
||||||
Chat.text("Sauvegarde map ").color(PandaTheme.CHAT_BROWN_2_SAT).thenData(w.getName()).thenText(" ...")
|
Chat.text("Sauvegarde map ").color(PandaTheme.CHAT_BROWN_2_SAT).thenData(w.getName()).thenText(" ...")
|
||||||
@@ -27,9 +30,14 @@ public class WorldSaveUtil {
|
|||||||
PerformanceAnalysisManager.getInstance().setAlteredTPSTitle(null);
|
PerformanceAnalysisManager.getInstance().setAlteredTPSTitle(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save all the loaded worlds, using {@link #nmsSaveFlush(World)}.
|
||||||
|
*/
|
||||||
public static void nmsSaveAllFlush() {
|
public static void nmsSaveAllFlush() {
|
||||||
Bukkit.getWorlds().forEach(WorldSaveUtil::nmsSaveFlush);
|
Bukkit.getWorlds().forEach(WorldSaveUtil::nmsSaveFlush);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WorldSaveUtil() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package fr.pandacube.lib.paper.reflect.wrapper.minecraft.world;
|
package fr.pandacube.lib.paper.reflect.wrapper.minecraft.world;
|
||||||
|
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
||||||
|
import fr.pandacube.lib.paper.reflect.wrapper.spottedleaf.moonrise.ChunkSystemChunkStorage;
|
||||||
import fr.pandacube.lib.reflect.Reflect;
|
import fr.pandacube.lib.reflect.Reflect;
|
||||||
import fr.pandacube.lib.reflect.ReflectClass;
|
import fr.pandacube.lib.reflect.ReflectClass;
|
||||||
import fr.pandacube.lib.reflect.ReflectMethod;
|
import fr.pandacube.lib.reflect.ReflectMethod;
|
||||||
@@ -12,17 +13,13 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
import static fr.pandacube.lib.util.ThrowableUtil.wrapEx;
|
import static fr.pandacube.lib.util.ThrowableUtil.wrapEx;
|
||||||
import static fr.pandacube.lib.util.ThrowableUtil.wrapReflectEx;
|
import static fr.pandacube.lib.util.ThrowableUtil.wrapReflectEx;
|
||||||
|
|
||||||
public class ChunkStorage extends ReflectWrapper {
|
public class ChunkStorage extends ReflectWrapper implements ChunkSystemChunkStorage {
|
||||||
public static final ReflectClass<?> REFLECT = wrapEx(() -> Reflect.ofClass("net.minecraft.world.level.chunk.storage.ChunkStorage"));
|
public static final ReflectClass<?> REFLECT = wrapEx(() -> Reflect.ofClass("net.minecraft.world.level.chunk.storage.ChunkStorage"));
|
||||||
private static final ReflectMethod<?> readSync = wrapEx(() -> REFLECT.method("readSync", ChunkPos.REFLECT.get())); // spigot/paper method
|
private static final ReflectMethod<?> read = wrapEx(() -> REFLECT.method("read", ChunkPos.REFLECT.get())); // spigot/paper method
|
||||||
|
|
||||||
public CompoundTag readSync(ChunkPos pos) {
|
|
||||||
return wrap(wrapReflectEx(() -> readSync.invoke(__getRuntimeInstance(), unwrap(pos))), CompoundTag.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<Optional<CompoundTag>> read(ChunkPos pos) {
|
public CompletableFuture<Optional<CompoundTag>> read(ChunkPos pos) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
CompletableFuture<Optional<?>> nmsFuture = (CompletableFuture<Optional<?>>) wrapReflectEx(() -> readSync.invoke(__getRuntimeInstance(), unwrap(pos)));
|
CompletableFuture<Optional<?>> nmsFuture = ((CompletableFuture<Optional<?>>) wrapReflectEx(() -> read.invoke(__getRuntimeInstance(), unwrap(pos))));
|
||||||
return nmsFuture.thenApply(o -> o.map(c -> wrap(c, CompoundTag.class)));
|
return nmsFuture.thenApply(o -> o.map(c -> wrap(c, CompoundTag.class)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
package fr.pandacube.lib.paper.reflect.wrapper.minecraft.world;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag;
|
||||||
|
import fr.pandacube.lib.reflect.Reflect;
|
||||||
|
import fr.pandacube.lib.reflect.ReflectClass;
|
||||||
|
import fr.pandacube.lib.reflect.ReflectMethod;
|
||||||
|
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||||
|
|
||||||
|
import static fr.pandacube.lib.util.ThrowableUtil.wrapEx;
|
||||||
|
import static fr.pandacube.lib.util.ThrowableUtil.wrapReflectEx;
|
||||||
|
|
||||||
|
public class RegionFileStorage extends ReflectWrapper {
|
||||||
|
public static final ReflectClass<?> REFLECT = wrapEx(() -> Reflect.ofClass("net.minecraft.world.level.chunk.storage.RegionFileStorage"));
|
||||||
|
private static final ReflectMethod<?> read = wrapEx(() -> REFLECT.method("read", ChunkPos.REFLECT.get())); // spigot/paper method
|
||||||
|
|
||||||
|
public CompoundTag read(ChunkPos pos) {
|
||||||
|
return wrap(wrapReflectEx(() -> read.invoke(__getRuntimeInstance(), unwrap(pos))), CompoundTag.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RegionFileStorage(Object obj) {
|
||||||
|
super(obj);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,25 +0,0 @@
|
|||||||
package fr.pandacube.lib.paper.reflect.wrapper.paper;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.reflect.Reflect;
|
|
||||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
|
||||||
import fr.pandacube.lib.reflect.ReflectClass;
|
|
||||||
import fr.pandacube.lib.reflect.ReflectMethod;
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|
||||||
|
|
||||||
import static fr.pandacube.lib.util.ThrowableUtil.wrapEx;
|
|
||||||
import static fr.pandacube.lib.util.ThrowableUtil.wrapReflectEx;
|
|
||||||
|
|
||||||
public class QueuedChangesMapLong2Object extends ReflectWrapper {
|
|
||||||
public static final ReflectClass<?> REFLECT = wrapEx(() -> Reflect.ofClass("com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object"));
|
|
||||||
public static final ReflectMethod<?> getVisibleMap = wrapEx(() -> REFLECT.method("getVisibleMap"));
|
|
||||||
|
|
||||||
/** The entries in the returned value are not mapped */
|
|
||||||
public Long2ObjectLinkedOpenHashMap<?> getVisibleMap() {
|
|
||||||
return (Long2ObjectLinkedOpenHashMap<?>) wrapReflectEx(() -> getVisibleMap.invoke(__getRuntimeInstance()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected QueuedChangesMapLong2Object(Object obj) {
|
|
||||||
super(obj);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,31 @@
|
|||||||
|
package fr.pandacube.lib.paper.reflect.wrapper.spottedleaf.moonrise;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.RegionFileStorage;
|
||||||
|
import fr.pandacube.lib.reflect.Reflect;
|
||||||
|
import fr.pandacube.lib.reflect.ReflectClass;
|
||||||
|
import fr.pandacube.lib.reflect.ReflectMethod;
|
||||||
|
import fr.pandacube.lib.reflect.wrapper.ConcreteWrapper;
|
||||||
|
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||||
|
import fr.pandacube.lib.reflect.wrapper.ReflectWrapperI;
|
||||||
|
|
||||||
|
import static fr.pandacube.lib.reflect.wrapper.ReflectWrapper.wrap;
|
||||||
|
import static fr.pandacube.lib.util.ThrowableUtil.wrapEx;
|
||||||
|
import static fr.pandacube.lib.util.ThrowableUtil.wrapReflectEx;
|
||||||
|
|
||||||
|
@ConcreteWrapper(ChunkSystemChunkStorage.__concrete.class)
|
||||||
|
public interface ChunkSystemChunkStorage extends ReflectWrapperI {
|
||||||
|
ReflectClass<?> REFLECT = wrapEx(() -> Reflect.ofClass("ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkStorage"));
|
||||||
|
ReflectMethod<?> moonrise$getRegionStorage = wrapEx(() -> REFLECT.method("moonrise$getRegionStorage"));
|
||||||
|
|
||||||
|
default RegionFileStorage moonrise$getRegionStorage() {
|
||||||
|
return wrap(wrapReflectEx(() -> moonrise$getRegionStorage.invoke(__getRuntimeInstance())), RegionFileStorage.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class __concrete extends ReflectWrapper implements ChunkSystemChunkStorage {
|
||||||
|
private __concrete(Object obj) {
|
||||||
|
super(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -69,6 +69,6 @@ public class SchedulerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private SchedulerUtil() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
package fr.pandacube.lib.paper.util;
|
||||||
|
|
||||||
import java.util.Timer;
|
import fr.pandacube.lib.chat.Chat;
|
||||||
import java.util.TimerTask;
|
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||||
import java.util.function.Predicate;
|
import fr.pandacube.lib.util.log.Log;
|
||||||
|
import net.kyori.adventure.bossbar.BossBar;
|
||||||
|
import net.kyori.adventure.bossbar.BossBar.Color;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
@@ -14,26 +16,37 @@ import org.bukkit.event.player.PlayerQuitEvent;
|
|||||||
import org.bukkit.scheduler.BukkitScheduler;
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
import java.util.Timer;
|
||||||
import fr.pandacube.lib.util.log.Log;
|
import java.util.TimerTask;
|
||||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
import java.util.function.Predicate;
|
||||||
import net.kyori.adventure.bossbar.BossBar;
|
|
||||||
import net.kyori.adventure.bossbar.BossBar.Color;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link BossBar} capable of automatically updating itself by registering a {@link BukkitTask} or using a {@link Timer}.
|
||||||
|
*/
|
||||||
public class AutoUpdatedBossBar implements Listener {
|
public class AutoUpdatedBossBar implements Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The boss bar itself.
|
||||||
|
*/
|
||||||
public final BossBar bar;
|
public final BossBar bar;
|
||||||
|
/**
|
||||||
|
* the function executed to update the boss bar.
|
||||||
|
*/
|
||||||
public final BarUpdater updater;
|
public final BarUpdater updater;
|
||||||
|
|
||||||
private Timer timer = null;
|
private Timer timer = null;
|
||||||
private BukkitTask bukkitTask = null;
|
private BukkitTask bukkitTask = null;
|
||||||
|
|
||||||
private boolean scheduled = false;
|
private boolean scheduled = false;
|
||||||
|
|
||||||
private boolean followPlayerList = false;
|
private LoginLogoutListener followPlayerList = null;
|
||||||
private Predicate<Player> playerCondition = null;
|
private Predicate<Player> playerCondition = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the provided boss bar into a new instance of {@link AutoUpdatedBossBar}.
|
||||||
|
* @param bar the boss bar itself.
|
||||||
|
* @param updater the updater that will be run to update the boss bar.
|
||||||
|
*/
|
||||||
public AutoUpdatedBossBar(BossBar bar, BarUpdater updater) {
|
public AutoUpdatedBossBar(BossBar bar, BarUpdater updater) {
|
||||||
this.bar = bar;
|
this.bar = bar;
|
||||||
this.updater = updater;
|
this.updater = updater;
|
||||||
@@ -66,10 +79,9 @@ public class AutoUpdatedBossBar implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule the update of this boss bar with synchronisation with the main Thread of the
|
* Schedule the update of this boss bar with synchronisation with the ticking of this Minecraft server.
|
||||||
* current Minecraft server (follow the tick count progress).
|
|
||||||
* The underlying method called is {@link BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)}.
|
* The underlying method called is {@link BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)}.
|
||||||
* The updater is executed by the Server Thread.
|
* The updater is executed by the main Server Thread.
|
||||||
* @param tickDelay number of server tick before running the first update of this boss bar
|
* @param tickDelay number of server tick before running the first update of this boss bar
|
||||||
* @param tickPeriod number of server tick between each call of the updater
|
* @param tickPeriod number of server tick between each call of the updater
|
||||||
*/
|
*/
|
||||||
@@ -90,55 +102,65 @@ public class AutoUpdatedBossBar implements Listener {
|
|||||||
}, tickDelay, tickPeriod);
|
}, tickDelay, tickPeriod);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-update the boss bar on player join and quit.
|
||||||
|
* @param condition an additional test that if it's true on player join/quit, will actually run the update. Can be null.
|
||||||
|
*/
|
||||||
public synchronized void followLoginLogout(Predicate<Player> condition) {
|
public synchronized void followLoginLogout(Predicate<Player> condition) {
|
||||||
playerCondition = condition;
|
playerCondition = condition;
|
||||||
if (followPlayerList)
|
if (followPlayerList != null)
|
||||||
return;
|
return;
|
||||||
followPlayerList = true;
|
followPlayerList = new LoginLogoutListener();
|
||||||
BukkitEvent.register(this);
|
BukkitEvent.register(followPlayerList);
|
||||||
Bukkit.getServer().getOnlinePlayers().forEach(p -> onPlayerJoin(new PlayerJoinEvent(p, Component.text(""))));
|
Bukkit.getServer().getOnlinePlayers().forEach(p -> followPlayerList.onPlayerJoin(new PlayerJoinEvent(p, Component.text(""))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the auto-update on player join and quit.
|
||||||
|
*/
|
||||||
public synchronized void unfollowPlayerList() {
|
public synchronized void unfollowPlayerList() {
|
||||||
if (!followPlayerList)
|
if (followPlayerList == null)
|
||||||
return;
|
return;
|
||||||
followPlayerList = false;
|
|
||||||
playerCondition = null;
|
playerCondition = null;
|
||||||
PlayerJoinEvent.getHandlerList().unregister(this);
|
BukkitEvent.unregister(followPlayerList);
|
||||||
PlayerQuitEvent.getHandlerList().unregister(this);
|
followPlayerList = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority=EventPriority.MONITOR)
|
private class LoginLogoutListener implements Listener {
|
||||||
public synchronized void onPlayerJoin(PlayerJoinEvent event) {
|
|
||||||
if (!followPlayerList)
|
@EventHandler(priority=EventPriority.MONITOR)
|
||||||
return;
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
if (playerCondition != null && !playerCondition.test(event.getPlayer()))
|
if (playerCondition != null && !playerCondition.test(event.getPlayer()))
|
||||||
return;
|
return;
|
||||||
synchronized (bar) {
|
synchronized (bar) {
|
||||||
event.getPlayer().showBossBar(bar);
|
event.getPlayer().showBossBar(bar);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@EventHandler(priority=EventPriority.HIGH)
|
||||||
@EventHandler(priority=EventPriority.HIGH)
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
public synchronized void onPlayerQuit(PlayerQuitEvent event) {
|
synchronized (bar) {
|
||||||
if (!followPlayerList)
|
event.getPlayer().hideBossBar(bar);
|
||||||
return;
|
}
|
||||||
synchronized (bar) {
|
|
||||||
event.getPlayer().hideBossBar(bar);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides this boss bar from all players.
|
||||||
|
*/
|
||||||
public void removeAll() {
|
public void removeAll() {
|
||||||
synchronized (bar) {
|
synchronized (bar) {
|
||||||
for (Player p : Bukkit.getOnlinePlayers())
|
for (Player p : Bukkit.getOnlinePlayers())
|
||||||
p.hideBossBar(bar);
|
p.hideBossBar(bar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel any auto-updating of this boss bar.
|
||||||
|
*/
|
||||||
public synchronized void cancel() {
|
public synchronized void cancel() {
|
||||||
if (!scheduled)
|
if (!scheduled)
|
||||||
return;
|
return;
|
||||||
@@ -151,13 +173,19 @@ public class AutoUpdatedBossBar implements Listener {
|
|||||||
bukkitTask.cancel();
|
bukkitTask.cancel();
|
||||||
bukkitTask = null;
|
bukkitTask = null;
|
||||||
}
|
}
|
||||||
|
unfollowPlayerList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface taking an instance of {@link AutoUpdatedBossBar} to update it. Returns nothing.
|
||||||
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface BarUpdater {
|
public interface BarUpdater {
|
||||||
|
/**
|
||||||
|
* Updates the boss bar.
|
||||||
|
* @param bar the auto-updated boss bar instance.
|
||||||
|
*/
|
||||||
void update(AutoUpdatedBossBar bar);
|
void update(AutoUpdatedBossBar bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +193,7 @@ public class AutoUpdatedBossBar implements Listener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to update the title of the boss bar without unnecessary packet.
|
* Utility method to update the title of the boss bar without unnecessary packet.
|
||||||
|
* @param title the new title.
|
||||||
*/
|
*/
|
||||||
public void setTitle(Chat title) {
|
public void setTitle(Chat title) {
|
||||||
synchronized (bar) {
|
synchronized (bar) {
|
||||||
@@ -174,6 +203,7 @@ public class AutoUpdatedBossBar implements Listener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to update the color of the boss bar without unnecessary packet.
|
* Utility method to update the color of the boss bar without unnecessary packet.
|
||||||
|
* @param color the new color.
|
||||||
*/
|
*/
|
||||||
public void setColor(Color color) {
|
public void setColor(Color color) {
|
||||||
synchronized (bar) {
|
synchronized (bar) {
|
||||||
@@ -183,6 +213,7 @@ public class AutoUpdatedBossBar implements Listener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to update the progress of the boss bar without unnecessary packet.
|
* Utility method to update the progress of the boss bar without unnecessary packet.
|
||||||
|
* @param progress the new progress value.
|
||||||
*/
|
*/
|
||||||
public void setProgress(double progress) {
|
public void setProgress(double progress) {
|
||||||
synchronized (bar) {
|
synchronized (bar) {
|
||||||
|
@@ -1,72 +0,0 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
|
||||||
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import net.kyori.adventure.text.format.TextColor;
|
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.DyeColor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class around chat coloring.
|
|
||||||
*/
|
|
||||||
public class BukkitChatColorUtil {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link TextColor} that is visually the closest from the provided {@link DyeColor} when used on a sign.
|
|
||||||
* @param dye the provided dye color
|
|
||||||
* @return the closest chat color from {@code dye}
|
|
||||||
*/
|
|
||||||
public static TextColor fromDyeToSignColor(DyeColor dye) {
|
|
||||||
// following code invalid due to text color on sign does not use rgb value of dye color.
|
|
||||||
//org.bukkit.Color col = dye.getColor();
|
|
||||||
//return ChatColor.of(new Color(col.asRGB()));
|
|
||||||
|
|
||||||
// the color values are extracted from IG screenshot of dyed text signs.
|
|
||||||
return switch (dye) {
|
|
||||||
case BLACK -> TextColor.fromHexString("#000000");
|
|
||||||
case RED -> TextColor.fromHexString("#650000");
|
|
||||||
case GREEN -> TextColor.fromHexString("#006500");
|
|
||||||
case BROWN -> TextColor.fromHexString("#361B07");
|
|
||||||
case BLUE -> TextColor.fromHexString("#000065");
|
|
||||||
case PURPLE -> TextColor.fromHexString("#3F0C5F");
|
|
||||||
case CYAN -> TextColor.fromHexString("#006565");
|
|
||||||
case LIGHT_GRAY -> TextColor.fromHexString("#535353");
|
|
||||||
case GRAY -> TextColor.fromHexString("#323232");
|
|
||||||
case PINK -> TextColor.fromHexString("#652947");
|
|
||||||
case LIME -> TextColor.fromHexString("#4B6500");
|
|
||||||
case YELLOW -> TextColor.fromHexString("#656500");
|
|
||||||
case LIGHT_BLUE -> TextColor.fromHexString("#3C4B51");
|
|
||||||
case MAGENTA -> TextColor.fromHexString("#650065");
|
|
||||||
case ORANGE -> TextColor.fromHexString("#65280C");
|
|
||||||
case WHITE -> TextColor.fromHexString("#656565");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static ChatColor toBukkit(net.md_5.bungee.api.ChatColor color) {
|
|
||||||
return ChatColor.valueOf(color.getName().toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static ChatColor toBukkit(TextColor color) {
|
|
||||||
return toBukkit(NamedTextColor.nearestTo(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static ChatColor toBukkit(NamedTextColor color) {
|
|
||||||
return ChatColor.valueOf(color.toString().toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static NamedTextColor toAdventure(ChatColor color) {
|
|
||||||
return NamedTextColor.NAMES.value(color.name().toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NamedTextColor toAdventure(net.md_5.bungee.api.ChatColor color) {
|
|
||||||
return NamedTextColor.NAMES.value(color.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -16,45 +16,102 @@ import org.bukkit.scheduler.BukkitTask;
|
|||||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||||
import fr.pandacube.lib.reflect.Reflect;
|
import fr.pandacube.lib.reflect.Reflect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to more concisely handle Bukkit Events
|
||||||
|
*/
|
||||||
public class BukkitEvent {
|
public class BukkitEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a single event executor.
|
||||||
|
* <p>
|
||||||
|
* The priority if the event executor is {@link EventPriority#NORMAL}.
|
||||||
|
* Does not ignore cancelled events.
|
||||||
|
* @param eventClass the class of the event to listen to.
|
||||||
|
* @param eventExecutor the executor.
|
||||||
|
* @return the {@link Listener} instance, that is in our case the provided executor.
|
||||||
|
* @param <E> the event type.
|
||||||
|
*/
|
||||||
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor) {
|
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor) {
|
||||||
return register(eventClass, eventExecutor, EventPriority.NORMAL, false);
|
return register(eventClass, eventExecutor, EventPriority.NORMAL, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a single event executor.
|
||||||
|
* <p>
|
||||||
|
* Does not ignore cancelled events.
|
||||||
|
* @param eventClass the class of the event to listen to.
|
||||||
|
* @param eventExecutor the executor.
|
||||||
|
* @param priority the event priority.
|
||||||
|
* @return the {@link Listener} instance, that is in our case the provided executor.
|
||||||
|
* @param <E> the event type.
|
||||||
|
*/
|
||||||
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority) {
|
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority) {
|
||||||
return register(eventClass, eventExecutor, priority, false);
|
return register(eventClass, eventExecutor, priority, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a single event executor.
|
||||||
|
* <p>
|
||||||
|
* The priority if the event executor is {@link EventPriority#NORMAL}.
|
||||||
|
* @param eventClass the class of the event to listen to.
|
||||||
|
* @param eventExecutor the executor.
|
||||||
|
* @param ignoreCancelled whether to pass cancelled events or not.
|
||||||
|
* @return the {@link Listener} instance, that is in our case the provided executor.
|
||||||
|
* @param <E> the event type.
|
||||||
|
*/
|
||||||
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, boolean ignoreCancelled) {
|
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, boolean ignoreCancelled) {
|
||||||
return register(eventClass, eventExecutor, EventPriority.NORMAL, ignoreCancelled);
|
return register(eventClass, eventExecutor, EventPriority.NORMAL, ignoreCancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a single event executor.
|
||||||
|
* @param eventClass the class of the event to listen to.
|
||||||
|
* @param eventExecutor the executor.
|
||||||
|
* @param priority the event priority.
|
||||||
|
* @param ignoreCancelled whether to pass cancelled events or not.
|
||||||
|
* @return the {@link Listener} instance, that is in our case the provided executor.
|
||||||
|
* @param <E> the event type.
|
||||||
|
*/
|
||||||
public static <E extends Event> Listener register(Class<E> eventClass, EventListener<E> eventExecutor, EventPriority priority, boolean ignoreCancelled) {
|
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);
|
Bukkit.getPluginManager().registerEvent(eventClass, eventExecutor, priority, eventExecutor, PandaLibPaper.getPlugin(), ignoreCancelled);
|
||||||
return eventExecutor;
|
return eventExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a listener.
|
||||||
|
* This is equivalent to calling {@code Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin());}
|
||||||
|
* @param l the listener.
|
||||||
|
*/
|
||||||
public static void register(Listener l) {
|
public static void register(Listener l) {
|
||||||
Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin());
|
Bukkit.getPluginManager().registerEvents(l, PandaLibPaper.getPlugin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a listener
|
||||||
|
* @param listener the listener to unregister.
|
||||||
|
*/
|
||||||
public static void unregister(Listener listener) {
|
public static void unregister(Listener listener) {
|
||||||
HandlerList.unregisterAll(listener);
|
HandlerList.unregisterAll(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all existing subclasses of {@link Event}.
|
||||||
|
* @return a list of all existing subclasses of {@link Event}.
|
||||||
|
*/
|
||||||
public static List<Class<? extends Event>> getAllEventClasses() {
|
public static List<Class<? extends Event>> getAllEventClasses() {
|
||||||
List<Class<? extends Event>> classes = Reflect.ofClass(Event.class).getAllSubclasses(false);
|
List<Class<? extends Event>> classes = Reflect.ofClass(Event.class).getAllSubclasses(false);
|
||||||
classes.removeIf(e -> getHandlerList(e) == null);
|
classes.removeIf(e -> getHandlerList(e) == null);
|
||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the handlerList of the provided Event class.
|
||||||
|
* @param type the event class.
|
||||||
|
* @return the handlerList.
|
||||||
|
*/
|
||||||
// method retrieved from OB.plugin.SimplePluginManager#getEventListeners
|
// method retrieved from OB.plugin.SimplePluginManager#getEventListeners
|
||||||
public static HandlerList getHandlerList(Class<? extends Event> type) {
|
public static HandlerList getHandlerList(Class<? extends Event> type) {
|
||||||
try {
|
try {
|
||||||
@@ -81,11 +138,18 @@ public class BukkitEvent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An single executor event listener. Used for the {@link #register(Class, EventListener)} static method and the other variants.
|
||||||
|
* @param <E> the event type.
|
||||||
|
*/
|
||||||
public interface EventListener<E extends Event> extends Listener, EventExecutor {
|
public interface EventListener<E extends Event> extends Listener, EventExecutor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event handler.
|
||||||
|
* @param event the event.
|
||||||
|
*/
|
||||||
void onEvent(E event);
|
void onEvent(E event);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -99,12 +163,17 @@ public class BukkitEvent {
|
|||||||
* Abstract implementation of {@link EventListener} that ensure as good as it can,
|
* Abstract implementation of {@link EventListener} that ensure as good as it can,
|
||||||
* that it is the last listener called to handle the event.
|
* that it is the last listener called to handle the event.
|
||||||
*
|
*
|
||||||
* @param <E> the type of the event
|
* @param <E> the type of the event.
|
||||||
*/
|
*/
|
||||||
public static abstract class EnforcedLastListener<E extends Event> implements EventListener<E> {
|
public static abstract class EnforcedLastListener<E extends Event> implements EventListener<E> {
|
||||||
private final Class<E> eventClass;
|
private final Class<E> eventClass;
|
||||||
private final boolean ignoreCancelled;
|
private final boolean ignoreCancelled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link EnforcedLastListener}.
|
||||||
|
* @param eventClass the event to listen.
|
||||||
|
* @param ignoreCancelled whether to pass cancelled events or not.
|
||||||
|
*/
|
||||||
public EnforcedLastListener(Class<E> eventClass, boolean ignoreCancelled) {
|
public EnforcedLastListener(Class<E> eventClass, boolean ignoreCancelled) {
|
||||||
this.eventClass = eventClass;
|
this.eventClass = eventClass;
|
||||||
this.ignoreCancelled = ignoreCancelled;
|
this.ignoreCancelled = ignoreCancelled;
|
||||||
@@ -143,6 +212,9 @@ public class BukkitEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BukkitEvent() {}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,13 @@ package fr.pandacube.lib.paper.util;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import org.bukkit.Color;
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.DyeColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color related utility class.
|
||||||
|
*/
|
||||||
public class ColorUtil {
|
public class ColorUtil {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -13,9 +18,10 @@ public class ColorUtil {
|
|||||||
private static final List<Color> rainbowColors = new ArrayList<>();
|
private static final List<Color> rainbowColors = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a rainbow color
|
* Gets a rainbow color.
|
||||||
* @param variation from 0 (include) to 1 (exclude).
|
* @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
|
* 0 is red, 1/6 is yellow, 2/6 is green, 3/6 is cyan, 4/6 is blue, 5/6 is magenta
|
||||||
|
* @return the computed rainbow color.
|
||||||
*/
|
*/
|
||||||
public static Color getRainbowColor(double variation) {
|
public static Color getRainbowColor(double variation) {
|
||||||
synchronized (rainbowColors) {
|
synchronized (rainbowColors) {
|
||||||
@@ -34,16 +40,42 @@ public class ColorUtil {
|
|||||||
|
|
||||||
return rainbowColors.get((int)(variation * rainbowColors.size()));
|
return rainbowColors.get((int)(variation * rainbowColors.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link TextColor} that is visually the closest from the provided {@link DyeColor} when used on a sign.
|
||||||
|
* @param dye the provided dye color
|
||||||
|
* @return the closest chat color from {@code dye}
|
||||||
|
*/
|
||||||
|
public static TextColor getTextColorOfDyedSign(DyeColor dye) {
|
||||||
|
// following code invalid due to text color on sign does not use rgb value of dye color.
|
||||||
|
//org.bukkit.Color col = dye.getColor();
|
||||||
|
//return ChatColor.of(new Color(col.asRGB()));
|
||||||
|
|
||||||
|
// the color values are extracted from IG screenshot of dyed text signs.
|
||||||
|
return switch (dye) {
|
||||||
|
case BLACK -> TextColor.fromHexString("#000000");
|
||||||
|
case RED -> TextColor.fromHexString("#650000");
|
||||||
|
case GREEN -> TextColor.fromHexString("#006500");
|
||||||
|
case BROWN -> TextColor.fromHexString("#361B07");
|
||||||
|
case BLUE -> TextColor.fromHexString("#000065");
|
||||||
|
case PURPLE -> TextColor.fromHexString("#3F0C5F");
|
||||||
|
case CYAN -> TextColor.fromHexString("#006565");
|
||||||
|
case LIGHT_GRAY -> TextColor.fromHexString("#535353");
|
||||||
|
case GRAY -> TextColor.fromHexString("#323232");
|
||||||
|
case PINK -> TextColor.fromHexString("#652947");
|
||||||
|
case LIME -> TextColor.fromHexString("#4B6500");
|
||||||
|
case YELLOW -> TextColor.fromHexString("#656500");
|
||||||
|
case LIGHT_BLUE -> TextColor.fromHexString("#3C4B51");
|
||||||
|
case MAGENTA -> TextColor.fromHexString("#650065");
|
||||||
|
case ORANGE -> TextColor.fromHexString("#65280C");
|
||||||
|
case WHITE -> TextColor.fromHexString("#656565");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ColorUtil() {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,49 +0,0 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
|
||||||
|
|
||||||
import org.bukkit.Location;
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to handle stacks of entities. A stack an entity is when an entity is mounting onto another one.
|
|
||||||
* For instance, a player mounting a horse. We also say that the horse is the vehicle of the player.
|
|
||||||
*/
|
|
||||||
public class EntityStackUtil {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Teleport a stack of entity, all at once.
|
|
||||||
*
|
|
||||||
* @param e An entity that is part of the stack to teleport.
|
|
||||||
* @param l The location where to send the entity stack.
|
|
||||||
* @deprecated This method has not been tested since a long time ago.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
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 une 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é
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -54,12 +54,12 @@ public class ExperienceUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @see <a href="http://minecraft.gamepedia.com/Experience#Leveling_up">Experience (#leveling up) - Minecraft Wiki</a>
|
* @see <a href="http://minecraft.gamepedia.com/Experience#Leveling_up">Experience (#leveling up) - Minecraft Wiki</a>
|
||||||
*
|
* <p>
|
||||||
* "The formulas for figuring out how many experience orbs you need to
|
* <q>The formulas for figuring out how many experience orbs you need to
|
||||||
* get to the next level are as follows:
|
* get to the next level are as follows:
|
||||||
* Experience Required = 2[Current Level] + 7 (at levels 0-15)
|
* Experience Required = 2[Current Level] + 7 (at levels 0-15)
|
||||||
* 5[Current Level] - 38 (at levels 16-30)
|
* 5[Current Level] - 38 (at levels 16-30)
|
||||||
* 9[Current Level] - 158 (at level 31+)"
|
* 9[Current Level] - 158 (at level 31+)</q>
|
||||||
*/
|
*/
|
||||||
private static int getExpToNext(int level) {
|
private static int getExpToNext(int level) {
|
||||||
if (level > 30) return 9 * level - 158;
|
if (level > 30) return 9 * level - 158;
|
||||||
@@ -89,4 +89,7 @@ public class ExperienceUtil {
|
|||||||
player.setExp((float) (levelAndExp - level));
|
player.setExp((float) (levelAndExp - level));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ExperienceUtil() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,116 +0,0 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.World.Environment;
|
|
||||||
import org.bukkit.WorldCreator;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.util.BiMap;
|
|
||||||
import fr.pandacube.lib.util.FileUtils;
|
|
||||||
import fr.pandacube.lib.util.log.Log;
|
|
||||||
import fr.pandacube.lib.util.RandomUtil;
|
|
||||||
|
|
||||||
public class GameWorldUtils implements Listener {
|
|
||||||
|
|
||||||
private static final BiMap<String, World> gameWorld = new BiMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
public static World getOrLoadGameWorld(String world, Consumer<World> operationOnLoad) throws IOException {
|
|
||||||
if (gameWorld.containsKey(world)) {
|
|
||||||
return gameWorld.get(world);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return loadGameWorld(world, operationOnLoad);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.severe(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static World getGameWorldIfLoaded(String world) {
|
|
||||||
if (gameWorld.containsKey(world)) {
|
|
||||||
return gameWorld.get(world);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean unloadGameWorld(String world) {
|
|
||||||
if (gameWorld.containsKey(world)) {
|
|
||||||
World rem = gameWorld.remove(world);
|
|
||||||
String copiedName = rem.getName();
|
|
||||||
boolean ret = Bukkit.unloadWorld(rem, false);
|
|
||||||
if (ret)
|
|
||||||
FileUtils.delete(new File(Bukkit.getWorldContainer(), copiedName));
|
|
||||||
else
|
|
||||||
Log.warning("Unable to unload game world " + copiedName + " for some reason.");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void unloadUnusedGameWorlds() {
|
|
||||||
for (String world : new ArrayList<>(gameWorld.keySet())) {
|
|
||||||
World rem = gameWorld.get(world);
|
|
||||||
if (rem.getPlayers().stream().noneMatch(Player::isOnline)) {
|
|
||||||
unloadGameWorld(world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean isGameWorldLoaded(String world) {
|
|
||||||
return gameWorld.containsKey(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static World loadGameWorld(String world, Consumer<World> operationOnLoad) throws IOException {
|
|
||||||
if (gameWorld.containsKey(world))
|
|
||||||
throw new IllegalStateException("GameWorld '"+world+"' is already loaded.");
|
|
||||||
|
|
||||||
if (!new File(Bukkit.getWorldContainer(), world).isDirectory())
|
|
||||||
throw new IllegalStateException("GameWorld '"+world+"' does not exist");
|
|
||||||
|
|
||||||
String copiedName = world + "_gen" + RandomUtil.rand.nextInt(100000, 999999);
|
|
||||||
|
|
||||||
File srcDir = new File(Bukkit.getWorldContainer(), world);
|
|
||||||
File destDir = new File(Bukkit.getWorldContainer(), copiedName);
|
|
||||||
FileUtils.delete(destDir);
|
|
||||||
FileUtils.copy(srcDir, destDir);
|
|
||||||
new File(destDir, "session.lock").delete();
|
|
||||||
new File(destDir, "uid.dat").delete();
|
|
||||||
|
|
||||||
World w = Bukkit.createWorld(new WorldCreator(copiedName).environment(Environment.NORMAL));
|
|
||||||
if (w == null) {
|
|
||||||
throw new RuntimeException("Unable to create the world " + copiedName + ": Bukkit.createWorld(...) returned null value.");
|
|
||||||
}
|
|
||||||
w.setAutoSave(false);
|
|
||||||
gameWorld.put(world, w);
|
|
||||||
if (Bukkit.getPluginManager().getPlugin("Multiverse-Core") != null)
|
|
||||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "mvm set hidden true "+copiedName);
|
|
||||||
operationOnLoad.accept(w);
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -1,12 +1,7 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
package fr.pandacube.lib.paper.util;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||||
import java.util.Set;
|
import fr.pandacube.lib.util.RandomUtil;
|
||||||
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.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
@@ -17,20 +12,35 @@ import org.bukkit.block.Block;
|
|||||||
import org.bukkit.block.BlockFace;
|
import org.bukkit.block.BlockFace;
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
import fr.pandacube.lib.util.RandomUtil;
|
import java.util.Set;
|
||||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class related to {@link Location}.
|
||||||
|
*/
|
||||||
public class LocationUtil {
|
public class LocationUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a concise {@link String} representation of a {@link Location}.
|
||||||
|
* <p>
|
||||||
|
* The format is {@code (worldName, 12, 45, -1304)}. The coordinates are those of the containing block (the values
|
||||||
|
* are cast to int).
|
||||||
|
* @param loc the location.
|
||||||
|
* @return a short string representation of the location.
|
||||||
|
*/
|
||||||
public static String conciseToString(Location loc) {
|
public static String conciseToString(Location loc) {
|
||||||
String world = loc.getWorld() == null ? "null" : loc.getWorld().getName();
|
String world = loc.getWorld() == null ? "null" : loc.getWorld().getName();
|
||||||
return "(" + world + ", " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")";
|
return "(" + world + ", " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ")";
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Return a random secure location in the provided world, inside the current
|
* Return a random secure location in the provided world, inside the current WorldBorder of the world.
|
||||||
* WorldBorder. Will be on the surface, for non-nether world, or below the roof of the nether world
|
* Will be on the surface, for non-nether world, or below the roof of the nether world.
|
||||||
* @param w the world in which to pick a location
|
* @param w the world in which to pick a location.
|
||||||
* @param extraSecureCheck provides extra checks to determine location security
|
* @param extraSecureCheck provides extra checks to determine location security.
|
||||||
|
* @return a future that will provide a random secure location.
|
||||||
*/
|
*/
|
||||||
public static CompletableFuture<Location> getRandomSecureLocation(World w, Predicate<Location> extraSecureCheck) {
|
public static CompletableFuture<Location> getRandomSecureLocation(World w, Predicate<Location> extraSecureCheck) {
|
||||||
|
|
||||||
@@ -48,6 +58,17 @@ public class LocationUtil {
|
|||||||
|
|
||||||
|
|
||||||
private static final int maxTryBeforeCancelRandomLocation = 75;
|
private static final int maxTryBeforeCancelRandomLocation = 75;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a random secure location in the provided world, inside the bounding box defined by min and max.
|
||||||
|
* Will be on the surface (the height limits of the bounding box are ignored), for non-nether world, or below the
|
||||||
|
* roof of the nether world.
|
||||||
|
* @param w the world in which to pick a location.
|
||||||
|
* @param min the min of the bounding box.
|
||||||
|
* @param max the max of the bounding box.
|
||||||
|
* @param extraSecureCheck provides extra checks to determine location security.
|
||||||
|
* @return a future that will provide a random secure location.
|
||||||
|
*/
|
||||||
public static CompletableFuture<Location> getRandomSecureLocation(World w, Location min, Location max, Predicate<Location> extraSecureCheck) {
|
public static CompletableFuture<Location> getRandomSecureLocation(World w, Location min, Location max, Predicate<Location> extraSecureCheck) {
|
||||||
|
|
||||||
CompletableFuture<Location> future = new CompletableFuture<>();
|
CompletableFuture<Location> future = new CompletableFuture<>();
|
||||||
@@ -94,12 +115,12 @@ public class LocationUtil {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Try to get a secure location with the same X and Z coordinate as the
|
||||||
* @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
|
* provided location, but Y modified to ensure security for player
|
||||||
* who will be teleported to this location.
|
* who will be teleported to this location.
|
||||||
* May return null if it is impossible to find a secure location.
|
* May return null if it is impossible to find a secure location.
|
||||||
|
* @param l the source location
|
||||||
|
* @return a secure location, or null if not found around the provided location.
|
||||||
*/
|
*/
|
||||||
public static Location getSecureLocationOrNull(Location l) {
|
public static Location getSecureLocationOrNull(Location l) {
|
||||||
l = l.clone();
|
l = l.clone();
|
||||||
@@ -112,7 +133,13 @@ public class LocationUtil {
|
|||||||
return currPosSafe(b) ? b.getLocation().add(0.5, 0, 0.5) : null;
|
return currPosSafe(b) ? b.getLocation().add(0.5, 0, 0.5) : null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided block is a safe block to spawn/be teleported in.
|
||||||
|
* More specifically, this block and its block above is air, and the block below is a non-lethal solid block.
|
||||||
|
* @param b the block to test.
|
||||||
|
* @return true if the provided block is a safe block to spawn/be teleported in, false otherwise.
|
||||||
|
*/
|
||||||
public static boolean currPosSafe(Block b) {
|
public static boolean currPosSafe(Block b) {
|
||||||
return b.getY() >= b.getWorld().getMinHeight() + 1 && b.getY() <= b.getWorld().getMaxHeight()
|
return b.getY() >= b.getWorld().getMinHeight() + 1 && b.getY() <= b.getWorld().getMaxHeight()
|
||||||
&& isSecureFloor(b.getRelative(BlockFace.DOWN))
|
&& isSecureFloor(b.getRelative(BlockFace.DOWN))
|
||||||
@@ -120,10 +147,10 @@ public class LocationUtil {
|
|||||||
&& isAir(b.getRelative(BlockFace.UP));
|
&& isAir(b.getRelative(BlockFace.UP));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isAir(Block b) { return b.getType() == Material.AIR; }
|
private static boolean isAir(Block b) { return b.getType() == Material.AIR; }
|
||||||
public static boolean isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); }
|
private static boolean isSecureFloor(Block b) { return !isAir(b) && !dangerousBlocks.contains(b.getType()); }
|
||||||
|
|
||||||
public static final Set<Material> dangerousBlocks = EnumSet.of(
|
private static final Set<Material> dangerousBlocks = Set.of(
|
||||||
Material.LAVA,
|
Material.LAVA,
|
||||||
Material.WATER,
|
Material.WATER,
|
||||||
Material.COBWEB,
|
Material.COBWEB,
|
||||||
@@ -137,21 +164,6 @@ public class LocationUtil {
|
|||||||
Material.NETHER_PORTAL,
|
Material.NETHER_PORTAL,
|
||||||
Material.END_GATEWAY
|
Material.END_GATEWAY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the {@link Location} l is inside the cuboid formed by the 2 others
|
|
||||||
* Locations min and max.
|
|
||||||
* @return true if l is inside the cuboid min-max
|
|
||||||
*/
|
|
||||||
public static boolean isIn(Location l, Location min, Location max) {
|
|
||||||
return (l.getWorld().equals(min.getWorld()) && l.getWorld().equals(max.getWorld()) && l.getX() >= min.getX()
|
|
||||||
&& l.getX() <= max.getX() && l.getY() >= min.getY() && l.getY() <= max.getY() && l.getZ() >= min.getZ()
|
|
||||||
&& l.getZ() <= max.getZ());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -160,14 +172,18 @@ public class LocationUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new location based on the linear interpolation between p0 and p1, according to the value c.
|
* Return a new location based on the linear interpolation between p0 and p1, according to the value c.
|
||||||
* @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. Other finite numbers are allowed, but the returned location won't be part of the {@code [p0;p1]} segment.
|
* @param p0 the first location.
|
||||||
|
* @param p1 the second location.
|
||||||
|
* @param c between 0 and 1. If 0, it returns p0 and if 1, returns p1. All finite numbers are allowed.
|
||||||
* @return The location, linearly interpolated between p0 and p1 with the value c. The yaw and pitch in the returned location are those of p0.
|
* @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.
|
* @throws IllegalArgumentException if the provided locations are not in the same world.
|
||||||
*/
|
*/
|
||||||
public static Location lerp(Location p0, Location p1, float c) {
|
public static Location lerp(Location p0, Location p1, float c) {
|
||||||
return p0.clone().add(p1.clone().subtract(p0).multiply(c));
|
return p0.clone().add(p1.clone().subtract(p0).multiply(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private LocationUtil() {}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,29 @@
|
|||||||
package fr.pandacube.lib.paper.util;
|
package fr.pandacube.lib.paper.util;
|
||||||
|
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.block.data.type.HangingSign;
|
||||||
import org.bukkit.block.data.type.Sign;
|
import org.bukkit.block.data.type.Sign;
|
||||||
import org.bukkit.block.data.type.WallSign;
|
import org.bukkit.block.data.type.WallSign;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class around {@link Material}.
|
||||||
|
*/
|
||||||
public class MaterialUtil {
|
public class MaterialUtil {
|
||||||
|
|
||||||
|
private static final Set<Class<? extends BlockData>> signBlockDataTypes = Set.of(WallSign.class, Sign.class, HangingSign.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided {@link Material} is a sign, a wall sign or a hanging sign.
|
||||||
|
* @param m the material.
|
||||||
|
* @return true if the material is a kind of sign.
|
||||||
|
*/
|
||||||
public static boolean isSign(Material m) {
|
public static boolean isSign(Material m) {
|
||||||
return WallSign.class.equals(m.data) || Sign.class.equals(m.data);
|
return signBlockDataTypes.contains(m.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MaterialUtil() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -116,9 +116,9 @@ public class ScoreboardUtil {
|
|||||||
public static void updateScoreboardSidebar(Scoreboard scBrd, Chat title, Chat[] lines) {
|
public static void updateScoreboardSidebar(Scoreboard scBrd, Chat title, Chat[] lines) {
|
||||||
Component[] cmpLines = new Component[lines.length];
|
Component[] cmpLines = new Component[lines.length];
|
||||||
for (int i = 0; i < lines.length; i++) {
|
for (int i = 0; i < lines.length; i++) {
|
||||||
cmpLines[i] = lines[i].getAdv();
|
cmpLines[i] = lines[i].get();
|
||||||
}
|
}
|
||||||
updateScoreboardSidebar(scBrd, title.getAdv(), cmpLines);
|
updateScoreboardSidebar(scBrd, title.get(), cmpLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -135,11 +135,13 @@ public class ScoreboardUtil {
|
|||||||
public static void updateScoreboardSidebar(Scoreboard scBrd, Chat title, List<Chat> lines) {
|
public static void updateScoreboardSidebar(Scoreboard scBrd, Chat title, List<Chat> lines) {
|
||||||
Component[] cmpLines = new Component[lines.size()];
|
Component[] cmpLines = new Component[lines.size()];
|
||||||
for (int i = 0; i < cmpLines.length; i++) {
|
for (int i = 0; i < cmpLines.length; i++) {
|
||||||
cmpLines[i] = lines.get(i).getAdv();
|
cmpLines[i] = lines.get(i).get();
|
||||||
}
|
}
|
||||||
updateScoreboardSidebar(scBrd, title.getAdv(), cmpLines);
|
updateScoreboardSidebar(scBrd, title.get(), cmpLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ScoreboardUtil() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -24,40 +24,73 @@ import fr.pandacube.lib.chat.Chat;
|
|||||||
*/
|
*/
|
||||||
public enum Skull {
|
public enum Skull {
|
||||||
|
|
||||||
|
/** Standard skull of player MHF_ArrowLeft. */
|
||||||
ARROW_LEFT("MHF_ArrowLeft"),
|
ARROW_LEFT("MHF_ArrowLeft"),
|
||||||
|
/** Standard skull of player MHF_ArrowRight. */
|
||||||
ARROW_RIGHT("MHF_ArrowRight"),
|
ARROW_RIGHT("MHF_ArrowRight"),
|
||||||
|
/** Standard skull of player MHF_ArrowUp. */
|
||||||
ARROW_UP("MHF_ArrowUp"),
|
ARROW_UP("MHF_ArrowUp"),
|
||||||
|
/** Standard skull of player MHF_ArrowDown. */
|
||||||
ARROW_DOWN("MHF_ArrowDown"),
|
ARROW_DOWN("MHF_ArrowDown"),
|
||||||
|
/** Standard skull of player MHF_Question. */
|
||||||
QUESTION("MHF_Question"),
|
QUESTION("MHF_Question"),
|
||||||
|
/** Standard skull of player MHF_Exclamation. */
|
||||||
EXCLAMATION("MHF_Exclamation"),
|
EXCLAMATION("MHF_Exclamation"),
|
||||||
|
/** Standard skull of player FHG_Cam. */
|
||||||
CAMERA("FHG_Cam"),
|
CAMERA("FHG_Cam"),
|
||||||
|
|
||||||
|
/** Standard skull of player MHF_PigZombie. */
|
||||||
ZOMBIE_PIGMAN("MHF_PigZombie"),
|
ZOMBIE_PIGMAN("MHF_PigZombie"),
|
||||||
|
/** Standard skull of player MHF_Pig. */
|
||||||
PIG("MHF_Pig"),
|
PIG("MHF_Pig"),
|
||||||
|
/** Standard skull of player MHF_Sheep. */
|
||||||
SHEEP("MHF_Sheep"),
|
SHEEP("MHF_Sheep"),
|
||||||
|
/** Standard skull of player MHF_Blaze. */
|
||||||
BLAZE("MHF_Blaze"),
|
BLAZE("MHF_Blaze"),
|
||||||
|
/** Standard skull of player MHF_Chicken. */
|
||||||
CHICKEN("MHF_Chicken"),
|
CHICKEN("MHF_Chicken"),
|
||||||
|
/** Standard skull of player MHF_Cow. */
|
||||||
COW("MHF_Cow"),
|
COW("MHF_Cow"),
|
||||||
|
/** Standard skull of player MHF_Slime. */
|
||||||
SLIME("MHF_Slime"),
|
SLIME("MHF_Slime"),
|
||||||
|
/** Standard skull of player MHF_Spider. */
|
||||||
SPIDER("MHF_Spider"),
|
SPIDER("MHF_Spider"),
|
||||||
|
/** Standard skull of player MHF_Squid. */
|
||||||
SQUID("MHF_Squid"),
|
SQUID("MHF_Squid"),
|
||||||
|
/** Standard skull of player MHF_Villager. */
|
||||||
VILLAGER("MHF_Villager"),
|
VILLAGER("MHF_Villager"),
|
||||||
|
/** Standard skull of player MHF_Ocelot. */
|
||||||
OCELOT("MHF_Ocelot"),
|
OCELOT("MHF_Ocelot"),
|
||||||
|
/** Standard skull of player MHF_Herobrine. */
|
||||||
HEROBRINE("MHF_Herobrine"),
|
HEROBRINE("MHF_Herobrine"),
|
||||||
|
/** Standard skull of player MHF_LavaSlime. */
|
||||||
LAVA_SLIME("MHF_LavaSlime"),
|
LAVA_SLIME("MHF_LavaSlime"),
|
||||||
|
/** Standard skull of player MHF_MushroomCow. */
|
||||||
MOOSHROOM("MHF_MushroomCow"),
|
MOOSHROOM("MHF_MushroomCow"),
|
||||||
|
/** Standard skull of player MHF_Golem. */
|
||||||
GOLEM("MHF_Golem"),
|
GOLEM("MHF_Golem"),
|
||||||
|
/** Standard skull of player MHF_Ghast. */
|
||||||
GHAST("MHF_Ghast"),
|
GHAST("MHF_Ghast"),
|
||||||
|
/** Standard skull of player MHF_Enderman. */
|
||||||
ENDERMAN("MHF_Enderman"),
|
ENDERMAN("MHF_Enderman"),
|
||||||
|
/** Standard skull of player MHF_CaveSpider. */
|
||||||
CAVE_SPIDER("MHF_CaveSpider"),
|
CAVE_SPIDER("MHF_CaveSpider"),
|
||||||
|
|
||||||
|
/** Standard skull of player MHF_Cactus. */
|
||||||
CACTUS("MHF_Cactus"),
|
CACTUS("MHF_Cactus"),
|
||||||
|
/** Standard skull of player MHF_Cake. */
|
||||||
CAKE("MHF_Cake"),
|
CAKE("MHF_Cake"),
|
||||||
|
/** Standard skull of player MHF_Chest. */
|
||||||
CHEST("MHF_Chest"),
|
CHEST("MHF_Chest"),
|
||||||
|
/** Standard skull of player MHF_Melon. */
|
||||||
MELON("MHF_Melon"),
|
MELON("MHF_Melon"),
|
||||||
|
/** Standard skull of player MHF_OakLog. */
|
||||||
LOG("MHF_OakLog"),
|
LOG("MHF_OakLog"),
|
||||||
|
/** Standard skull of player MHF_Pumpkin. */
|
||||||
PUMPKIN("MHF_Pumpkin"),
|
PUMPKIN("MHF_Pumpkin"),
|
||||||
|
/** Standard skull of player MHF_TNT. */
|
||||||
TNT("MHF_TNT"),
|
TNT("MHF_TNT"),
|
||||||
|
/** Standard skull of player MHF_TNT2. */
|
||||||
DYNAMITE("MHF_TNT2");
|
DYNAMITE("MHF_TNT2");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
@@ -76,7 +109,8 @@ public enum Skull {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Return the item based on this Skull enum, with the provided display name and lore.
|
* Return the item based on this Skull enum, with the provided display name and lore.
|
||||||
*
|
* @param displayName the display name to add to the returned skull.
|
||||||
|
* @param lore the lore to add to the returned skull.
|
||||||
* @return item stack
|
* @return item stack
|
||||||
*/
|
*/
|
||||||
public ItemStack get(Chat displayName, List<Chat> lore) {
|
public ItemStack get(Chat displayName, List<Chat> lore) {
|
||||||
@@ -89,6 +123,8 @@ public enum Skull {
|
|||||||
* Return a skull of a player based on their name.
|
* Return a skull of a player based on their name.
|
||||||
*
|
*
|
||||||
* @param name player's name
|
* @param name player's name
|
||||||
|
* @param displayName the display name to add to the returned skull.
|
||||||
|
* @param lore the lore to add to the returned skull.
|
||||||
* @return item stack
|
* @return item stack
|
||||||
*/
|
*/
|
||||||
public static ItemStack getFromPlayerName(String name, Chat displayName, List<Chat> lore) {
|
public static ItemStack getFromPlayerName(String name, Chat displayName, List<Chat> lore) {
|
||||||
@@ -99,10 +135,10 @@ public enum Skull {
|
|||||||
boolean b = meta.setOwner(name);
|
boolean b = meta.setOwner(name);
|
||||||
|
|
||||||
if (displayName != null)
|
if (displayName != null)
|
||||||
meta.displayName(displayName.getAdv());
|
meta.displayName(displayName.get());
|
||||||
|
|
||||||
if (lore != null)
|
if (lore != null)
|
||||||
meta.lore(lore.stream().map(Chat::getAdv).collect(Collectors.toList()));
|
meta.lore(lore.stream().map(Chat::get).collect(Collectors.toList()));
|
||||||
|
|
||||||
itemStack.setItemMeta(meta);
|
itemStack.setItemMeta(meta);
|
||||||
return itemStack;
|
return itemStack;
|
||||||
@@ -120,8 +156,7 @@ public enum Skull {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a skull that has a custom texture specified by url.
|
* Return a skull that has a custom texture specified by url.
|
||||||
*
|
* @param url skin url.
|
||||||
* @param url skin url
|
|
||||||
* @return item stack
|
* @return item stack
|
||||||
*/
|
*/
|
||||||
public static ItemStack getFromSkinURL(String url) {
|
public static ItemStack getFromSkinURL(String url) {
|
||||||
@@ -131,11 +166,13 @@ public enum Skull {
|
|||||||
/**
|
/**
|
||||||
* Return a skull that has a custom texture specified by url.
|
* Return a skull that has a custom texture specified by url.
|
||||||
*
|
*
|
||||||
* @param url the skin full url
|
* @param url the skin full url.
|
||||||
|
* @param displayName the display name to add to the returned skull.
|
||||||
|
* @param lore the lore to add to the returned skull.
|
||||||
* @return item stack
|
* @return item stack
|
||||||
*/
|
*/
|
||||||
public static ItemStack getFromSkinURL(String url, Chat name, List<Chat> lore) {
|
public static ItemStack getFromSkinURL(String url, Chat displayName, List<Chat> lore) {
|
||||||
return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", url).getBytes()), name, lore);
|
return getFromBase64String(Base64.getEncoder().encodeToString(String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", url).getBytes()), displayName, lore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -148,7 +185,7 @@ public enum Skull {
|
|||||||
/**
|
/**
|
||||||
* Return a skull that has a custom texture specified by a base64 String.
|
* Return a skull that has a custom texture specified by a base64 String.
|
||||||
*
|
*
|
||||||
* @param str the base64 string from game profile information
|
* @param str the base64 string from game profile information.
|
||||||
* @return item stack
|
* @return item stack
|
||||||
*/
|
*/
|
||||||
public static ItemStack getFromBase64String(String str) {
|
public static ItemStack getFromBase64String(String str) {
|
||||||
@@ -159,7 +196,9 @@ public enum Skull {
|
|||||||
/**
|
/**
|
||||||
* Return a skull that has a custom texture specified by a base64 String.
|
* Return a skull that has a custom texture specified by a base64 String.
|
||||||
*
|
*
|
||||||
* @param str the base64 string from game profile information
|
* @param str the base64 string from game profile information.
|
||||||
|
* @param displayName the display name to add to the returned skull.
|
||||||
|
* @param lore the lore to add to the returned skull.
|
||||||
* @return item stack
|
* @return item stack
|
||||||
*/
|
*/
|
||||||
public static ItemStack getFromBase64String(String str, Chat displayName, List<Chat> lore) {
|
public static ItemStack getFromBase64String(String str, Chat displayName, List<Chat> lore) {
|
||||||
@@ -172,10 +211,10 @@ public enum Skull {
|
|||||||
headMeta.setPlayerProfile(profile);
|
headMeta.setPlayerProfile(profile);
|
||||||
|
|
||||||
if (displayName != null)
|
if (displayName != null)
|
||||||
headMeta.displayName(displayName.getAdv());
|
headMeta.displayName(displayName.get());
|
||||||
|
|
||||||
if (lore != null)
|
if (lore != null)
|
||||||
headMeta.lore(lore.stream().map(Chat::getAdv).collect(Collectors.toList()));
|
headMeta.lore(lore.stream().map(Chat::get).collect(Collectors.toList()));
|
||||||
|
|
||||||
head.setItemMeta(headMeta);
|
head.setItemMeta(headMeta);
|
||||||
|
|
||||||
|
@@ -5,8 +5,17 @@ import org.bukkit.World;
|
|||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a XZ chunk coordinates.
|
||||||
|
* @param x the x coordinate.
|
||||||
|
* @param z the z coordinate.
|
||||||
|
*/
|
||||||
public record ChunkCoord(int x, int z) {
|
public record ChunkCoord(int x, int z) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the {@link ChunkCoord} of a {@link Chunk}.
|
||||||
|
* @param c the chunks from which to get its coordinates.
|
||||||
|
*/
|
||||||
public ChunkCoord(Chunk c) {
|
public ChunkCoord(Chunk c) {
|
||||||
this(c.getX(), c.getZ());
|
this(c.getX(), c.getZ());
|
||||||
}
|
}
|
||||||
@@ -16,22 +25,48 @@ public record ChunkCoord(int x, int z) {
|
|||||||
return "(" + x + ";" + z + ")";
|
return "(" + x + ";" + z + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the coordinates of the region file.
|
||||||
|
* @return the {@link RegionCoord}.
|
||||||
|
*/
|
||||||
public RegionCoord getRegionCoord() {
|
public RegionCoord getRegionCoord() {
|
||||||
return new RegionCoord(x >> 5, z >> 5);
|
return new RegionCoord(x >> 5, z >> 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link Chunk} at this coordinate in the provided World.
|
||||||
|
* @param w the {@link World}.
|
||||||
|
* @return a chunk, using {@link World#getChunkAt(int, int)}.
|
||||||
|
*/
|
||||||
public Chunk getChunk(World w) {
|
public Chunk getChunk(World w) {
|
||||||
return w.getChunkAt(x, z);
|
return w.getChunkAt(x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link Chunk} at this coordinate in the provided World.
|
||||||
|
* @param w the {@link World}.
|
||||||
|
* @param generate Whether the chunk should be fully generated or not.
|
||||||
|
* @return a chunk, using {@link World#getChunkAt(int, int, boolean)}.
|
||||||
|
*/
|
||||||
public Chunk getChunk(World w, boolean generate) {
|
public Chunk getChunk(World w, boolean generate) {
|
||||||
return w.getChunkAt(x, z, generate);
|
return w.getChunkAt(x, z, generate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link Chunk} at this coordinate in the provided World, asynchronously.
|
||||||
|
* @param w the {@link World}.
|
||||||
|
* @return a completable future of a chunk, using {@link World#getChunkAtAsync(int, int)}.
|
||||||
|
*/
|
||||||
public CompletableFuture<Chunk> getChunkAsync(World w) {
|
public CompletableFuture<Chunk> getChunkAsync(World w) {
|
||||||
return w.getChunkAtAsync(x, z);
|
return w.getChunkAtAsync(x, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link Chunk} at this coordinate in the provided World, asynchronously.
|
||||||
|
* @param w the {@link World}.
|
||||||
|
* @param generate Whether the chunk should be fully generated or not.
|
||||||
|
* @return a completable future of a chunk, using {@link World#getChunkAtAsync(int, int, boolean)}.
|
||||||
|
*/
|
||||||
public CompletableFuture<Chunk> getChunkAsync(World w, boolean generate) {
|
public CompletableFuture<Chunk> getChunkAsync(World w, boolean generate) {
|
||||||
return w.getChunkAtAsync(x, z, generate);
|
return w.getChunkAtAsync(x, z, generate);
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.lib.paper.reflect.util;
|
package fr.pandacube.lib.paper.world;
|
||||||
|
|
||||||
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer;
|
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer;
|
||||||
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
|
||||||
@@ -36,5 +36,5 @@ public class PrimaryWorlds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private PrimaryWorlds() {}
|
||||||
}
|
}
|
@@ -3,20 +3,42 @@ package fr.pandacube.lib.paper.world;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a XZ region coordinates.
|
||||||
|
* @param x the x coordinate.
|
||||||
|
* @param z the z coordinate.
|
||||||
|
*/
|
||||||
public record RegionCoord(int x, int z) {
|
public record RegionCoord(int x, int z) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="https://en.wikipedia.org/wiki/Chebyshev_distance">Chebyshev distance</a> from this region to the center (0, 0) region.
|
||||||
|
* The unit is one region, i.e. the region (1, 0) has a distance of 1 from the center.
|
||||||
|
* @return the distance.
|
||||||
|
*/
|
||||||
public int distToCenter() {
|
public int distToCenter() {
|
||||||
return Math.max(Math.abs(x), Math.abs(z));
|
return Math.max(Math.abs(x), Math.abs(z));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the file name that store this region.
|
||||||
|
* @return the region file name.
|
||||||
|
*/
|
||||||
public String getFileName() {
|
public String getFileName() {
|
||||||
return "r." + x + "." + z + ".mca";
|
return "r." + x + "." + z + ".mca";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the chunk with the lowest coordinates of this region.
|
||||||
|
* @return the chunk with the lowest coordinates of this region.
|
||||||
|
*/
|
||||||
public ChunkCoord getMinChunk() {
|
public ChunkCoord getMinChunk() {
|
||||||
return new ChunkCoord(x << 5, z << 5);
|
return new ChunkCoord(x << 5, z << 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the chunk with the highest coordinates of this region.
|
||||||
|
* @return the chunk with the highest coordinates of this region.
|
||||||
|
*/
|
||||||
public ChunkCoord getMaxChunk() {
|
public ChunkCoord getMaxChunk() {
|
||||||
return new ChunkCoord(x << 5 | 31, z << 5 | 31);
|
return new ChunkCoord(x << 5 | 31, z << 5 | 31);
|
||||||
}
|
}
|
||||||
@@ -25,6 +47,12 @@ public record RegionCoord(int x, int z) {
|
|||||||
|
|
||||||
private static final Pattern REGION_FILE_NAME_PATTERN = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
|
private static final Pattern REGION_FILE_NAME_PATTERN = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the coordinate from a region file name.
|
||||||
|
* @param name the name of the file to parse
|
||||||
|
* @return a new {@link RegionCoord}
|
||||||
|
* @throws IllegalArgumentException if the provided file name is not a valid MCA file name (including the extension).
|
||||||
|
*/
|
||||||
public static RegionCoord fromFileName(String name) {
|
public static RegionCoord fromFileName(String name) {
|
||||||
Matcher m = REGION_FILE_NAME_PATTERN.matcher(name);
|
Matcher m = REGION_FILE_NAME_PATTERN.matcher(name);
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
|
@@ -0,0 +1,145 @@
|
|||||||
|
package fr.pandacube.lib.paper.world;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.util.FileUtils;
|
||||||
|
import fr.pandacube.lib.util.RandomUtil;
|
||||||
|
import fr.pandacube.lib.util.log.Log;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.World.Environment;
|
||||||
|
import org.bukkit.WorldCreator;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles worlds that are loaded temporarily and based on a template. Useful for mini-game servers.
|
||||||
|
*/
|
||||||
|
public class TemplatedWorldHandler implements Listener {
|
||||||
|
|
||||||
|
private static final Map<String, World> loadedWorlds = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a world based on the provided template world.
|
||||||
|
* <p>
|
||||||
|
* If a world based on the template is already loaded, it will be returned.
|
||||||
|
* If there is no world loaded based on the template, the template world will be copied with a temporary name, and
|
||||||
|
* loaded like a regular world with {@link Bukkit#createWorld(WorldCreator)}.
|
||||||
|
* Only one copy per template can be loaded per server instance.
|
||||||
|
* @param templateWorld the template name of the world, that is the name of the original world's folder.
|
||||||
|
* @param operationOnLoad an optional consumer executed if a world is loaded (it is ignored if a copy of the template is already loaded).
|
||||||
|
* @return a World instance based on a copy of the provided world.
|
||||||
|
* @throws IOException if an error occurs while loading a world.
|
||||||
|
*/
|
||||||
|
public static World getOrLoadWorld(String templateWorld, Consumer<World> operationOnLoad) throws IOException {
|
||||||
|
if (loadedWorlds.containsKey(templateWorld)) {
|
||||||
|
return loadedWorlds.get(templateWorld);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return loadGameWorld(templateWorld, operationOnLoad);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Log.severe(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the already-loaded world based on the provided template world.
|
||||||
|
* @param templateWorld the template name of the world, that is the name of the original world's folder.
|
||||||
|
* @return a World instance based on a copy of the provided world, or null if there is none loaded yet.
|
||||||
|
*/
|
||||||
|
public static World getWorldIfLoaded(String templateWorld) {
|
||||||
|
if (loadedWorlds.containsKey(templateWorld)) {
|
||||||
|
return loadedWorlds.get(templateWorld);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unload the world based on the provided template world.
|
||||||
|
* Do nothing if there is no loaded wold based on the provided template.
|
||||||
|
* After unloading using {@link Bukkit#unloadWorld(World, boolean)}, the copy of the template is deleted.
|
||||||
|
* @param templateWorld the template name of the world, that is the name of the original world's folder.
|
||||||
|
* @return true if the world was unloaded successfully (or there were no world to unload), false if the unloading
|
||||||
|
* failed ({@link Bukkit#unloadWorld(World, boolean)} returned false).
|
||||||
|
*/
|
||||||
|
public static boolean unloadWorld(String templateWorld) {
|
||||||
|
if (loadedWorlds.containsKey(templateWorld)) {
|
||||||
|
World rem = loadedWorlds.remove(templateWorld);
|
||||||
|
String copiedName = rem.getName();
|
||||||
|
boolean ret = Bukkit.unloadWorld(rem, false);
|
||||||
|
if (ret)
|
||||||
|
FileUtils.delete(new File(Bukkit.getWorldContainer(), copiedName));
|
||||||
|
else
|
||||||
|
Log.warning("Unable to unload game world " + copiedName + " for some reason.");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unload all the templated worlds, using {@link #unloadWorld(String)}.
|
||||||
|
*/
|
||||||
|
public static void unloadUnusedWorlds() {
|
||||||
|
for (String world : new ArrayList<>(loadedWorlds.keySet())) {
|
||||||
|
World rem = loadedWorlds.get(world);
|
||||||
|
if (rem.getPlayers().stream().noneMatch(Player::isOnline)) {
|
||||||
|
unloadWorld(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided template world has a currently loaded copy.
|
||||||
|
* @param templateWorld the template name of the world, that is the name of the original world's folder.
|
||||||
|
* @return true if the world is loaded.
|
||||||
|
*/
|
||||||
|
public static boolean isWorldLoaded(String templateWorld) {
|
||||||
|
return loadedWorlds.containsKey(templateWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static World loadGameWorld(String world, Consumer<World> operationOnLoad) throws IOException {
|
||||||
|
if (loadedWorlds.containsKey(world))
|
||||||
|
throw new IllegalStateException("GameWorld '"+world+"' is already loaded.");
|
||||||
|
|
||||||
|
if (!new File(Bukkit.getWorldContainer(), world).isDirectory())
|
||||||
|
throw new IllegalStateException("GameWorld '"+world+"' does not exist");
|
||||||
|
|
||||||
|
String copiedName = world + "_gen" + RandomUtil.rand.nextInt(100000, 999999);
|
||||||
|
|
||||||
|
File srcDir = new File(Bukkit.getWorldContainer(), world);
|
||||||
|
File destDir = new File(Bukkit.getWorldContainer(), copiedName);
|
||||||
|
FileUtils.delete(destDir);
|
||||||
|
FileUtils.copy(srcDir, destDir);
|
||||||
|
new File(destDir, "session.lock").delete();
|
||||||
|
new File(destDir, "uid.dat").delete();
|
||||||
|
|
||||||
|
World w = Bukkit.createWorld(new WorldCreator(copiedName).environment(Environment.NORMAL));
|
||||||
|
if (w == null) {
|
||||||
|
throw new RuntimeException("Unable to create the world " + copiedName + ": Bukkit.createWorld(...) returned null value.");
|
||||||
|
}
|
||||||
|
w.setAutoSave(false);
|
||||||
|
loadedWorlds.put(world, w);
|
||||||
|
if (Bukkit.getPluginManager().getPlugin("Multiverse-Core") != null)
|
||||||
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "mvm set hidden true "+copiedName);
|
||||||
|
operationOnLoad.accept(w);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private TemplatedWorldHandler() {}
|
||||||
|
|
||||||
|
}
|
@@ -10,11 +10,18 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides utility methods to manage and analyze world's directory.
|
||||||
|
*/
|
||||||
public class WorldUtil {
|
public class WorldUtil {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the {@link Environment} of the provided world.
|
||||||
|
* @param world the name of the world.
|
||||||
|
* @return the {@link Environment}.
|
||||||
|
* @throws IllegalStateException if the provided world is not valid (cannot determine its environment)
|
||||||
|
*/
|
||||||
public static Environment determineEnvironment(String world) {
|
public static Environment determineEnvironment(String world) {
|
||||||
World bWorld = Bukkit.getWorld(world);
|
World bWorld = Bukkit.getWorld(world);
|
||||||
if (bWorld != null) {
|
if (bWorld != null) {
|
||||||
@@ -40,7 +47,12 @@ public class WorldUtil {
|
|||||||
|
|
||||||
throw new IllegalStateException("Unable to determine the type of the world " + world + ".");
|
throw new IllegalStateException("Unable to determine the type of the world " + world + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of all the regions of the provided world, based on the name of the region files.
|
||||||
|
* @param world the world.
|
||||||
|
* @return a {@link List} of {@link RegionCoord}.
|
||||||
|
*/
|
||||||
public static List<RegionCoord> getExistingRegions(String world) {
|
public static List<RegionCoord> getExistingRegions(String world) {
|
||||||
Environment env = determineEnvironment(world);
|
Environment env = determineEnvironment(world);
|
||||||
|
|
||||||
@@ -71,11 +83,22 @@ public class WorldUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final List<String> REGION_DATA_FILES = Arrays.asList("entities", "poi", "region", "DIM-1", "DIM1");
|
private static final List<String> REGION_DATA_FILES = Arrays.asList("entities", "poi", "region", "DIM-1", "DIM1");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the directory containing region related data (entities, poi, region, DIM*) of the provided world.
|
||||||
|
* @param world the world.
|
||||||
|
* @return a {@link List} of directory.
|
||||||
|
*/
|
||||||
public static List<File> regionDataFolders(String world) {
|
public static List<File> regionDataFolders(String world) {
|
||||||
return onlyExisting(worldDir(world), REGION_DATA_FILES);
|
return onlyExisting(worldDir(world), REGION_DATA_FILES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of all map related data files.
|
||||||
|
* That is the file {@code data/idcounts.dat} and all the files with the pattern {@code data/map_*.dat}.
|
||||||
|
* @param world the world.
|
||||||
|
* @return the list of all map related data files
|
||||||
|
*/
|
||||||
public static List<File> mapFiles(String world) {
|
public static List<File> mapFiles(String world) {
|
||||||
Pattern mapFilePattern = Pattern.compile("map_\\d+.dat");
|
Pattern mapFilePattern = Pattern.compile("map_\\d+.dat");
|
||||||
return List.of(dataDir(world).listFiles((dir, name) -> mapFilePattern.matcher(name).find() || "idcounts.dat".equals(name)));
|
return List.of(dataDir(world).listFiles((dir, name) -> mapFilePattern.matcher(name).find() || "idcounts.dat".equals(name)));
|
||||||
@@ -88,18 +111,35 @@ public class WorldUtil {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the directory of the provided world.
|
||||||
|
* @param world the world.
|
||||||
|
* @return the directory of the world.
|
||||||
|
*/
|
||||||
public static File worldDir(String world) {
|
public static File worldDir(String world) {
|
||||||
return new File(Bukkit.getWorldContainer(), world);
|
return new File(Bukkit.getWorldContainer(), world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the data directory of the provided world.
|
||||||
|
* @param world the world.
|
||||||
|
* @return the data directory of the world.
|
||||||
|
*/
|
||||||
public static File dataDir(String world) {
|
public static File dataDir(String world) {
|
||||||
return new File(worldDir(world), "data");
|
return new File(worldDir(world), "data");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the provided world is a valid one (has a directory and contains a file named level.dat).
|
||||||
|
* @param world the world.
|
||||||
|
* @return true if the world exists, false otherwise.
|
||||||
|
*/
|
||||||
public static boolean isValidWorld(String world) {
|
public static boolean isValidWorld(String world) {
|
||||||
File d = worldDir(world);
|
File d = worldDir(world);
|
||||||
return d.isDirectory() && new File(d, "level.dat").isFile();
|
return d.isDirectory() && new File(d, "level.dat").isFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private WorldUtil() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,12 @@
|
|||||||
<artifactId>javaluator</artifactId>
|
<artifactId>javaluator</artifactId>
|
||||||
<version>3.0.3</version>
|
<version>3.0.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>${guava.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@@ -1,5 +1,18 @@
|
|||||||
package fr.pandacube.lib.permissions;
|
package fr.pandacube.lib.permissions;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import fr.pandacube.lib.chat.Chat;
|
||||||
|
import fr.pandacube.lib.chat.ChatTreeNode;
|
||||||
|
import fr.pandacube.lib.chat.LegacyChatFormat;
|
||||||
|
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedEntity;
|
||||||
|
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedGroup;
|
||||||
|
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedPlayer;
|
||||||
|
import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
|
||||||
|
import fr.pandacube.lib.util.log.Log;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -12,19 +25,6 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.chat.Chat;
|
|
||||||
import fr.pandacube.lib.chat.ChatTreeNode;
|
|
||||||
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedEntity;
|
|
||||||
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedGroup;
|
|
||||||
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedPlayer;
|
|
||||||
import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
|
|
||||||
import fr.pandacube.lib.util.log.Log;
|
|
||||||
|
|
||||||
/* package */ class PermissionsResolver {
|
/* package */ class PermissionsResolver {
|
||||||
|
|
||||||
private final PermissionsCachedBackendReader backendReader;
|
private final PermissionsCachedBackendReader backendReader;
|
||||||
@@ -105,7 +105,7 @@ import fr.pandacube.lib.util.log.Log;
|
|||||||
Log.warning("For data " + dataType + ":\n"
|
Log.warning("For data " + dataType + ":\n"
|
||||||
+ resolutionResult.toDisplayTreeNode().render(true).stream()
|
+ resolutionResult.toDisplayTreeNode().render(true).stream()
|
||||||
.map(Chat::getLegacyText)
|
.map(Chat::getLegacyText)
|
||||||
.collect(Collectors.joining(ChatColor.RESET + "\n")));
|
.collect(Collectors.joining(LegacyChatFormat.RESET + "\n")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolutionResult.result != null ? resolutionResult.result : "";
|
return resolutionResult.result != null ? resolutionResult.result : "";
|
||||||
@@ -167,7 +167,7 @@ import fr.pandacube.lib.util.log.Log;
|
|||||||
if (result == null)
|
if (result == null)
|
||||||
c.then(Chat.text(" (non défini)").gray());
|
c.then(Chat.text(" (non défini)").gray());
|
||||||
else
|
else
|
||||||
c.thenLegacyText(" \"" + ChatColor.RESET + result + ChatColor.RESET + "\"");
|
c.thenLegacyText(" \"" + LegacyChatFormat.RESET + result + LegacyChatFormat.RESET + "\"");
|
||||||
if (conflictMessage != null)
|
if (conflictMessage != null)
|
||||||
c.thenFailure(" " + conflictMessage);
|
c.thenFailure(" " + conflictMessage);
|
||||||
ChatTreeNode node = new ChatTreeNode(c);
|
ChatTreeNode node = new ChatTreeNode(c);
|
||||||
@@ -307,7 +307,7 @@ import fr.pandacube.lib.util.log.Log;
|
|||||||
Log.warning("For permission " + permission + ":\n"
|
Log.warning("For permission " + permission + ":\n"
|
||||||
+ resolutionResult.toDisplayTreeNode().render(true).stream()
|
+ resolutionResult.toDisplayTreeNode().render(true).stream()
|
||||||
.map(Chat::getLegacyText)
|
.map(Chat::getLegacyText)
|
||||||
.collect(Collectors.joining(ChatColor.RESET + "\n")));
|
.collect(Collectors.joining(LegacyChatFormat.RESET + "\n")));
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolutionResult.result;
|
return resolutionResult.result;
|
||||||
@@ -487,7 +487,7 @@ import fr.pandacube.lib.util.log.Log;
|
|||||||
Chat c = Chat.chat()
|
Chat c = Chat.chat()
|
||||||
.then(result == PermState.UNDEFINED ? Chat.dataText("■") : result == PermState.GRANTED ? Chat.successText("✔") : Chat.failureText("✘"))
|
.then(result == PermState.UNDEFINED ? Chat.dataText("■") : result == PermState.GRANTED ? Chat.successText("✔") : Chat.failureText("✘"))
|
||||||
.then(Chat.text(entity instanceof CachedPlayer cp ? Permissions.playerNameGetter.apply(cp.playerId) : entity.name)
|
.then(Chat.text(entity instanceof CachedPlayer cp ? Permissions.playerNameGetter.apply(cp.playerId) : entity.name)
|
||||||
.color(entity instanceof CachedPlayer ? ChatColor.GOLD : ChatColor.DARK_AQUA)
|
.color(entity instanceof CachedPlayer ? NamedTextColor.GOLD : NamedTextColor.DARK_AQUA)
|
||||||
);
|
);
|
||||||
if (server != null)
|
if (server != null)
|
||||||
c.thenData(" s=" + server);
|
c.thenData(" s=" + server);
|
||||||
@@ -523,7 +523,7 @@ import fr.pandacube.lib.util.log.Log;
|
|||||||
public ChatTreeNode toDisplayTreeNode() {
|
public ChatTreeNode toDisplayTreeNode() {
|
||||||
return new ChatTreeNode(Chat.chat()
|
return new ChatTreeNode(Chat.chat()
|
||||||
.then(result ? Chat.successText("✔") : Chat.failureText("✘"))
|
.then(result ? Chat.successText("✔") : Chat.failureText("✘"))
|
||||||
.then(Chat.text(permission).color(type == PermType.WILDCARD ? ChatColor.YELLOW : type == PermType.SPECIAL ? ChatColor.LIGHT_PURPLE : ChatColor.WHITE)));
|
.then(Chat.text(permission).color(type == PermType.WILDCARD ? NamedTextColor.YELLOW : type == PermType.SPECIAL ? NamedTextColor.LIGHT_PURPLE : NamedTextColor.WHITE)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,12 +21,11 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- <dependency>
|
<dependency>
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>pandalib-util</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${guava.version}</version>
|
||||||
<scope>provided</scope>
|
</dependency>
|
||||||
</dependency> -->
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@@ -25,9 +25,9 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>org.plumelib</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>hashmap-util</artifactId>
|
||||||
<version>32.1.2-jre</version> <!-- Match the version imported by Paper API/BungeeCord API if possible -->
|
<version>0.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
<artifactSet>
|
<artifactSet>
|
||||||
<includes>
|
<includes>
|
||||||
<include>io.github.classgraph:classgraph</include>
|
<include>io.github.classgraph:classgraph</include>
|
||||||
|
<include>org.plumelib:*</include>
|
||||||
</includes>
|
</includes>
|
||||||
</artifactSet>
|
</artifactSet>
|
||||||
<filters>
|
<filters>
|
||||||
@@ -60,6 +61,26 @@
|
|||||||
<exclude>META-INF/MANIFEST.MF</exclude>
|
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</filter>
|
</filter>
|
||||||
|
<filter>
|
||||||
|
<artifact>org.plumelib:hashmap-util</artifact>
|
||||||
|
<excludes>
|
||||||
|
<exclude>module-info.class</exclude>
|
||||||
|
<exclude>META-INF/*.SF</exclude>
|
||||||
|
<exclude>META-INF/*.DSA</exclude>
|
||||||
|
<exclude>META-INF/*.RSA</exclude>
|
||||||
|
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||||
|
</excludes>
|
||||||
|
</filter>
|
||||||
|
<filter>
|
||||||
|
<artifact>org.plumelib:reflection-util</artifact>
|
||||||
|
<excludes>
|
||||||
|
<exclude>module-info.class</exclude>
|
||||||
|
<exclude>META-INF/*.SF</exclude>
|
||||||
|
<exclude>META-INF/*.DSA</exclude>
|
||||||
|
<exclude>META-INF/*.RSA</exclude>
|
||||||
|
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||||
|
</excludes>
|
||||||
|
</filter>
|
||||||
</filters>
|
</filters>
|
||||||
<relocations>
|
<relocations>
|
||||||
<relocation>
|
<relocation>
|
||||||
@@ -70,6 +91,10 @@
|
|||||||
<pattern>nonapi.io.github.classgraph</pattern>
|
<pattern>nonapi.io.github.classgraph</pattern>
|
||||||
<shadedPattern>fr.pandacube.lib.reflect.shaded.classgraph.nonapi</shadedPattern>
|
<shadedPattern>fr.pandacube.lib.reflect.shaded.classgraph.nonapi</shadedPattern>
|
||||||
</relocation>
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.plumelib</pattern>
|
||||||
|
<shadedPattern>fr.pandacube.lib.reflect.shaded.plumelib</shadedPattern>
|
||||||
|
</relocation>
|
||||||
</relocations>
|
</relocations>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
@@ -9,7 +9,7 @@ import java.lang.reflect.Modifier;
|
|||||||
*/
|
*/
|
||||||
public final class ReflectField<T> extends ReflectMember<T, String, Field, NoSuchFieldException> {
|
public final class ReflectField<T> extends ReflectMember<T, String, Field, NoSuchFieldException> {
|
||||||
|
|
||||||
/* Those fields are used to modify the value of a static variable. Depending on the current Java version,
|
/* Those fields are used to modify the value of a final variable. Depending on the current Java version,
|
||||||
* one of them will be used for this purpose.
|
* one of them will be used for this purpose.
|
||||||
*/
|
*/
|
||||||
private static sun.misc.Unsafe sunMiscUnsafeInstance;
|
private static sun.misc.Unsafe sunMiscUnsafeInstance;
|
||||||
@@ -122,13 +122,30 @@ public final class ReflectField<T> extends ReflectMember<T, String, Field, NoSuc
|
|||||||
// if the field is final, we have to do some unsafe stuff :/
|
// if the field is final, we have to do some unsafe stuff :/
|
||||||
if (sunMiscUnsafeInstance != null) { // Java >= 16
|
if (sunMiscUnsafeInstance != null) { // Java >= 16
|
||||||
// set the value of the field, directly in the memory
|
// set the value of the field, directly in the memory
|
||||||
if (Modifier.isStatic(realModifiers)) {
|
Object unsafeObjInstance = Modifier.isStatic(realModifiers)
|
||||||
long offset = sunMiscUnsafeInstance.staticFieldOffset(f);
|
? sunMiscUnsafeInstance.staticFieldBase(f)
|
||||||
sunMiscUnsafeInstance.putObject(sunMiscUnsafeInstance.staticFieldBase(f), offset, value);
|
: instance;
|
||||||
} else {
|
long offset = Modifier.isStatic(realModifiers)
|
||||||
long offset = sunMiscUnsafeInstance.objectFieldOffset(f);
|
? sunMiscUnsafeInstance.staticFieldOffset(f)
|
||||||
sunMiscUnsafeInstance.putObject(instance, offset, value);
|
: sunMiscUnsafeInstance.objectFieldOffset(f);
|
||||||
}
|
if (char.class.isAssignableFrom(f.getType()))
|
||||||
|
sunMiscUnsafeInstance.putChar(unsafeObjInstance, offset, (char)value);
|
||||||
|
else if (byte.class.isAssignableFrom(f.getType()))
|
||||||
|
sunMiscUnsafeInstance.putByte(unsafeObjInstance, offset, (byte)value);
|
||||||
|
else if (short.class.isAssignableFrom(f.getType()))
|
||||||
|
sunMiscUnsafeInstance.putShort(unsafeObjInstance, offset, (short)value);
|
||||||
|
else if (int.class.isAssignableFrom(f.getType()))
|
||||||
|
sunMiscUnsafeInstance.putInt(unsafeObjInstance, offset, (int)value);
|
||||||
|
else if (long.class.isAssignableFrom(f.getType()))
|
||||||
|
sunMiscUnsafeInstance.putLong(unsafeObjInstance, offset, (long)value);
|
||||||
|
else if (boolean.class.isAssignableFrom(f.getType()))
|
||||||
|
sunMiscUnsafeInstance.putBoolean(unsafeObjInstance, offset, (boolean)value);
|
||||||
|
else if (float.class.isAssignableFrom(f.getType()))
|
||||||
|
sunMiscUnsafeInstance.putFloat(unsafeObjInstance, offset, (float)value);
|
||||||
|
else if (double.class.isAssignableFrom(f.getType()))
|
||||||
|
sunMiscUnsafeInstance.putDouble(unsafeObjInstance, offset, (double)value);
|
||||||
|
else
|
||||||
|
sunMiscUnsafeInstance.putObject(unsafeObjInstance, offset, value);
|
||||||
} else { // Java < 16
|
} else { // Java < 16
|
||||||
// change the modifier in the Field instance so the method #set(instance, value) doesn't throw an exception
|
// change the modifier in the Field instance so the method #set(instance, value) doesn't throw an exception
|
||||||
try {
|
try {
|
||||||
|
@@ -50,7 +50,7 @@ public sealed abstract class ReflectMember<T, ID, EL extends AccessibleObject &
|
|||||||
setAccessible(el);
|
setAccessible(el);
|
||||||
return el;
|
return el;
|
||||||
} catch (ReflectiveOperationException e2) {
|
} catch (ReflectiveOperationException e2) {
|
||||||
ex.addSuppressed(e2);
|
//ex.addSuppressed(e2);
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,15 +16,27 @@ public class ReflectionWrapperBypass {
|
|||||||
|
|
||||||
private static final AtomicBoolean enabled = new AtomicBoolean(false);
|
private static final AtomicBoolean enabled = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables bypassing the eventual translation of the class names when using {@link Class#forName(String)}.
|
||||||
|
*/
|
||||||
public static void enable() {
|
public static void enable() {
|
||||||
enabled.set(true);
|
enabled.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables bypassing the eventual translation of the class names when using {@link Class#forName(String)}.
|
||||||
|
*/
|
||||||
public static void disable() {
|
public static void disable() {
|
||||||
enabled.set(false);
|
enabled.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@link Class#forName(String)}, but detects if the returned class has been translated and if so, uses
|
||||||
|
* reflection to call {@link Class#forName(String)} (some sort of reflection-ception).
|
||||||
|
* @param className the binary name of the class or the string representing an array type.
|
||||||
|
* @return the {@link Class} object for the class with the specified name.
|
||||||
|
* @throws ClassNotFoundException if the class cannot be located.
|
||||||
|
*/
|
||||||
public static Class<?> getClass(String className) throws ClassNotFoundException {
|
public static Class<?> getClass(String className) throws ClassNotFoundException {
|
||||||
if (!enabled.get()) {
|
if (!enabled.get()) {
|
||||||
return Class.forName(className);
|
return Class.forName(className);
|
||||||
@@ -51,4 +63,8 @@ public class ReflectionWrapperBypass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ReflectionWrapperBypass() {}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
package fr.pandacube.lib.reflect.wrapper;
|
package fr.pandacube.lib.reflect.wrapper;
|
||||||
|
|
||||||
import com.google.common.collect.MapMaker;
|
|
||||||
|
|
||||||
import fr.pandacube.lib.reflect.ReflectConstructor;
|
import fr.pandacube.lib.reflect.ReflectConstructor;
|
||||||
|
import org.plumelib.util.WeakIdentityHashMap;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -17,7 +16,10 @@ import static fr.pandacube.lib.util.ThrowableUtil.wrapEx;
|
|||||||
public abstract class ReflectWrapper implements ReflectWrapperI {
|
public abstract class ReflectWrapper implements ReflectWrapperI {
|
||||||
|
|
||||||
|
|
||||||
private static final Map<Object, ReflectWrapperI> objectWrapperCache = new MapMaker().weakKeys().makeMap();
|
private static final Map<Object, ReflectWrapperI> objectWrapperCache = new WeakIdentityHashMap<>();
|
||||||
|
/*
|
||||||
|
* WeakHashMap is not suitable because we want the keys to be compared by reference (==) and not by the .equals() method.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unwraps the object from the provided reflect wrapper.
|
* Unwraps the object from the provided reflect wrapper.
|
||||||
|
@@ -1,394 +0,0 @@
|
|||||||
package fr.pandacube.lib.util;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enumeration of all known, post Netty-rewrite (1.7.2+), stable Minecraft Java versions.
|
|
||||||
* <p>
|
|
||||||
* It provides various utility methods to nicely display a set of Minecraft version (for instance "1.13.x",
|
|
||||||
* "1.16-1.16.3", "1.8.x and 1.9", "1.18.1 or 1.18.2")
|
|
||||||
* <p>
|
|
||||||
* Note that this enum uses one value to represent every Minecraft version using the same protocol version number.
|
|
||||||
* @deprecated This class may not be updated. Use the class ProtocolVersion in pandalib-core module instead.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true)
|
|
||||||
public enum MinecraftVersion {
|
|
||||||
/** Minecraft versions 1.7.2 to 1.7.5, protocol version 4. */
|
|
||||||
v1_7_2_to_1_7_5(4, "1.7.2", "1.7.3", "1.7.4", "1.7.5"),
|
|
||||||
/** Minecraft versions 1.7.6 to 1.7.10, protocol version 5. */
|
|
||||||
v1_7_6_to_1_7_10(5, "1.7.6", "1.7.7", "1.7.8", "1.7.9", "1.7.10"),
|
|
||||||
|
|
||||||
/** Minecraft versions 1.8.x, protocol version 47. */
|
|
||||||
v1_8(47, "1.8", "1.8.1", "1.8.2", "1.8.3", "1.8.4", "1.8.5", "1.8.6", "1.8.7", "1.8.8", "1.8.9"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.9, protocol version 107. */
|
|
||||||
v1_9(107, "1.9"),
|
|
||||||
/** Minecraft version 1.9.1, protocol version 108. */
|
|
||||||
v1_9_1(108, "1.9.1"),
|
|
||||||
/** Minecraft version 1.9.2, protocol version 109. */
|
|
||||||
v1_9_2(109, "1.9.2"),
|
|
||||||
/** Minecraft versions 1.9.3 and 1.9.4, protocol version 110. */
|
|
||||||
v1_9_3_to_1_9_4(110, "1.9.3", "1.9.4"),
|
|
||||||
|
|
||||||
/** Minecraft versions 1.10.x, protocol version 210. */
|
|
||||||
v1_10(210, "1.10", "1.10.1", "1.10.2"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.11, protocol version 315. */
|
|
||||||
v1_11(315, "1.11"),
|
|
||||||
/** Minecraft versions 1.11.1 and 1.11.2, protocol version 316. */
|
|
||||||
v1_11_1_to_1_11_2(316, "1.11.1", "1.11.2"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.12, protocol version 335. */
|
|
||||||
v1_12(335, "1.12"),
|
|
||||||
/** Minecraft version 1.12.1, protocol version 338. */
|
|
||||||
v1_12_1(338, "1.12.1"),
|
|
||||||
/** Minecraft version 1.12.2, protocol version 340. */
|
|
||||||
v1_12_2(340, "1.12.2"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.13, protocol version 393. */
|
|
||||||
v1_13(393, "1.13"),
|
|
||||||
/** Minecraft version 1.13.1, protocol version 401. */
|
|
||||||
v1_13_1(401, "1.13.1"),
|
|
||||||
/** Minecraft version 1.13.2, protocol version 404. */
|
|
||||||
v1_13_2(404, "1.13.2"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.14, protocol version 477. */
|
|
||||||
v1_14(477, "1.14"),
|
|
||||||
/** Minecraft version 1.14.1, protocol version 480. */
|
|
||||||
v1_14_1(480, "1.14.1"),
|
|
||||||
/** Minecraft version 1.14.2, protocol version 485. */
|
|
||||||
v1_14_2(485, "1.14.2"),
|
|
||||||
/** Minecraft version 1.14.3, protocol version 490. */
|
|
||||||
v1_14_3(490, "1.14.3"),
|
|
||||||
/** Minecraft version 1.14.4, protocol version 498. */
|
|
||||||
v1_14_4(498, "1.14.4"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.15, protocol version 573. */
|
|
||||||
v1_15(573, "1.15"),
|
|
||||||
/** Minecraft version 1.15.1, protocol version 575. */
|
|
||||||
v1_15_1(575, "1.15.1"),
|
|
||||||
/** Minecraft version 1.15.2, protocol version 578. */
|
|
||||||
v1_15_2(578, "1.15.2"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.16, protocol version 735. */
|
|
||||||
v1_16(735, "1.16"),
|
|
||||||
/** Minecraft version 1.16.1, protocol version 736. */
|
|
||||||
v1_16_1(736, "1.16.1"),
|
|
||||||
/** Minecraft version 1.16.2, protocol version 751. */
|
|
||||||
v1_16_2(751, "1.16.2"),
|
|
||||||
/** Minecraft version 1.16.3, protocol version 753. */
|
|
||||||
v1_16_3(753, "1.16.3"),
|
|
||||||
/** Minecraft versions 1.16.4 and 1.16.5, protocol version 754. */
|
|
||||||
v1_16_4_to_1_16_5(754, "1.16.4", "1.16.5"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.17, protocol version 755. */
|
|
||||||
v1_17(755, "1.17"),
|
|
||||||
/** Minecraft version 1.17.1, protocol version 756. */
|
|
||||||
v1_17_1(756, "1.17.1"),
|
|
||||||
|
|
||||||
/** Minecraft versions 1.18 and 1.18.1, protocol version 757. */
|
|
||||||
v1_18_to_1_18_1(757, "1.18", "1.18.1"),
|
|
||||||
/** Minecraft version 1.18.2, protocol version 758. */
|
|
||||||
v1_18_2(758, "1.18.2"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.19, protocol version 759. */
|
|
||||||
v1_19(759, "1.19"),
|
|
||||||
/** Minecraft versions 1.19.1 and 1.19.2, protocol version 760. */
|
|
||||||
v1_19_1_to_1_19_2(760, "1.19.1", "1.19.2"),
|
|
||||||
/** Minecraft versions 1.19.3, protocol version 761. */
|
|
||||||
v1_19_3(761, "1.19.3"),
|
|
||||||
/** Minecraft versions 1.19.4, protocol version 762. */
|
|
||||||
v1_19_4(762, "1.19.4"),
|
|
||||||
|
|
||||||
/** Minecraft version 1.20 and 1.20.1, protocol version 763. */
|
|
||||||
v1_20(763, "1.20", "1.20.1");
|
|
||||||
|
|
||||||
// IMPORTANT: don't forget to update the versionMergeDisplay value when adding a new version;
|
|
||||||
|
|
||||||
private static final Map<EnumSet<MinecraftVersion>, List<String>> versionMergeDisplay;
|
|
||||||
|
|
||||||
static {
|
|
||||||
versionMergeDisplay = new HashMap<>();
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_7_2_to_1_7_5, v1_7_6_to_1_7_10),
|
|
||||||
List.of("1.7.2-1.7.10"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1, v1_9_2, v1_9_3_to_1_9_4),
|
|
||||||
List.of("1.9.x"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1, v1_9_2),
|
|
||||||
List.of("1.9-1.9.2"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_9, v1_9_1),
|
|
||||||
List.of("1.9", "1.9.1"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_9_1, v1_9_2, v1_9_3_to_1_9_4),
|
|
||||||
List.of("1.9.1-1.9.4"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_9_1, v1_9_2),
|
|
||||||
List.of("1.9.1", "1.9.2"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_9_2, v1_9_3_to_1_9_4),
|
|
||||||
List.of("1.9.2-1.9.4"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_11, v1_11_1_to_1_11_2),
|
|
||||||
List.of("1.11.x"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_12, v1_12_1, v1_12_2),
|
|
||||||
List.of("1.12.x"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_12, v1_12_1),
|
|
||||||
List.of("1.12", "1.12.1"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_12_1, v1_12_2),
|
|
||||||
List.of("1.12.1", "1.12.2"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_13, v1_13_1, v1_13_2),
|
|
||||||
List.of("1.13.x"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_13, v1_13_1),
|
|
||||||
List.of("1.13", "1.13.1"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_13_1, v1_13_2),
|
|
||||||
List.of("1.13.1", "1.13.2"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2, v1_14_3, v1_14_4),
|
|
||||||
List.of("1.14.x"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2, v1_14_3),
|
|
||||||
List.of("1.14-1.14.3"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2, v1_14_3, v1_14_4),
|
|
||||||
List.of("1.14.1-1.14.4"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1, v1_14_2),
|
|
||||||
List.of("1.14-1.14.2"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2, v1_14_3),
|
|
||||||
List.of("1.14.1-1.14.3"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14_2, v1_14_3, v1_14_4),
|
|
||||||
List.of("1.14.2-1.14.4"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14, v1_14_1),
|
|
||||||
List.of("1.14", "1.14.1"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14_1, v1_14_2),
|
|
||||||
List.of("1.14.1", "1.14.2"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14_2, v1_14_3),
|
|
||||||
List.of("1.14.2", "1.14.3"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_14_3, v1_14_4),
|
|
||||||
List.of("1.14.3", "1.14.4"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_15, v1_15_1, v1_15_2),
|
|
||||||
List.of("1.15.x"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_15, v1_15_1),
|
|
||||||
List.of("1.15", "1.15.1"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_15_1, v1_15_2),
|
|
||||||
List.of("1.15.1", "1.15.2"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
|
|
||||||
List.of("1.16.x"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2, v1_16_3),
|
|
||||||
List.of("1.16-1.16.3"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
|
|
||||||
List.of("1.16.1-1.16.5"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1, v1_16_2),
|
|
||||||
List.of("1.16-1.16.2"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2, v1_16_3),
|
|
||||||
List.of("1.16.1-1.16.3"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3, v1_16_4_to_1_16_5),
|
|
||||||
List.of("1.16.2-1.16.5"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16, v1_16_1),
|
|
||||||
List.of("1.16", "1.16.1"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16_1, v1_16_2),
|
|
||||||
List.of("1.16.1", "1.16.2"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16_2, v1_16_3),
|
|
||||||
List.of("1.16.2", "1.16.3"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_16_3, v1_16_4_to_1_16_5),
|
|
||||||
List.of("1.16.3-1.16.5"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_17, v1_17_1),
|
|
||||||
List.of("1.17.x"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_18_to_1_18_1, v1_18_2),
|
|
||||||
List.of("1.18.x"));
|
|
||||||
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_19, v1_19_1_to_1_19_2, v1_19_3, v1_19_4),
|
|
||||||
List.of("1.19.x"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_19, v1_19_1_to_1_19_2, v1_19_3),
|
|
||||||
List.of("1.19-1.19.3"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_19_1_to_1_19_2, v1_19_3, v1_19_4),
|
|
||||||
List.of("1.19.1-1.19.4"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_19, v1_19_1_to_1_19_2),
|
|
||||||
List.of("1.19-1.19.2"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_19_1_to_1_19_2, v1_19_3),
|
|
||||||
List.of("1.19.1-1.19.3"));
|
|
||||||
versionMergeDisplay.put(EnumSet.of(v1_19_3, v1_19_4),
|
|
||||||
List.of("1.19.3-1.19.4"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The protocol version number of this Minecraft version.
|
|
||||||
*/
|
|
||||||
public final int protocolVersionNumber;
|
|
||||||
/**
|
|
||||||
* All Minecraft version supported by this protocol version number.
|
|
||||||
*/
|
|
||||||
public final List<String> versionsDisplay;
|
|
||||||
|
|
||||||
MinecraftVersion(int protocolVersionNumber, String... versionsDisplay) {
|
|
||||||
this.protocolVersionNumber = protocolVersionNumber;
|
|
||||||
this.versionsDisplay = Arrays.asList(versionsDisplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name() + "{protocol=" + protocolVersionNumber + ", toString(\"and\")=" + toString("and") + "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of all the Minecraft version of this enum value, using
|
|
||||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)}.
|
|
||||||
*
|
|
||||||
* @param finalWordSeparator the word separator between the two last versions in the returned string, like "and",
|
|
||||||
* "or" or any other word of any language. The spaces before and after are already
|
|
||||||
* concatenated.
|
|
||||||
* @return a string representation of this {@link MinecraftVersion}.
|
|
||||||
*/
|
|
||||||
public String toString(String finalWordSeparator) {
|
|
||||||
return StringUtil.joinGrammatically(", ", " " + finalWordSeparator + " ", versionsDisplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of all the Minecraft version of this enum value, using
|
|
||||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the grammatical word "et"
|
|
||||||
* ("and" in french).
|
|
||||||
*
|
|
||||||
* @return a string representation of this {@link MinecraftVersion}.
|
|
||||||
* @deprecated it uses the hardcoded French word "et" as the final word separator.
|
|
||||||
* Use {@link #displayOptimizedListOfVersions(List, String)} with "et" as the last parameter instead.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true)
|
|
||||||
public String toStringAnd() {
|
|
||||||
return toString("et");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of all the Minecraft version of this enum value, using
|
|
||||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the grammatical word "ou"
|
|
||||||
* ("or" in french).
|
|
||||||
*
|
|
||||||
* @return a string representation of this {@link MinecraftVersion}.
|
|
||||||
* @deprecated it uses the hardcoded French word "ou" as the final word separator.
|
|
||||||
* Use {@link #displayOptimizedListOfVersions(List, String)} with "ou" as the last parameter instead.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true)
|
|
||||||
public String toStringOr() {
|
|
||||||
return toString("ou");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the {@link MinecraftVersion} instance associated with the provided protocol version number.
|
|
||||||
*
|
|
||||||
* @param protocolVersionNumber the protocol version number
|
|
||||||
* @return the {@link MinecraftVersion} instance associated with the provided protocol version number, or null if
|
|
||||||
* there is none.
|
|
||||||
*/
|
|
||||||
public static MinecraftVersion getVersion(int protocolVersionNumber) {
|
|
||||||
for (MinecraftVersion mcV : values())
|
|
||||||
if (mcV.protocolVersionNumber == protocolVersionNumber) return mcV;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a string representation of the provided list of version, using
|
|
||||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)}.
|
|
||||||
*
|
|
||||||
* @param versions the minecraft versions to list
|
|
||||||
* @param finalWordSeparator the word separator between the two last versions in the returned string, like "and",
|
|
||||||
* "or" or any other word of any language. The spaces before and after are already
|
|
||||||
* concatenated.
|
|
||||||
* @return a string representation of the provided list of version.
|
|
||||||
*/
|
|
||||||
public static String displayOptimizedListOfVersions(List<MinecraftVersion> versions, String finalWordSeparator) {
|
|
||||||
return StringUtil.joinGrammatically(", ", " " + finalWordSeparator + " ", getVersionsDisplayList(versions));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a string representation of the provided list of version, using
|
|
||||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the grammatical word "et"
|
|
||||||
* ("and" in french).
|
|
||||||
*
|
|
||||||
* @param versions the minecraft versions to list
|
|
||||||
* @return a string representation of the provided list of version.
|
|
||||||
* @deprecated it uses the hardcoded French word "et" as the final word separator.
|
|
||||||
* Use {@link #displayOptimizedListOfVersions(List, String)} with "et" as the last parameter instead.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true)
|
|
||||||
public static String displayOptimizedListOfVersionsAnd(List<MinecraftVersion> versions) {
|
|
||||||
return displayOptimizedListOfVersions(versions, "et");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a string representation of the provided list of version, using
|
|
||||||
* {@link StringUtil#joinGrammatically(CharSequence, CharSequence, List)} with the grammatical word "ou"
|
|
||||||
* ("or" in french).
|
|
||||||
*
|
|
||||||
* @param versions the minecraft versions to list
|
|
||||||
* @return a string representation of the provided list of version.
|
|
||||||
* @deprecated it uses the hardcoded French word "ou" as the final word separator.
|
|
||||||
* Use {@link #displayOptimizedListOfVersions(List, String)} with "ou" as the last parameter instead.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true)
|
|
||||||
public static String displayOptimizedListOfVersionsOr(List<MinecraftVersion> versions) {
|
|
||||||
return displayOptimizedListOfVersions(versions, "ou");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an optimized list of string representation of Minecraft version, that represent the provided list of
|
|
||||||
* Minecraft version.
|
|
||||||
* <p>
|
|
||||||
* This method try to merge successive Minecraft version into a single string: for instance, all versions from 1.18
|
|
||||||
* to 1.18.2 are represented by the string "1.18.x"; all version from 1.14.1 to 1.14.4 are represented by the string
|
|
||||||
* "1.14.1-1.14.4".
|
|
||||||
* <p>
|
|
||||||
* All possible merges of {@link MinecraftVersion} are listed in the static initializer of this enum.
|
|
||||||
*
|
|
||||||
* @param vList the {@link List} of {@link MinecraftVersion}
|
|
||||||
* @return an optimized list of string representation of Minecraft version.
|
|
||||||
*/
|
|
||||||
public static List<String> getVersionsDisplayList(List<MinecraftVersion> vList) {
|
|
||||||
if (vList == null)
|
|
||||||
return new ArrayList<>();
|
|
||||||
Set<MinecraftVersion> vSet = EnumSet.copyOf(vList);
|
|
||||||
|
|
||||||
List<String> ret = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < values().length; i++) {
|
|
||||||
if (!vSet.contains(values()[i]))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
EnumSet<MinecraftVersion> vSubSet = EnumSet.of(values()[i]);
|
|
||||||
while (i + 1 < values().length && vSet.contains(values()[i + 1])) {
|
|
||||||
i++;
|
|
||||||
vSubSet.add(values()[i]);
|
|
||||||
if (!versionMergeDisplay.containsKey(vSubSet)) {
|
|
||||||
vSubSet.remove(values()[i]);
|
|
||||||
i--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vSubSet.size() == 1) {
|
|
||||||
ret.addAll(values()[i].versionsDisplay);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ret.addAll(versionMergeDisplay.get(vSubSet));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
13
pom.xml
13
pom.xml
@@ -56,16 +56,20 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
<bungeecord.version>1.21-R0.1-SNAPSHOT</bungeecord.version>
|
<bungeecord.version>1.21-R0.1-SNAPSHOT</bungeecord.version>
|
||||||
<paper.version>1.20.6-R0.1</paper.version>
|
<paper.version>1.21.3-R0.1</paper.version>
|
||||||
<mc.version>1.20.6</mc.version>
|
<mc.version>1.21.3</mc.version>
|
||||||
|
|
||||||
|
<guava.version>32.1.2-jre</guava.version> <!-- Match the version imported by Paper API/BungeeCord API if possible -->
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>pandalib-bungee</module>
|
<module>pandalib-bungee</module>
|
||||||
|
<module>pandalib-bungee-chat</module>
|
||||||
<module>pandalib-bungee-permissions</module>
|
<module>pandalib-bungee-permissions</module>
|
||||||
<module>pandalib-chat</module>
|
<module>pandalib-chat</module>
|
||||||
<module>pandalib-cli</module>
|
<module>pandalib-cli</module>
|
||||||
<module>pandalib-commands</module>
|
<module>pandalib-commands</module>
|
||||||
|
<module>pandalib-config</module>
|
||||||
<module>pandalib-core</module>
|
<module>pandalib-core</module>
|
||||||
<module>pandalib-db</module>
|
<module>pandalib-db</module>
|
||||||
<module>pandalib-netapi</module>
|
<module>pandalib-netapi</module>
|
||||||
@@ -119,11 +123,6 @@
|
|||||||
</executions>
|
</executions>
|
||||||
<configuration>
|
<configuration>
|
||||||
<tags>
|
<tags>
|
||||||
<tag>
|
|
||||||
<name>apiNote</name>
|
|
||||||
<placement>a</placement>
|
|
||||||
<head>API Note:</head>
|
|
||||||
</tag>
|
|
||||||
<tag>
|
<tag>
|
||||||
<name>implSpec</name>
|
<name>implSpec</name>
|
||||||
<placement>a</placement>
|
<placement>a</placement>
|
||||||
|
Reference in New Issue
Block a user